Подтвердить что ты не робот

Как упаковать библиотеку с несколькими архитектурами .NET, предназначенную для универсальной платформы Windows?

Как мне упаковать библиотеку универсальной платформы Windows, написанную на С#, которая предлагает только зависящие от архитектуры сборки? Для иллюстрации предположим, что у меня есть некоторый код, специфичный для архитектуры, условно скомпилированный для каждой архитектуры (с использованием #if ARM и эквивалентов).

Чтобы быть ясным, никакой сборки AnyCPU для моей библиотеки не существует - только x86, x64 и ARM.

Эквивалентная и потенциально более распространенная ситуация - это ситуация, когда у меня есть зависимость от внешней библиотеки, которая предоставляется только в виде архитектурных сборок (например, Win2D). Чтобы упростить контекст, предположим, что нет зависимостей, и задействован только мой собственный код - решение должно свести к одному и тому же вопросу.

Это серия вопросов и ответов, которые документируют мои выводы по теме современного дизайна пакетов NuGet, особенно с учетом изменений, внесенных в NuGet 3. Вам также могут быть интересны некоторые связанные вопросы:

4b9b3361

Ответ 1

Этот ответ основывается на принципах упаковки библиотеки .NET Framework и принципах упаковки универсальной платформы Windows Platform. Сначала прочитайте связанные ответы, чтобы лучше понять следующее.

Я предполагаю, что все ваши архитектурные сборки выставляют одну и ту же поверхность 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, мы можем просто взять любой из них и поручить компилятору использовать ее в качестве эталонной сборки. В SDK Windows содержится утилита с именем CorFlags.exe, которая может быть использована для преобразования сборки x86 в сборку AnyCPU, что делает это возможным.

Ниже приведено создание пакета script, который создает необходимые ссылочные сборки перед упаковкой библиотеки. Предполагается, что 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 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"
}

Вам может потребоваться настроить пути в script для соответствия соглашениям, используемым в вашем решении.

Запустите этот script, чтобы создать пакет NuGet, который позволяет использовать вашу библиотеку во всех ее вариантах, специфичных для архитектуры! Не забудьте создать свое решение с помощью конфигурации Release для all архитектуры до создания пакета NuGet.

Библиотека образцов и соответствующие упаковочные файлы доступны в GitHub. Решением, соответствующим этому ответу, является MultiArchitectureUwpLibrary.