Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этом руководстве вы создадите настраиваемую задачу в MSBuild на C#, которая обрабатывает создание кода, а затем будете использовать задачу в сборке. В этом примере показано, как использовать MSBuild для обработки операций очистки и перестроения. В примере также показано, как поддерживать добавочную сборку, чтобы код был создан только при изменении входных файлов. Описанные методы применимы к широкому спектру сценариев создания кода. В шагах также показано использование NuGet для упаковки задачи с целью распространения, и в этом руководстве содержится необязательный шаг для использования средства просмотра BinLog, чтобы улучшить процесс устранения неполадок.
Необходимые условия
У вас должно быть представление о понятиях MSBuild, таких как задачи, целевые объекты и свойства. См. основные понятия MSBuild .
В примерах требуется MSBuild, которая установлена с Visual Studio, но также может быть установлена отдельно. См. раздел Скачивание MSBuild безVisual Studio.
Общие сведения о примере кода
Пример принимает входной текстовый файл, содержащий значения, и создает файл кода C#с кодом, который создает эти значения. Хотя это простой пример, те же основные методы можно применять к более сложным сценариям создания кода.
В этом руководстве вы создадите настраиваемую задачу MSBuild с именем AppSettingStronglyTyped. Задача считывает набор текстовых файлов и каждый файл со строками со следующим форматом:
propertyName:type:defaultValue
Код создает класс C# со всеми константами. Проблема должна остановить сборку и предоставить пользователю достаточно информации для диагностики проблемы.
Полный пример кода для этого учебника приведен в разделе "Настраиваемая задача – создание кода" в репозитории примеров .NET на GitHub.
Создайте проект AppSettingStronglyTyped
Создайте библиотеку классов .NET Standard. Платформа должна быть .NET Standard 2.0.
Обратите внимание на разницу между полной msBuild (той, которую использует Visual Studio) и переносимой MSBuild, которая входит в состав командной строки .NET Core.
- Full MSBuild: эта версия MSBuild обычно находится в Visual Studio. Выполняется в .NET Framework. Visual Studio использует это при выполнении сборки в вашем решении или проекте. Эта версия также доступна в среде командной строки, такой как командная строка разработчика Visual Studio или PowerShell.
- .NET MSBuild: эта версия MSBuild входит в состав командной строки .NET Core. Он выполняется в .NET Core. Visual Studio не вызывает эту версию MSBuild напрямую. Он поддерживает только проекты, которые создаются с помощью Microsoft.NET.Sdk.
Если вы хотите делиться кодом между .NET Framework и любой другой реализацией .NET, например, .NET Core, ваша библиотека должна быть нацелена на .NET Standard 2.0, и вы хотите запускать код в Visual Studio, которая работает на .NET Framework. Платформа .NET Framework не поддерживает .NET Standard 2.1.
Выберите версию API MSBuild для использования
При компиляции настраиваемой задачи следует ссылаться на версию API MSBuild (Microsoft.Build.*
), которая соответствует минимальной версии Visual Studio и (или) пакету SDK для .NET, который будет поддерживаться. Например, для поддержки пользователей на Visual Studio 2019, вам следует сборку осуществлять с использованием MSBuild 16.11.
Создание настраиваемой задачи AppSettingStronglyTyped MSBuild
Первым шагом является создание настраиваемой задачи MSBuild. Сведения о том, как создать настраиваемую задачу MSBuild, могут помочь вам лучше понять последующие шаги. Настраиваемая задача MSBuild — это класс, реализующий интерфейс ITask.
Добавьте ссылку на пакет NuGet Microsoft.Build.Utilities.Core, а затем создайте класс с именем AppSettingStronglyTyped, производный от Microsoft.Build.Utilities.Task.
Добавьте три свойства. Эти свойства определяют параметры задачи, заданной пользователями при использовании задачи в клиентском проекте:
//The name of the class which is going to be generated [Required] public string SettingClassName { get; set; } //The name of the namespace where the class is going to be generated [Required] public string SettingNamespaceName { get; set; } //List of files which we need to read with the defined format: 'propertyName:type:defaultValue' per line [Required] public ITaskItem[] SettingFiles { get; set; }
Задача обрабатывает SettingFiles и создает класс
SettingNamespaceName.SettingClassName
. Созданный класс будет иметь набор констант на основе содержимого текстового файла.Выходные данные задачи должны быть строкой, которая дает имя файла созданного кода:
// The filename where the class was generated [Output] public string ClassNameFile { get; set; }
Когда вы создаёте настраиваемую задачу, вы наследуете от Microsoft.Build.Utilities.Task. Чтобы реализовать задачу, переопределите метод Execute(). Метод
Execute
возвращаетtrue
, если задача выполнена успешно, иfalse
в противном случае.Task
реализует Microsoft.Build.Framework.ITask и предоставляет реализации некоторых членовITask
по умолчанию, а также предоставляет некоторые функции ведения журнала. Важно выводить состояние в журнал для диагностики и устранения неполадок задачи, особенно если возникает проблема, и задача должна возвращать результат ошибки (false
). При ошибке класс сигнализирует об ошибке путем вызова TaskLoggingHelper.LogError.public override bool Execute() { //Read the input files and return a IDictionary<string, object> with the properties to be created. //Any format error it will return false and log an error var (success, settings) = ReadProjectSettingFiles(); if (!success) { return !Log.HasLoggedErrors; } //Create the class based on the Dictionary success = CreateSettingClass(settings); return !Log.HasLoggedErrors; }
API задач позволяет возвращать значение false, указывающее на сбой, не указывая пользователю, что произошло неправильно. Лучше всего возвращать
!Log.HasLoggedErrors
вместо логического кода и регистрировать ошибку, когда что-то происходит неправильно.
Ошибки лога
Рекомендуется предоставлять такие сведения, как номер строки и отдельный код ошибки при ведении журнала ошибок. Следующий код анализирует текстовый входной файл и использует метод TaskLoggingHelper.LogError с номером строки в текстовом файле, который вызвал ошибку.
private (bool, IDictionary<string, object>) ReadProjectSettingFiles()
{
var values = new Dictionary<string, object>();
foreach (var item in SettingFiles)
{
int lineNumber = 0;
var settingFile = item.GetMetadata("FullPath");
foreach (string line in File.ReadLines(settingFile))
{
lineNumber++;
var lineParse = line.Split(':');
if (lineParse.Length != 3)
{
Log.LogError(subcategory: null,
errorCode: "APPS0001",
helpKeyword: null,
file: settingFile,
lineNumber: lineNumber,
columnNumber: 0,
endLineNumber: 0,
endColumnNumber: 0,
message: "Incorrect line format. Valid format prop:type:defaultvalue");
return (false, null);
}
var value = GetValue(lineParse[1], lineParse[2]);
if (!value.Item1)
{
return (value.Item1, null);
}
values[lineParse[0]] = value.Item2;
}
}
return (true, values);
}
Используя методы, показанные в предыдущем коде, ошибки в синтаксисе текстового входного файла отображаются в виде ошибок сборки с полезными диагностическими сведениями:
Microsoft (R) Build Engine version 17.2.0 for .NET Framework
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 2/16/2022 10:23:24 AM.
Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" on node 1 (default targets).
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
Done Building Project "S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default targets) -- FAILED.
Build FAILED.
"S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild" (default target) (1) ->
(generateSettingClass target) ->
S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\error-prop.setting(1): error APPS0001: Incorrect line format. Valid format prop:type:defaultvalue [S:\work\msbuild-examples\custom-task-code-generation\AppSettingStronglyTyped\AppSettingStronglyTyped.Test\bin\Debug\net6.0\Resources\testscript-fail.msbuild]
0 Warning(s)
1 Error(s)
При перехвате исключений в задаче используйте метод TaskLoggingHelper.LogErrorFromException. Это улучшит результаты вывода ошибок, например, путем получения стека вызовов, в котором было выброшено исключение.
catch (Exception ex)
{
// This logging helper method is designed to capture and display information
// from arbitrary exceptions in a standard way.
Log.LogErrorFromException(ex, showStackTrace: true);
return false;
}
Реализация других методов, использующих эти входные данные для сборки текста для созданного файла кода, не отображается здесь; см. AppSettingStronglyTyped.cs в примере репозитория.
Приведённый код создаёт C#-код во время процесса сборки. Задача похожа на любой другой класс C#, поэтому при завершении работы с этим руководством вы можете настроить его и добавить все функциональные возможности, необходимые для собственного сценария.
Создание консольного приложения и использование настраиваемой задачи
В этом разделе описано, как создать стандартное консольное приложение .NET Core, использующее задачу.
Важный
Важно избежать создания настраиваемой задачи MSBuild в том же процессе MSBuild, который будет его использовать. Новый проект должен быть помещён в совершенно другое решение Visual Studio или использовать библиотеку dll, заранее созданную и перемещенную из стандартных выходных данных.
Создайте проект консоли .NET MSBuildConsoleExample в новом решении Visual Studio.
Обычный способ распространения задачи — через пакет NuGet, но во время разработки и отладки можно включить все сведения о
.props
и.targets
непосредственно в файл проекта приложения, а затем перейти к формату NuGet при распространении задачи другим пользователям.Измените файл проекта, чтобы использовать задачу генерации кода. В этом разделе показан измененный файл проекта, в котором приведена ссылка на задачу, указаны входные параметры для задачи и прописаны цели для выполнения операций очистки и перестроения таким образом, чтобы сгенерированный файл кода удалялся, как и ожидалось.
Задачи регистрируются с помощью элемента UsingTask (MSBuild). Элемент
UsingTask
регистрирует задачу; Он сообщает MSBuild имя задачи и как найти и запустить сборку, содержащую класс задач. Путь сборки является относительным к файлу проекта.PropertyGroup
содержит определения свойств, соответствующие свойствам, определенным в задаче. Эти свойства задаются с помощью атрибутов, а имя задачи используется в качестве имени элемента.TaskName
— это имя задачи для использования в сборке. Этот атрибут всегда должен использовать полностью указанные пространства имен.AssemblyFile
— это путь к файлу сборки.Чтобы вызвать задачу, добавьте задачу в соответствующий целевой объект, в этом случае
GenerateSetting
.Целевая
ForceGenerateOnRebuild
выполняет операции очистки и сборки путем удаления созданного файла. Он будет выполняться после целевого объектаCoreClean
, установив для атрибутаAfterTargets
значениеCoreClean
.<Project Sdk="Microsoft.NET.Sdk"> <UsingTask TaskName="AppSettingStronglyTyped.AppSettingStronglyTyped" AssemblyFile="..\..\AppSettingStronglyTyped\AppSettingStronglyTyped\bin\Debug\netstandard2.0\AppSettingStronglyTyped.dll"/> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <RootFolder>$(MSBuildProjectDirectory)</RootFolder> <SettingClass>MySetting</SettingClass> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> <SettingExtensionFile>mysettings</SettingExtensionFile> </PropertyGroup> <ItemGroup> <SettingFiles Include="$(RootFolder)\*.mysettings" /> </ItemGroup> <Target Name="GenerateSetting" BeforeTargets="CoreCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs"> <AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)"> <Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" /> </AppSettingStronglyTyped> <ItemGroup> <Compile Remove="$(SettingClassFileName)" /> <Compile Include="$(SettingClassFileName)" /> </ItemGroup> </Target> <Target Name="ForceReGenerateOnRebuild" AfterTargets="CoreClean"> <Delete Files="$(RootFolder)\$(SettingClass).generated.cs" /> </Target> </Project>
Заметка
Вместо переопределения целевого объекта, например
CoreClean
, этот код использует другой способ упорядочивания целевых объектов (BeforeTarget и AfterTarget). Проекты в стиле SDK имеют неявный импорт целевых объектов после последней строки файла проекта; Это означает, что вы не можете переопределить целевые объекты по умолчанию, если не указать импорт вручную. См. переопределение предварительно заданных целей.Атрибуты
Inputs
иOutputs
помогают MSBuild эффективнее предоставлять сведения для добавочных сборок. Даты входных данных сравниваются с выходными данными, чтобы узнать, требуется ли выполнить целевой объект или можно ли повторно использовать выходные данные предыдущей сборки.Создайте входной текстовый файл с расширением, определенным для обнаружения. Используя расширение по умолчанию, создайте
MyValues.mysettings
в корне со следующим содержимым:Greeting:string:Hello World!
Пересоберите проект, и файл должен быть создан и собран. Проверьте в папке проекта файл MySetting.generated.cs.
Класс MySetting находится в неправильном пространстве имен, измените его так, чтобы использовать наше пространство имен приложения. Откройте файл проекта и добавьте следующий код:
<PropertyGroup> <SettingNamespace>MSBuildConsoleExample</SettingNamespace> </PropertyGroup>
Перестройте снова и обратите внимание, что класс находится в пространстве имен
MSBuildConsoleExample
. Таким образом, можно переопределить имя созданного класса (SettingClass
), текстовые файлы расширений (SettingExtensionFile
) для использования в качестве входных данных, а также расположение (RootFolder
) из них, если вы хотите.Откройте
Program.cs
и измените жестко закодированный "Hello World!!" для определяемой пользователем константы:static void Main(string[] args) { Console.WriteLine(MySetting.Greeting); }
Выполнение программы; он распечатает приветствие из созданного класса.
(Необязательно) Журнал событий во время процесса сборки
Можно скомпилировать с помощью команды командной строки. Перейдите в папку проекта. Вы будете использовать параметр -bl
(двоичный журнал) для создания двоичного журнала. Двоичный журнал будет содержать полезную информацию, чтобы понять, что происходит во время сборочного процесса.
# Using dotnet MSBuild (run core environment)
dotnet build -bl
# or full MSBuild (run on net framework environment; this is used by Visual Studio)
msbuild -bl
Обе команды создают файл журнала msbuild.binlog
, который можно открыть с помощью msBuild Binary и структурированного средства просмотра журналов. Параметр /t:rebuild
означает запуск целевого объекта перестроения. Принудительное повторное создание кодового файла.
Поздравляю! Вы создали задачу, которая создает код и использует ее в сборке.
Упаковать задачу для распространения
Если вам нужно использовать пользовательскую задачу только в нескольких проектах или в одном решении, использование задачи в качестве сырой сборки может быть всем, что вам нужно, но лучший способ подготовить задачу, чтобы использовать ее в другом месте или поделиться ею с другими — в виде пакета NuGet.
Пакеты задач MSBuild имеют несколько ключевых отличий от пакетов NuGet библиотеки:
- Они должны упаковать свои зависимости сборки, а не предоставлять их для использования в проекте, который использует эти зависимости.
- Они не упаковывают необходимые сборки в папку
lib/<target framework>
, так как это приведет к тому, что NuGet будет включать сборки в любой пакет, использующее задачу. - Они должны только компилировать с использованием сборок Microsoft.Build — во время выполнения их предоставляет фактический движок MSBuild, поэтому нет необходимости включать их в пакет.
- Они создают специальный файл
.deps.json
, который помогает MSBuild загружать зависимости задачи (особенно собственные зависимости) согласованно.
Чтобы достичь всех этих целей, необходимо внести несколько изменений в стандартный файл проекта, сверх тех, с которыми вы можете быть знакомы.
Создание пакета NuGet
Создание пакета NuGet — это рекомендуемый способ распространения пользовательской задачи другим пользователям.
Подготовка к созданию пакета
Чтобы подготовиться к созданию пакета NuGet, внесите некоторые изменения в файл проекта, чтобы указать сведения, описывающие пакет. Созданный исходный файл проекта похож на следующий код:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="17.0.0" />
</ItemGroup>
</Project>
Чтобы создать пакет NuGet, добавьте следующий код, чтобы задать свойства пакета. Полный список поддерживаемых свойств MSBuild можно просмотреть в документации по пакету :
<PropertyGroup>
...
<IsPackable>true</IsPackable>
<Version>1.0.0</Version>
<Title>AppSettingStronglyTyped</Title>
<Authors>Your author name</Authors>
<Description>Generates a strongly typed setting class base on a text file.</Description>
<PackageTags>MyTags</PackageTags>
<Copyright>Copyright ©Contoso 2022</Copyright>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
...
</PropertyGroup>
Свойство CopyLocalLockFileAssemblies необходимо, чтобы убедиться, что зависимости копируются в выходной каталог.
Пометка зависимостей как частных
Зависимости задачи MSBuild необходимо упаковыть внутри пакета; Они не могут быть выражены как обычные ссылки на пакеты. Пакет не раскрывает стандартные зависимости внешним пользователям. Для этого необходимо выполнить два шага: пометить сборки как частные и внедрить их в создаваемый пакет. В этом примере предполагается, что ваша задача будет зависеть от Microsoft.Extensions.DependencyInjection
для выполнения, поэтому добавьте PackageReference
в Microsoft.Extensions.DependencyInjection
в версии 6.0.0
.
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0" />
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0" />
</ItemGroup>
Теперь отметьте каждую зависимость этого проекта задачи атрибутами PackageReference
и ProjectReference
, используя атрибут PrivateAssets="all"
. Это укажет NuGet совсем не предоставлять эти зависимости потребляющим проектам. Дополнительные сведения об управлении ресурсами зависимостей см. в документации NuGet .
<ItemGroup>
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
/>
<PackageReference
Include="Microsoft.Extensions.DependencyInjection"
Version="6.0.0"
PrivateAssets="all"
/>
</ItemGroup>
Упакуйте зависимости в пакет
Необходимо также внедрить ресурсы среды выполнения наших зависимостей в пакет задач. Здесь есть две части: целевой объект MSBuild, который добавляет наши зависимости в BuildOutputInPackage
ItemGroup, и несколько свойств, управляющих макетом этих BuildOutputInPackage
элементов. Дополнительные сведения об этом процессе см. в документации по NuGet.
<PropertyGroup>
...
<!-- This target will run when MSBuild is collecting the files to be packaged, and we'll implement it below. This property controls the dependency list for this packaging process, so by adding our custom property we hook ourselves into the process in a supported way. -->
<TargetsForTfmSpecificBuildOutput>
$(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage
</TargetsForTfmSpecificBuildOutput>
<!-- This property tells MSBuild where the root folder of the package's build assets should be. Because we are not a library package, we should not pack to 'lib'. Instead, we choose 'tasks' by convention. -->
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<!-- NuGet does validation that libraries in a package are exposed as dependencies, but we _explicitly_ do not want that behavior for MSBuild tasks. They are isolated by design. Therefore we ignore this specific warning. -->
<NoWarn>NU5100</NoWarn>
<!-- Suppress NuGet warning NU5128. -->
<SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
...
</PropertyGroup>
...
<!-- This is the target we defined above. It's purpose is to add all of our PackageReference and ProjectReference's runtime assets to our package output. -->
<Target
Name="CopyProjectReferencesToPackage"
DependsOnTargets="ResolveReferences">
<ItemGroup>
<!-- The TargetPath is the path inside the package that the source file will be placed. This is already precomputed in the ReferenceCopyLocalPaths items' DestinationSubPath, so reuse it here. -->
<BuildOutputInPackage
Include="@(ReferenceCopyLocalPaths)"
TargetPath="%(ReferenceCopyLocalPaths.DestinationSubPath)" />
</ItemGroup>
</Target>
Не упаковывайте сборку Microsoft.Build.Utilities.Core
Как описано выше, эта зависимость будет предоставляться самим MSBuild во время выполнения, поэтому нам не нужно упаковать его в пакет. Чтобы это сделать, добавьте атрибут ExcludeAssets="Runtime"
к PackageReference
.
...
<PackageReference
Include="Microsoft.Build.Utilities.Core"
Version="17.0.0"
PrivateAssets="all"
ExcludeAssets="Runtime"
/>
...
Создайте и внедрите файл deps.json
Файл deps.json
можно использовать с помощью MSBuild для обеспечения загрузки правильных версий ваших зависимостей. Необходимо добавить некоторые свойства MSBuild, чтобы создать файл, так как он не создается по умолчанию для библиотек. Затем добавьте целевой объект, чтобы включить его в выходные данные пакета, аналогично тому, как вы сделали для зависимостей пакета.
<PropertyGroup>
...
<!-- Tell the SDK to generate a deps.json file -->
<GenerateDependencyFile>true</GenerateDependencyFile>
...
</PropertyGroup>
...
<!-- This target adds the generated deps.json file to our package output -->
<Target
Name="AddBuildDependencyFileToBuiltProjectOutputGroupOutput"
BeforeTargets="BuiltProjectOutputGroup"
Condition=" '$(GenerateDependencyFile)' == 'true'">
<ItemGroup>
<BuiltProjectOutputGroupOutput
Include="$(ProjectDepsFilePath)"
TargetPath="$(ProjectDepsFileName)"
FinalOutputPath="$(ProjectDepsFilePath)" />
</ItemGroup>
</Target>
Включение свойств и целевых объектов MSBuild в пакет
Для получения дополнительной информации о свойствах и целевых объектах, а также о том, как включить свойства и целевые объекты в пакет NuGet.
В некоторых случаях может потребоваться добавить пользовательские целевые объекты сборки или свойства в проектах, использующих пакет, например запуск пользовательского средства или процесса во время сборки. Это можно сделать, разместив файлы в форме <package_id>.targets
или <package_id>.props
в папке build
проекта.
Файлы в корневой папке сборки проекта считаются подходящими для всех целевых фреймворков.
В этом разделе вы подключите реализацию задачи в файлах .props
и .targets
, которые будут включены в пакет NuGet и автоматически загружены из проекта, на который ссылаются.
В файле проекта задачи AppSettingStronglyTyped.csprojдобавьте следующий код:
<ItemGroup> <!-- these lines pack the build props/targets files to the `build` folder in the generated package. by convention, the .NET SDK will look for build\<Package Id>.props and build\<Package Id>.targets for automatic inclusion in the build. --> <Content Include="build\AppSettingStronglyTyped.props" PackagePath="build\" /> <Content Include="build\AppSettingStronglyTyped.targets" PackagePath="build\" /> </ItemGroup>
Создайте папку сборки и в этой папке добавьте два текстовых файла:
AppSettingStronglyTyped.props
и AppSettingStronglyTyped.targets.AppSettingStronglyTyped.props
импортируется в начале Microsoft.Common.props, а свойства, определенные позже, недоступны для него. Поэтому избегайте ссылки на свойства, которые еще не определены; они будут оцениваться как пустые.Directory.Build.targets импортируется из Microsoft.Common.targets после импорта файлов
.targets
из пакетов NuGet. Таким образом, он может переопределять свойства и целевые параметры, определенные в большинстве логики сборки, или задавать свойства для всех ваших проектов, независимо от установок в отдельных проектах. См. порядок импорта.AppSettingStronglyTyped.props включает задачу и определяет некоторые свойства со значениями по умолчанию:
<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--defining properties interesting for my task--> <PropertyGroup> <!--The folder where the custom task will be present. It points to inside the nuget package. --> <_AppSettingsStronglyTyped_TaskFolder>$(MSBuildThisFileDirectory)..\tasks\netstandard2.0</_AppSettingsStronglyTyped_TaskFolder> <!--Reference to the assembly which contains the MSBuild Task--> <CustomTasksAssembly>$(_AppSettingsStronglyTyped_TaskFolder)\$(MSBuildThisFileName).dll</CustomTasksAssembly> </PropertyGroup> <!--Register our custom task--> <UsingTask TaskName="$(MSBuildThisFileName).AppSettingStronglyTyped" AssemblyFile="$(CustomTasksAssembly)"/> <!--Task parameters default values, this can be overridden--> <PropertyGroup> <RootFolder Condition="'$(RootFolder)' == ''">$(MSBuildProjectDirectory)</RootFolder> <SettingClass Condition="'$(SettingClass)' == ''">MySetting</SettingClass> <SettingNamespace Condition="'$(SettingNamespace)' == ''">example</SettingNamespace> <SettingExtensionFile Condition="'$(SettingExtensionFile)' == ''">mysettings</SettingExtensionFile> </PropertyGroup> </Project>
Файл
AppSettingStronglyTyped.props
автоматически включается при установке пакета. Затем клиент имеет доступную задачу и некоторые значения по умолчанию. Тем не менее, он никогда не используется. Чтобы поместить этот код в действие, определите некоторые целевые объекты в файлеAppSettingStronglyTyped.targets
, который также будет автоматически включен при установке пакета:<?xml version="1.0" encoding="utf-8" ?> <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <!--Defining all the text files input parameters--> <ItemGroup> <SettingFiles Include="$(RootFolder)\*.$(SettingExtensionFile)" /> </ItemGroup> <!--A target that generates code, which is executed before the compilation--> <Target Name="BeforeCompile" Inputs="@(SettingFiles)" Outputs="$(RootFolder)\$(SettingClass).generated.cs"> <!--Calling our custom task--> <AppSettingStronglyTyped SettingClassName="$(SettingClass)" SettingNamespaceName="$(SettingNamespace)" SettingFiles="@(SettingFiles)"> <Output TaskParameter="ClassNameFile" PropertyName="SettingClassFileName" /> </AppSettingStronglyTyped> <!--Our generated file is included to be compiled--> <ItemGroup> <Compile Remove="$(SettingClassFileName)" /> <Compile Include="$(SettingClassFileName)" /> </ItemGroup> </Target> <!--The generated file is deleted after a general clean. It will force the regeneration on rebuild--> <Target Name="AfterClean"> <Delete Files="$(RootFolder)\$(SettingClass).generated.cs" /> </Target> </Project>
Первым шагом является создание ItemGroup, представляющего текстовые файлы (это может быть несколько) для чтения, что будет одним из параметров нашей задачи. Существуют значения по умолчанию для расположения и расширения, в которых мы осуществляем поиск, но можно переопределить значения, которые определяют свойства в файле проекта MSBuild клиента.
Затем определите два целевых объекта MSBuild . Мы расширяем процесс MSBuild, переопределяя предопределенные цели.
BeforeCompile
. Цель заключается в вызове настраиваемой задачи для создания класса и включения скомпилированного класса. Задачи в этом целевом объекте вставляются до завершения компиляции ядра. Поля ввода и вывода связаны с инкрементальной сборкой . Если все выходные данные имеют дату up-to, MSBuild пропускает цель. Эта добавочная сборка целевого объекта может значительно повысить производительность ваших сборок. Элемент считается up-to-date, если выходной файл имеет тот же возраст или более новый, чем входной файл или файлы.AfterClean
. Цель заключается в удалении созданного файла класса после выполнения общей очистки. Задачи в этом целевом объекте вставляются после вызова основных функций очистки. Он заставляет шаг создания кода повторяться при выполнении целевого объекта перестроения.
Создание пакета NuGet
Чтобы создать пакет NuGet, можно использовать Visual Studio (щелкните правой кнопкой мыши узел проекта в обозревателе решений и выберите пакет). Это также можно сделать с помощью командной строки. Перейдите в папку, в которой присутствует файл проекта задачи AppSettingStronglyTyped.csproj
, и выполните следующую команду:
// -o is to define the output; the following command chooses the current folder.
dotnet pack -o .
Поздравляю! Вы создали пакет NuGet с именем \AppSettingStronglyTyped\AppSettingStronglyTyped\AppSettingStronglyTyped.1.0.0.nupkg.
Пакет имеет расширение .nupkg
и является сжатым ZIP-файлом. Его можно открыть с помощью zip-инструмента. Файлы .target
и .props
находятся в папке build
. Файл .dll
находится в папке lib\netstandard2.0\
. Файл AppSettingStronglyTyped.nuspec
находится на корневом уровне.
(Необязательно) Поддержка многонацелевой поддержки
Рекомендуется поддерживать как Full
(.NET Framework), так и Core
(включая дистрибутивы MSBuild .NET 5 и более поздних версий) для поддержки максимально широкой пользовательской базы.
Для "нормальных" проектов SDK для .NET многозадачность означает установку нескольких TargetFrameworks в файле проекта. Когда вы это сделаете, сборки будут запущены для обоих TargetFrameworkMonikers, а общие результаты можно упаковать в один артефакт.
Это не полная история для MSBuild. MSBuild имеет два основных способа распространения: Visual Studio и .NET SDK. Это очень разные среды выполнения; один запускается в среде выполнения .NET Framework, а другой выполняется в CoreCLR. Это означает, что в то время как ваш код может быть нацелен на netstandard2.0, логика ваших задач может иметь различия в зависимости от того, какая среда выполнения MSBuild используется в данный момент. Фактически, поскольку в .NET 5.0 и более новых версиях имеется множество новых API, имеет смысл как ориентировать исходный код задачи MSBuild на несколько марок целевых платформ, так и ориентировать логику MSBuild на несколько типов среды выполнения MSBuild.
Изменения, необходимые для multitarget
Чтобы нацелиться на несколько TargetFrameworkMonikers (TFM):
Измените файл проекта, чтобы использовать
net472
иnet6.0
TFM (последний может измениться на основе целевого уровня пакета SDK). Вам может понадобиться использоватьnetcoreapp3.1
до тех пор, пока .NET Core 3.1 не выйдет из поддержки. При этом структура папок пакета изменяется сtasks/
наtasks/<TFM>/
.<TargetFrameworks>net472;net6.0</TargetFrameworks>
Обновите файлы
.targets
, чтобы использовать правильный TFM для того, чтобы загружать задачи. Требуемый TFM изменится в зависимости от выбранного выше .NET TFM, но для проекта, ориентированного наnet472
иnet6.0
, у вас будет свойство, такое как:
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472</AppSettingStronglyTyped_TFM>
<AppSettingStronglyTyped_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0</AppSettingStronglyTyped_TFM>
Этот код использует свойство MSBuildRuntimeType
в качестве прокси для активной среды размещения. После установки этого свойства его можно использовать в UsingTask
для загрузки правильного AssemblyFile
:
<UsingTask
AssemblyFile="$(MSBuildThisFileDirectory)../tasks/$(AppSettingStronglyTyped_TFM)/AppSettingStronglyTyped.dll"
TaskName="AppSettingStrongTyped.AppSettingStronglyTyped" />
Дальнейшие действия
Многие задачи включают вызов исполняемого файла. В некоторых сценариях можно использовать задачу Exec, но если ограничения задачи Exec являются проблемой, можно также создать пользовательскую задачу. В следующем руководстве рассматриваются оба варианта с более реалистичным сценарием создания кода: создание пользовательской задачи для создания клиентского кода для REST API.
Кроме того, узнайте, как протестировать настраиваемую задачу.