如何打包以通用Windows平台为目标的多架构.NET库?

时间:2016-01-05 12:41:53

标签: nuget uwp nuget-package

如何打包用C#编写的通用Windows平台库,它只提供与体系结构相关的构建?为了便于说明,假设我为每个体系结构有条件地编译了一些特定于体系结构的代码(使用#if ARM和等价物)。

要清楚,我的库没有AnyCPU构建 - 只有x86,x64和ARM。

一种等效且可能更常见的情况是我依赖外部库,而外部库仅作为特定于体系结构的构建(例如Win2D)。为了保持上下文简单,让我们假设没有依赖关系,只涉及我自己的代码 - 解决方案应该以相同的方式减少。

  

这是一系列问题和答案,记录了我对现代NuGet包创作主题的研究结果,特别关注NuGet 3引入的变化。您可能也对一些相关问题感兴趣:

     

1 个答案:

答案 0 :(得分:12)

此答案以principles of .NET Framework library packagingprinciples of Universal Windows Platform library packaging为基础。首先阅读链接的答案,以便更好地理解以下内容。

我将假设所有特定于体系结构的构建都暴露了相同的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包之前,请记住使用针对所有架构的版本配置构建解决方案。

示例库和相关的打包文件为available on GitHub。与此答案对应的解决方案是MultiArchitectureUwpLibrary。