This answer builds upon the principles of .NET Framework library packaging and the principles of Universal Windows Platform library packaging. Read the linked answers first to better understand the following.
I will assume that all your architecture-specific builds expose the same API surface, with only the implementation of those APIs differing.
The main complication with this scenario is that the build toolchain requires an AnyCPU assembly for compile-time reference resolution, even if this assembly is never used at runtime. Since your scenario does not have AnyCPU build output, we need to find a workaround. The concept that applies here is of reference assemblies - AnyCPU assemblies only used at compile-time for reference validation. Therefore, to publish your library, you will need to create a reference assembly and package the assets as outlined below.
For simplicity, I will assume that your library has no dependencies on other NuGet packages. This is not likely to be the case in practice but dependency management is already covered by the other answers linked above and is therefore omitted from this answer.
The desired structure of the NuGet package is as follows:
+---ref
| \---uap10.0
| | MultiArchitectureUwpLibrary.dll
| | MultiArchitectureUwpLibrary.pri
| | MultiArchitectureUwpLibrary.XML
| |
| \---MultiArchitectureUwpLibrary
| ArchitectureControl.xaml
| MultiArchitectureUwpLibrary.xr.xml
|
+---runtimes
| +---win10-arm
| | \---lib
| | \---uap10.0
| | MultiArchitectureUwpLibrary.dll
| | MultiArchitectureUwpLibrary.pdb
| |
| +---win10-x64
| | \---lib
| | \---uap10.0
| | MultiArchitectureUwpLibrary.dll
| | MultiArchitectureUwpLibrary.pdb
| |
| \---win10-x86
| \---lib
| \---uap10.0
| MultiArchitectureUwpLibrary.dll
| MultiArchitectureUwpLibrary.pdb
If you have familiarized yourself with the answers linked above, the files should all be known to you already, although the directory structure is rather unusual in this case. The ref
directory contains the reference assembly, the XML documentation and the resource files, while the architecture-specific assets are structured under the runtimes directory.
Most of this is pretty straightforward and can be accomplished by using a nuspec file created based on the following template:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.2">
<id>Example.MultiArchitectureUwpLibrary</id>
<version>1.0.0</version>
<authors>Firstname Lastname</authors>
<description>Example of library that is published as a set of architecture-specific assmeblies for the UWP platform.</description>
</metadata>
<files>
<!-- Architecture-independent reference library for use at compile-time; generated by the PowerShell script. -->
<file src="..\bin\Reference\Release\MultiArchitectureUwpLibrary.dll" target="ref\uap10.0" />
<!-- XML documentation file goes together with the reference library. -->
<file src="..\bin\x86\Release\MultiArchitectureUwpLibrary.xml" target="ref\uap10.0" />
<!-- Resource files go together with the reference library. -->
<file src="..\bin\x86\Release\MultiArchitectureUwpLibrary.pri" target="ref\uap10.0" />
<file src="..\bin\x86\Release\MultiArchitectureUwpLibrary\*" target="ref\uap10.0\MultiArchitectureUwpLibrary" />
<!-- The architecture-specific files go in architecture-specific directories. -->
<file src="..\bin\x86\Release\MultiArchitectureUwpLibrary.dll" target="runtimes\win10-x86\lib\uap10.0" />
<file src="..\bin\x86\Release\MultiArchitectureUwpLibrary.pdb" target="runtimes\win10-x86\lib\uap10.0" />
<file src="..\bin\x64\Release\MultiArchitectureUwpLibrary.dll" target="runtimes\win10-x64\lib\uap10.0" />
<file src="..\bin\x64\Release\MultiArchitectureUwpLibrary.pdb" target="runtimes\win10-x64\lib\uap10.0" />
<file src="..\bin\arm\Release\MultiArchitectureUwpLibrary.dll" target="runtimes\win10-arm\lib\uap10.0" />
<file src="..\bin\arm\Release\MultiArchitectureUwpLibrary.pdb" target="runtimes\win10-arm\lib\uap10.0" />
</files>
</package>
The missing piece, of course, is the reference assembly. Thankfully, this is rather simple to solve - a reference assembly is an AnyCPU assembly that defines the same classes and methods that the runtime assemblies contain. Its main purpose is to provide the compiler a reference to work with, so the compiler can verify that all method calls are actually referencing methods that will exist at runtime. The actual code in the reference assembly (if any) is not used for anything.
Since all your architecture-specific builds expose the same API surface, we can simply take any one of them and instruct the compiler to use it as the reference assembly. The Windows SDK contains a utility named CorFlags.exe that can be used to convert an x86 assembly to an AnyCPU assembly, making this possible.
Below is a package creation script that creates the required reference assemblies before packaging the library. It assumes the Windows SDK is installed in the standard location. To understand the details of the logic, see the inline comments.
# Any assembly matching this filter will be transformed into an AnyCPU assembly.
$referenceDllFilter = "MultiArchitectureUwpLibrary.dll"
$programfilesx86 = "${Env:ProgramFiles(x86)}"
$corflags = Join-Path $programfilesx86 "Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6 Tools\x64\CorFlags.exe"
If (!(Test-Path $corflags))
{
Throw "Unable to find CorFlags.exe"
}
$solutionRoot = Resolve-Path ..\..
$topLevelDirectories = Get-ChildItem $solutionRoot -Directory
$binDirectories = $topLevelDirectories | %{ Get-ChildItem $_.FullName -Directory -Filter "bin" }
# Create reference assemblies, because otherwise the NuGet packages cannot be used.
# This creates them for all outputs that match the filter, in all output directories of all projects.
# It's a bit overkill but who cares - the process is very fast and keeps the script simple.
Foreach ($bin in $binDirectories)
{
$x86 = Join-Path $bin.FullName "x86"
$any = Join-Path $bin.FullName "Reference"
If (!(Test-Path $x86))
{
Write-Host "Skipping reference assembly generation for $($bin.FullName) because it has no x86 directory."
continue;
}
if (Test-Path $any)
{
Remove-Item -Recurse $any
}
New-Item $any -ItemType Directory
New-Item "$any\Release" -ItemType Directory
$dlls = Get-ChildItem "$x86\Release" -File -Filter $referenceDllFilter
Foreach ($dll in $dlls)
{
Copy-Item $dll.FullName "$any\Release"
}
$dlls = Get-ChildItem "$any\Release" -File -Filter $referenceDllFilter
Foreach ($dll in $dlls)
{
Write-Host "Converting to AnyCPU: $dll"
& $corflags /32bitreq- $($dll.FullName)
}
}
# Delete any existing output.
Remove-Item *.nupkg
# Create new packages for any nuspec files that exist in this directory.
Foreach ($nuspec in $(Get-Item *.nuspec))
{
.\NuGet.exe pack "$nuspec"
}
You may need to adjust the paths in the script to match the conventions used in your solution.
Run this script to create a NuGet package that enables your library to be used in all its architecture-specific variants! Remember to build your solution using the Release configuration for all the architectures before creating the NuGet package.
A sample library and the relevant packaging files are available on GitHub. The solution corresponding to this answer is MultiArchitectureUwpLibrary.