如何打包用C#编写的通用Windows平台库,它只提供与体系结构相关的构建?为了便于说明,假设我为每个体系结构(使用#if ARM
和等效)有条件地编译了一些特定于体系结构的代码.
要清楚,我的库没有AnyCPU构建 - 只有x86,x64和ARM.
一个等价且可能更常见的情况是我依赖外部库,它只作为特定于体系结构的构建(例如Win2D)提供.为了保持上下文简单,让我们假设没有依赖关系,只涉及我自己的代码 - 解决方案应该以任何方式减少到相同的事情.
这是一系列问题和答案,记录了我对现代NuGet包创作主题的研究结果,特别关注NuGet 3引入的变化.您可能还对一些相关问题感兴趣:
如何打包.NET Framework库?
如何打包面向通用Windows平台的.NET库?
如何打包面向.NET Core的可移植.NET库?
如何打包面向.NET Framework和通用Windows平台的.NET库并包含特定于平台的功能?
如何打包一个针对通用Windows平台并依赖于Visual Studio扩展SDK的.NET库?
Sander.. 12
这个答案建立在.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文件来完成:
Example.MultiArchitectureUwpLibrary
1.0.0
Firstname Lastname
Example of library that is published as a set of architecture-specific assmeblies for the UWP platform.
当然,缺失的部分是参考组件.值得庆幸的是,这很容易解决 - 引用程序集是一个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包之前,请记住使用所有体系结构的Release配置构建解决方案.
GitHub上提供了一个示例库和相关的打包文件.与此答案对应的解决方案是MultiArchitectureUwpLibrary.
这个答案建立在.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文件来完成:
Example.MultiArchitectureUwpLibrary
1.0.0
Firstname Lastname
Example of library that is published as a set of architecture-specific assmeblies for the UWP platform.
当然,缺失的部分是参考组件.值得庆幸的是,这很容易解决 - 引用程序集是一个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包之前,请记住使用所有体系结构的Release配置构建解决方案.
GitHub上提供了一个示例库和相关的打包文件.与此答案对应的解决方案是MultiArchitectureUwpLibrary.