![](https://habrastorage.org/getpro/habr/upload_files/980/fdc/e16/980fdce166d5908bc0240f37d949a394.png)
Привет! Меня зовут Александр, я старший разработчик в команде, которая занимается оцифровкой документов. Когда мы разрабатываем пакеты библиотек компонентов, иногда возникает необходимость дополнить исполняемый код определенным контентом. Нередко этот контент нужен именно в виде отдельных файлов, а не встроенных ресурсов. Примерами таких задач могут быть различные пакеты .NET-оболочек, которым обычно необходимы исходные библиотеки. Нам же понадобились специальные шрифты во внутрикорпоративной библиотеке конвертации документов.
Мы видели два варианта решения проблемы:
-
Встроить шрифты как embedded-ресурсы и копировать их при инициализации библиотеки в целевую папку.
-
Добавить файлы в nuget-пакет.
Первое решение — это фактически хардкод. Если пользователь библиотеки захочет использовать свои шрифты вместо наших, мы все равно будем добавлять их в папку приложения при каждом запуске. Поэтому мы решили добавить файлы шрифтов в nuget-пакет.
Опишу решение и подводные камни, на которые наткнулся в процессе работы. На Хабре уже есть одна статья на эту тему. Я хотел бы подробнее рассказать о своем решении и обсудить некоторые моменты, которые не были разобраны в том материале.
Первая попытка (неудачная)
Сначала я решил установить для всех файлов свойства Build Action — None и Copy to Output Directory — Copy if never. Это решение отлично работает при прямых ссылках на проекты в солюшене, но в nuget-пакетах шрифты оказались доступны только в пакетах, которые напрямую ссылались на пакет со шрифтами.
Например, у нас есть nuget-пакет LibA, содержащий шрифты. LibA используется в nuget-пакете LibB, и при добавлении LibB в проект шрифты остаются доступны. LibB используется в nuget-пакете LibC, и при добавлении LibC в проект шрифты не добавляются.
Вторая попытка (удачная)
После долгого изучения MSDN и StackOverflow я пришел к выводу, что лучше сделать все вручную. То есть написать .nuspec-файл с описанием пакета и .props-файл с логикой, которая должна будет выполниться при сборке проекта.
Добавляем в проект папку buildTransitive, складываем в нее шрифты, .nuspec- и .props-файлы. Папку я назвал buildTransitive, потому что в пакете будет папка с таким же именем. Она нужна, чтобы каждый последующий пакет в цепочке ссылок имел доступ к шрифтам. Больше о ней можно узнать из документации.
Добавляем ссылки на .props и .nuspec в файле проекта.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> ... <NuspecFile>buildTransitive\LibA.nuspec</NuspecFile> </PropertyGroup> ... <Import Project="buildTransitive\LibA.props" /> </Project>
Открываем .nuspec-файл и описываем, какие файлы куда положить в нашем пакете.
<files> <file src="LibA.props" target="buildTransitive" /> <file src="fonts\**" target="buildTransitive\fonts" /> <file src="..\bin\Debug\net6.0\LibA.dll" target="lib\net6.0\LibA.dll" /> </files>
В этом случае мы копируем файлы LibA.props и папку fonts в папку пакета buildTransitive, а собранный файл проекта — в папку пакета lib\net6.0\LibA.dll.
Внимание! Всегда используйте обратный слеш в путях! Иначе можно получить разный результат при сборке под Windows и Linux. Более подробно ситуация описана здесь.
Описываем логику копирования файлов шрифтов при сборке в .props-файле.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <None Include="$(MSBuildThisFileDirectory)fonts\**" > <Link>fonts\%(RecursiveDir)%(Filename)%(Extension)</Link> <PackageCopyToOutputDirectory>PreserveNewest</PackageCopyToOutputDirectory> <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <Visible>False</Visible> </None> </ItemGroup> </Project>
В этом скрипте содержимое папки fonts из пакета при сборке будет рекурсивно скопировано в папку fonts в выходном каталоге.
Дополняем функциональность
На этом этапе уже получается собрать пакет со шрифтами, при использовании которого шрифты будут автоматически копироваться в выходной каталог независимо от длины цепочки ссылок на наш пакет.
Многие IDE позволяют заполнить данные об авторе, компании, описании пакета и так далее. В идеале эти метаданные о nuget-пакете нужно получать из файла проекта. Дополнительные переменные можно передать при сборке билда. В моем случае это была версия пакета.
Чтобы передать данные из файла проекта в .nuspec, используем тег NuspecProperties. В результате файл проекта будет выглядеть так:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>disable</ImplicitUsings> <Nullable>disable</Nullable> <GeneratePackageOnBuild>True</GeneratePackageOnBuild> <Version>$(PkgVersion)</Version> <Authors>Authors list</Authors> <Description>Project description</Description> <NuspecFile>buildTransitive\LibA.nuspec</NuspecFile> <NuspecProperties>$(NuspecProperties);PackageId=$(MSBuildProjectName)</NuspecProperties> <NuspecProperties>$(NuspecProperties);PackageAuthors=$(Authors)</NuspecProperties> <NuspecProperties>$(NuspecProperties);PackageDescription=$(Description)</NuspecProperties> </PropertyGroup> <Target Name="NuspecProperties" AfterTargets="Build"> <PropertyGroup> <NuspecProperties>$(NuspecProperties);PackageVersion=$(Version)</NuspecProperties> <NuspecProperties>$(NuspecProperties);PackageTargetPath=$(TargetPath)</NuspecProperties> </PropertyGroup> </Target> <Import Project="buildTransitive\LibA.props" /> </Project>
Номер версии будет передан при сборке в переменной PkgVersion и записан в тег Version. Переменные из .nuspec передаются в переменную NuspecProperties парами «ключ — значение» и разделяются точкой с запятой. При этом данные о версии и конечном пути к выходному файлу сборки записываются после билда.
Если не передать версию в переменной PkgVersion и запустить сборку проекта, ее номер будет 1.0.0.
Сборка проекта на билд-машине запускается с помощью команды:
- dotnet build $ SOLUTION_FILE_PATH -c Release --no-restore -p:PkgVersion=$PACKAGE_VERSION --output outDir
В этом случае, если в файле проекта тег GeneratePackageOnBuild установлен в true, будет выполнена сборка проекта и создан nuget-пакет. Если тег GeneratePackageOnBuild установить в false и разделить операции dotnet build и dotnet pack, данные из файла проекта не попадут в .nuspec-файл.
Пример .nuspec-файла с добавленными переменными, объявленными в файле проекта:
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd"> <metadata> <id>$PackageId$</id> <version>$PackageVersion$</version> <authors>$PackageAuthors$</authors> <description>$PackageDescription$</description> <dependencies> <group targetFramework="net6.0" /> </dependencies> </metadata> <files> <file src="LibA.props" target="buildTransitive" /> <file src="fonts\**" target="buildTransitive\fonts" /> <file src="$PackageTargetPath$" target="lib\net6.0\LibA.dll" /> </files> </package>
На этом все. Надеюсь, моя статья поможет кому-то сэкономить время и нервы! Тестовый проект можно посмотреть на GitHub.
Дополнительная информация
В моем тестовом проекте, который содержит шрифты, нет ссылок на другие пакеты. Если добавить ссылку на другой nuget-пакет, она будет записана в файл проекта и ее придется вручную прописать в .nuspec. Чтобы избежать ручной поддержки целостности ссылок, в рабочем проекте я вынес шрифты в отдельную сборку, в которой нет ничего кроме шрифтов, создал отдельный пакет и ссылался на него из других проектов.
Если при сборке нужно посмотреть значения переменных из файла проекта, это можно сделать с помощью тега Message.
<Project Sdk="Microsoft.NET.Sdk"> ... <Target Name="Log" AfterTargets="Build"> <Message Importance="High" Text="----------Build Variables-------------" /> <Message Importance="High" Text="MSBuildProjectName = $(MSBuildProjectName)" /> <Message Importance="High" Text="TargetPath = $(TargetPath)" /> <Message Importance="High" Text="NuspecProperties = $(NuspecProperties)" /> <Message Importance="High" Text="----------Build Variables-------------" /> </Target> ... </Project>
Если у вас остались вопросы или вы хотите поделиться опытом работы с nuget-пакетами, добро пожаловать в комментарии.
ссылка на оригинал статьи https://habr.com/ru/articles/728242/
Добавить комментарий