Skip to content

Latest commit

 

History

History
126 lines (75 loc) · 11.6 KB

Packaging.md

File metadata and controls

126 lines (75 loc) · 11.6 KB

[Note: This document is slightly outdated. Some of the steps described below have already found their way into the cargo uwp custom command and the build system it generates.]

Packaging

This document outlines the steps required to package a UWP application for Store submission. The process needs to perform the following, in order:

  • Compiling source files to modules/PDBs (.exe, .dll, .pdb)
  • Creating an Appx package for each target architecture (.appx)
  • Creating an Appx bundle containing all packages (.appxbundle)
  • Stripping private symbols from PDBs and zipping them per target architecture (.appxsym)
  • Zipping the bundle and symbols (.appxupload)

Compiling

The source code needs to be compiled for each target architecture. To get a UWP application, Rust needs to use one of the *-uwp-windows-* toolchains. Those are in Tier 3 support, meaning that rustup doesn't serve a pre-built standard library. Instead, it has to be compiled as well.

Cargo conveniently provides the build-std unstable feature, thus necessitating the use of the +nightly channel. The following command line argument will be used: -Z build-std=std,panic_abort. For this to work a --target argument needs to be supplied as well. This has not yet been incorporated into the automated build of this repo.

Once that is done, there should be an .exe and .pdb for each target architecture built, ready to be packaged into an .appx.

Packaging

With the application compiled, the binaries (well, just one .exe for now) can be packaged into an .appx alongside the assets. It's probably possible to package all shared assets into an individual .appx to reduce the overall deployment size, though let's just keep things simple for now, and copy them into each target architecture's .appx.

Per-architecture manifest file

Each target architecture requires its own AppxManifest.xml file. Eventually, this should be generated by the automated build from a template, yet this has not been implemented. There are a few key elements that need to be correct in order for Store submission and launching of the installed application to succeed:

