【问题标题】:How to package a multi-architecture .NET library that targets the Universal Windows Platform?如何打包面向通用 Windows 平台的多架构 .NET 库?
【发布时间】:2016-04-09 07:06:02
【问题描述】:

如何打包用 C# 编写的通用 Windows 平台库,该库仅提供依赖于体系结构的构建?为了便于说明,假设我为每个架构有条件地编译了一些特定于架构的代码(使用#if ARM 和等效项)。

需要明确的是,我的库不存在 AnyCPU 版本 - 只有 x86、x64 和 ARM。

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

这是一系列问题和答案,记录了我对现代 NuGet 包创作主题的发现,特别关注 NuGet 3 引入的更改。您可能还对一些相关问题感兴趣:

【问题讨论】:

    标签: nuget uwp nuget-package


    【解决方案1】:

    此答案基于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 文档和资源文件,而特定于体系结构的资产则在 runtimes 目录下构建。

    其中大部分都非常简单,可以通过使用基于以下模板创建的 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 安装在标准位置。要了解逻辑的详细信息,请参阅内联 cmets。

    # 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。

    【讨论】:

    • 如果您对此进行测试,请确保更改版本。我测试了它,它首先没有工作。因为 nuget 接缝缓存它无法安装包。
    猜你喜欢
    • 2016-04-09
    • 2016-04-09
    • 2016-04-09
    • 2016-04-09
    • 1970-01-01
    • 1970-01-01
    • 2012-04-09
    • 2013-06-05
    • 1970-01-01
    相关资源
    最近更新 更多