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

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

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

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

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


person Sander    schedule 05.01.2016    source источник


Ответы (1)


Этот ответ основан на принципах упаковки библиотеки .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.

person Sander    schedule 05.01.2016
comment
Если вы это протестируете, обязательно измените версию. Я протестировал его, и сначала он не работал. Поскольку nuget швы для кеширования, он не может установить пакет. - person lokimidgard; 20.06.2016