如何打包一个面向通用Windows平台的多架构.NET库?

17
我该如何打包一个仅提供架构相关版本的C#通用Windows平台库?举个例子,假设我为每种架构有一些有条件编译的架构特定代码(使用#if ARM和等效语句)。
明确一点,我的库中不存在AnyCPU版本 - 只有x86、x64和ARM版本。
一个等价且可能更常见的情况是,我依赖于一个仅提供架构特定版本的外部库(例如Win2D)。为了保持上下文简单,假设没有依赖项,只涉及我的自己的代码 - 无论哪种方式,解决方案都应该归结为相同的事情。
这是一系列关于现代NuGet包编写的问题和答案,重点关注NuGet 3引入的更改。您可能还对以下相关问题感兴趣: - 如何打包.NET Framework库? - 如何打包针对通用Windows平台的.NET库? - 如何打包针对.NET Core的可移植.NET库? - 如何打包针对.NET Framework和通用Windows平台的.NET库,并包括特定于平台的功能? - 如何打包针对通用Windows平台的.NET库,并依赖于Visual Studio扩展SDK?
1个回答

14

这篇答案基于.NET Framework库打包原则通用Windows平台库打包原则。请先阅读相关链接以更好地理解以下内容。

我假设你的所有特定架构构建都公开相同的API表面,只是这些API的实现不同。

这种情况的主要复杂性在于,即使在运行时从未使用该程序集,构建工具链也需要AnyCPU程序集进行编译时引用解析。由于您的场景没有AnyCPU生成输出,因此我们需要找到一种解决方法。适用于此处的概念是引用程序集-仅在编译时用于引用验证的AnyCPU程序集。因此,为了发布您的库,您需要创建一个引用程序集,并按下文所述打包资产。

为简单起见,我假设您的库不依赖其他NuGet软件包。在实践中可能不太可能出现这种情况,但依赖项管理已经在上面的其他链接中涵盖,因此在这篇答案中省略了此部分。

NuGet包的期望结构如下:

+---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

如果您已经熟悉了上面链接的答案,那么这些文件对您来说应该已经很熟悉了,尽管在这种情况下目录结构相当不寻常。 ref 目录包含参考程序集、XML 文档和资源文件,而架构特定的资产则结构化在运行时目录下。
大部分内容都很简单,可以通过使用基于以下模板创建的 nuspec 文件来完成:
<?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>

当然,缺少的部分是参考程序集。幸运的是,这个问题很容易解决——参考程序集是一个AnyCPU程序集,定义了与运行时程序集相同的类和方法。它的主要目的是为编译器提供一个参考,以便编译器可以验证所有方法调用实际上是引用将在运行时存在的方法。参考程序集中的实际代码(如果有)不会被用于任何事情。
由于所有特定于架构的构建都公开了相同的API表面,因此我们可以简单地选择其中任何一个,并指示编译器使用它作为参考程序集。Windows SDK包含一个名为CorFlags.exe的实用程序,可用于将x86程序集转换为AnyCPU程序集,从而实现此目的。
下面是一个创建所需参考程序集并打包库的脚本。它假定Windows SDK安装在标准位置。要了解逻辑的详细信息,请参见内联注释。
# 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"
}

你可能需要调整脚本中的路径,以匹配您解决方案中使用的约定。
运行此脚本创建一个NuGet包,使您的库能够在其所有体系结构特定变体中使用!在创建NuGet包之前,请记得使用发布配置为所有架构构建您的解决方案。
一个示例库和相关的打包文件可在GitHub上获取。对应于此答案的解决方案是MultiArchitectureUwpLibrary。

如果你测试这个,请确保更改版本。我测试过它,一开始它没有起作用。因为NuGet似乎会缓存无法安装该包的信息。 - lokimidgard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接