The <Package><Identity/> element needs to have a ProcessorArchitecture attribute that matches the object code in the binaries. It is used, among others, to determine how to resolve a <PackageDependency>. When missing, the architecture defaults to "neutral", causing Store submission to fail when resolving, e.g. the "VCLibs" dependency. This attribute should be set by the automated build system (though hasn't been implemented yet).

While it is not clear what the Version attribute is ultimately used for, it should probably be set to match the application version. It follows the pattern <Major>.<Minor>.<Build>.<Revision>, with "0.0.0.0" carrying special semantics (but let's not make use of those for now). Though I wasn't able to produce any documentation on this, it seems that the <Revision> field needs to be 0. A previous attempt to submit version "0.0.0.1" to the Store failed, where switching to "0.0.1.0" succeeded. The semantics around the <Revision> field need some more research. It seems reasonable to use this attribute (in the respective template) as an input to the automated build, deriving the names of the individual .appx packages. Again, this has not yet been implemented.

The <Package><Dependencies> element needs to contain all external dependencies. Notably, the "VCLibs" need to be specified for UWP applications. While this element can be static (sourced from the template), it has caused me some grief. The following element works as desired:

<PackageDependency Name="Microsoft.VCLibs.140.00" MinVersion="14.0.27810.0" Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" />

Note the unusual 140.00 version indicator. I don't know why there is a (seemingly) trailing 0 on the major version, though it has been verified that removing it will fail Store submission, due to the dependency failling to resolve. While the dependency needs to be spelled out in the manifest, it is not required to actually include it in the package. Apparently, the Store will automagically deliver it to clients. In fact, it's not even possible to include the dependency. Indeed, if it is included in the package, Store submission will fail since it isn't permitted to deploy a "Framework" package. Presumably, all of the above also applies to WinUI (2.x), but this has not yet been verified.

The <Package><Applications><Application> element has several attributes. It is unclear what Id and EntryPoint are used for, and subscribing to some (reasonable) static strings in the manifest template file appears to work. The Executable attribute needs to match the name of the binary created above. Though not strictly required, the names for the executables targeting different architectures can be different, and this attribute should be adjusted by an automated build.

Preparing the package layout

The MakeAppx.exe tool offers two ways to describe the final package layout: Implicit or explicit, by directory layout or a mapping file (using the /d or /f command line options, respectively). Either one needs to be prepared by an automated build.

Neither one is, at this time, implemented in the automated build. The following is a description of what is needed to make this work out.

A mapping file describes the local, file system relative path names of artifacts that will be included in the final .appx. It maps those to package-relative path names. Note that the application manifest needs to reference assets using package relative path names. If, for example, a StoreLogo.png gets packaged into Assets\StoreLogo.png, this needs to be reflected in the manifest, e.g. <Logo>Assets\StoreLogo.png</Logo>. Store submission will fail if the referenced assets cannot be resolved. A mapping file would need to be generated (ideally from a template) by the build system, replacing the respective per-architecture output directory placeholder. Using a mapping file has the benefit of allowing the build system to operate on the output directory directly, without first having to copy artifacts to a (temporary) directory.

The other option would be to copy all artifacts to a (temporary) staging directory that resembles the package relative structure, and feed that directory straight into MakeAppx.exe. Both options require build system support, so there's not a lot of a difference. Using a mapping file has the slight advantage of allowing to name the package contents in a version-controlled text file, rather than encoding that logic in the build system (which, granted, is also version-controlled).

Running MakeAppx.exe

With everything prepared it's now time to actually create a package. Depending on the strategy chosen in the previous step, either command line will accomplish this:

makeappx pack /p <name>_<Major>.<Minor>.<Build>.<Revision>_<arch>.appx /v /f <mapping file>

or

makeappx pack /p <name>_<Major>.<Minor>.<Build>.<Revision>_<arch>.appx /v /d <source directory>

With that done, we now have an .appx package per each supported target architecture. Next up is wrapping things up in a bundle.

Bundling

With the individual .appx packages created, it's time to collect them into an .appxbundle. A bundle allows an application to be compiled for different architectures, while being deployed as a single unit. It also provides the ability to share assets between application packages, though let's keep the complexity down and not make use of this at this time.

Bundling follows similar rules as packaging in declaring the packages that should get included. It, too, allows to use a mapping file, or a staging directory. The MakeAppx.exe tool can be used to create a bundle, much the same way as a package. Depending on whether a mapping file is used or a staging directory, the following command lines will do:

makeappx bundle /v /o /bv <Major>.<Minor>.<Build>.<Revision> /f <mapping file> /p <name>_<Major>.<Minor>.<Build>.<Revision>_<arch1>_<arch2>_<...>.appxbundle

or

makeappx bundle /v /o /bv <Major>.<Minor>.<Build>.<Revision> /d <source directory> /p <name>_<Major>.<Minor>.<Build>.<Revision>_<arch1>_<arch2>_<...>.appxbundle

Note: Zipping the individual .appx packages directly into an .appxupload archive will succeed Store submission, but only one .appx package will be published (presumably the one that sorts alphabetically first). Bundling is required to support multiple target architectures.

A few notes on the command line arguments:

  • /v Enables verbose logging
  • /o Overwrites existing files without asking for confirmation
  • /bv Sets the version of the bundle (defaults to 0.0.0.0 when missing)
  • /p Sets the output file name. By convention, this includes the list of _-separated target architectures

Creating symbol files

To get better crash diagnostics, it is advisable to include public symbols in the upload bundle. Each target architecture should have generated .pdb files for the respective binaries. Those include public as well as private symbols. Only the public symbols are needed, and the PDBCopy tool (part of the Windows Debugging Tools, default install location on 64-bit systems C:\Program Files (x86)\Windows Kits\10\Debuggers\) can be used to strip private symbols.

Once all the stripped .pdb's have been generated, they only need to be zipped into a (per-architecture) archive. The file name follows the pattern <name>_<Major>.<Minor>.<Build>.<Revision>_<arch>.appxsym.

Creating the upload file

Everything is in place now to create an .appxupload file for Store submission. The process is rather mundane, and merely involves zipping the .appxbundle and .appxsym's into a single archive. The file name follows the pattern <name>_<Major>.<Minor>.<Build>.<Revision>_<arch1>_<arch2>_<...>.appxupload. This file can readily be deployed through the Microsoft Store.

Signing

Store submissions require that the uploaded .appx packages are cryptographically signed, irrespective of whether they are submitted individually or as part of an .appxbundle. At this time it has not been verified, whether an .appxupload archive needs to be signed as well. The signing process follows the following procedure.

Generating a signing certificate

This is required only once (or whenever the certificate expires after a year, by default). To do this an elevated PowerShell prompt is required.

$cert=New-SelfSignedCertificate -Type Custom -Subject "CN=<Publisher>" -KeyUsage DigitalSignature -FriendlyName "<name>" -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}")
$data=$cert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
[io.file]::WriteAllBytes('<name>_TemporaryKey.pfx', $data)

This produces the <name>_TemporaryKey.pfx file containing the signing certificate. To inspect and verify that the certificate has been generated, you can launch certlm.msc and navigate to Personal/Certificates on the Local Computer.

Signing the artifact

With the certificate in place we can now sign a package using the SignTool (part of the Windows SDK). The command line looks like this:

signtool sign /v /fd SHA256 /a /f <name>_TemporaryKey.pfx <package name>.appx