Модель расширяемости средств интерфейса командной строки .NET Core.NET Core CLI tools extensibility model

В этом документе описаны различные способы расширения средств интерфейса командной строки .NET Core и сценарии, используемые в каждом из способов.This document covers the different ways you can extend the .NET Core Command-line Interface (CLI) tools and explain the scenarios that drive each one of them. Вы увидите, как пользоваться средствами и создавать различные типы средств.You'll see how to consume the tools as well as how to build the different types of tools.

Расширение средств CLIHow to extend CLI tools

Средства CLI можно расширять тремя основными способами:The CLI tools can be extended in three main ways:

  1. С помощью пакетов NuGet для каждого отдельного проектаVia NuGet packages on a per-project basis

    Средства для отдельных проектов включены в контекст проекта, но их легко установить с помощью восстановления.Per-project tools are contained within the project's context, but they allow easy installation through restoration.

  2. С помощью пакетов NuGet и пользовательских целевых объектовVia NuGet packages with custom targets

    Пользовательские целевые объекты позволяют с легкостью расширить процедуру сборки с помощью настраиваемых задач.Custom targets allow you to easily extend the build process with custom tasks.

  3. С помощью системной переменной PATHVia the system's PATH

    Подход на основе пути подходит для общих межпроектных средств, которые могут использоваться на одном компьютере.PATH-based tools are good for general, cross-project tools that are usable on a single machine.

Три указанных выше механизма расширяемости не являются взаимоисключающими.The three extensibility mechanisms outlined above are not exclusive. Вы можете использовать один механизм, все механизмы или любое их сочетание.You can use one, or all, or a combination of them. Выбор механизма зависит в первую очередь от цели, которой вы стремитесь достичь за счет расширения.Which one to pick depends largely on the goal you are trying to achieve with your extension.

Расширяемость на основе отдельных проектовPer-project based extensibility

Средства для отдельных проектов — это развертывания, зависимые от платформы, которые распространяются как пакеты NuGet.Per-project tools are framework-dependent deployments that are distributed as NuGet packages. Средства доступны только в контексте проекта, который ссылается на них и для которого они восстанавливаются.Tools are only available in the context of the project that references them and for which they are restored. Вызов вне контекста проекта (например, вне каталога, содержащего проект) завершится с ошибкой из-за того, что не удается найти команду.Invocation outside of the context of the project (for example, outside of the directory that contains the project) will fail because the command cannot be found.

Эти средства идеально подходят для создания серверов, так как для них не требуется ничего, кроме файла проекта.These tools are perfect for build servers, since nothing outside of the project file is needed. Процесс сборки выполняет восстановление для соответствующего проекта, и средства становятся доступны.The build process runs restore for the project it builds and tools will be available. К этой категории также относятся проекты на таких языках, как F#, так как каждый проект может быть написан только на одном языке.Language projects, such as F#, are also in this category since each project can only be written in one specific language.

Наконец, эта модель расширяемости обеспечивает поддержку создания средств, которым требуется доступ к выходным данным сборки проекта.Finally, this extensibility model provides support for creation of tools that need access to the built output of the project. Например, различные средства просмотра Razor в приложениях MVC ASP.NET попадают в эту категорию.For instance, various Razor view tools in ASP.NET MVC applications fall into this category.

Использование средств для отдельных проектовConsuming per-project tools

Для каждого средства, который нужно использовать, необходимо добавить элемент <DotNetCliToolReference> в файл проекта.Consuming these tools requires you to add a <DotNetCliToolReference> element to your project file for each tool you want to use. В элементе <DotNetCliToolReference> нужно сослаться на пакет, в котором находится средство, и указать необходимую версию.Inside the <DotNetCliToolReference> element, you reference the package in which the tool resides and specify the version you need. После выполнения команды dotnet restore средство и его зависимости восстанавливаются.After running dotnet restore, the tool and its dependencies are restored.

Примечание

Начиная с пакета SDK для .NET Core 2.0 нет необходимости выполнять команду dotnet restore, так как она выполняется неявно всеми командами, которые требуют восстановления, например dotnet new, dotnet build и dotnet run.Starting with .NET Core 2.0 SDK, you don't have to run dotnet restore because it's run implicitly by all commands that require a restore to occur, such as dotnet new, dotnet build and dotnet run. Эту команду по-прежнему можно использовать в некоторых сценариях, где необходимо явное восстановление, например в сборках с использованием непрерывной интеграции в Azure DevOps Services или системах сборки, где требуется явно контролировать время восстановления.It's still a valid command in certain scenarios where doing an explicit restore makes sense, such as continuous integration builds in Azure DevOps Services or in build systems that need to explicitly control the time at which the restore occurs.

Для средств, выполнение которых требует загрузки выходных данных сборки проекта, обычно имеется еще одна зависимость, которая указывается в списке стандартных зависимостей в файле проекта.For tools that need to load the build output of the project for execution, there is usually another dependency which is listed under the regular dependencies in the project file. Так как интерфейс командной строки использует MSBuild в качестве подсистемы сборки, рекомендуется записать эти части средства в виде пользовательских целевых объектов и задач MSBuild: так они смогут участвовать в общем процессе сборки.Since CLI uses MSBuild as its build engine, we recommend that these parts of the tool be written as custom MSBuild targets and tasks, since they can then take part in the overall build process. Кроме того, они легко могут получить любые данные, приобретенные в результате сборки, например расположение выходных файлов, текущую создаваемую конфигурацию и т. д. Все эти данные превращаются в набор свойств MSBuild, которые могут быть считаны из любого целевого объекта.Also, they can get any and all data easily that is produced via the build, such as the location of the output files, the current configuration being built, etc. All this information becomes a set of MSBuild properties that can be read from any target. Далее в этом документе вы узнаете, как добавить пользовательский целевой объект с помощью NuGet.You can see how to add a custom target using NuGet later in this document.

Рассмотрим пример добавления простого средства на основе узла tools в простой проект.Let's review an example of adding a simple tools-only tool to a simple project. Предположим, что есть команда dotnet-api-search, которая позволяет искать указанный интерфейс API в пакетах NuGet. Вот файл проекта консольного приложения, которое использует это средство:Given an example command called dotnet-api-search that allows you to search through the NuGet packages for the specified API, here is a console application's project file that uses that tool:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

  <!-- The tools reference -->
  <ItemGroup>
    <DotNetCliToolReference Include="dotnet-api-search" Version="1.0.0" />
  </ItemGroup>
</Project>

Элемент <DotNetCliToolReference> имеет структуру, подобную структуре элемента <PackageReference>.The <DotNetCliToolReference> element is structured in a similar way as the <PackageReference> element. Для восстановления потребуются идентификатор пакета, содержащего средство, и его версия.It needs the package ID of the package containing the tool and its version to be able to restore.

Создание средствBuilding tools

Как было сказано ранее, средства — это просто переносимые консольные приложения.As mentioned, tools are just portable console applications. Сборка средств выполняется так же, как и сборка других консольных приложений.You build tools as you would build any other console application. После сборки используйте команду dotnet pack, чтобы создать пакет NuGet (NUPKG-файл), содержащий код, сведения о зависимостях и т. д.After you build it, you use the dotnet pack command to create a NuGet package (.nupkg file) that contains your code, information about its dependencies, and so on. Имя пакета может быть любым, но содержащееся в нем приложение, то есть двоичный файл средства, должно соответствовать соглашению dotnet-<command>, чтобы среда dotnet могла вызывать его.You can give any name to the package, but the application inside, the actual tool binary, has to conform to the convention of dotnet-<command> in order for dotnet to be able to invoke it.

Примечание

В версиях-кандидатах до 3 средств командной строки .NET Core при работе команды dotnet pack возникала ошибка, из-за которой файл runtime.config.json не упаковывался вместе со средством.In pre-RC3 versions of the .NET Core command-line tools, the dotnet pack command had a bug that caused the runtime.config.json to not be packed with the tool. Из-за отсутствия файла происходили ошибки в среде выполнения.Lacking that file results in errors at runtime. При возникновении такой ситуации установите последнюю версию средства и повторите dotnet pack.If you encounter this behavior, be sure to update to the latest tooling and try the dotnet pack again.

Так как средства — это переносимые приложения, то для запуска средства у пользователя должна быть установлена та версия библиотек .NET Core, которая использовалась для сборки средства.Since tools are portable applications, the user consuming the tool must have the version of the .NET Core libraries that the tool was built against in order to run the tool. Зависимости, которые использует средство и которые не содержатся в библиотеках .NET Core, восстанавливаются и помещаются в кэш NuGet.Any other dependency that the tool uses and that is not contained within the .NET Core libraries is restored and placed in the NuGet cache. Таким образом, средство в целом выполняется с помощью сборок из библиотек .NET Core, а также сборок из кэша NuGet.The entire tool is, therefore, run using the assemblies from the .NET Core libraries as well as assemblies from the NuGet cache.

Подобные средства имеют схему зависимостей, которая никак не связана со схемой зависимостей проекта, использующего эти средства.These kinds of tools have a dependency graph that is completely separate from the dependency graph of the project that uses them. В ходе восстановления сначала восстанавливаются зависимости проекта, а затем каждое из средств и их зависимости.The restore process first restores the project's dependencies and then restores each of the tools and their dependencies.

Вы можете найти более подробные примеры и различные сочетания в репозитории CLI .NET Core.You can find richer examples and different combinations of this in the .NET Core CLI repo. Вы также можете найти реализацию используемых средств в том же репозитории.You can also see the implementation of tools used in the same repo.

Пользовательские целевые объектыCustom targets

NuGet позволяет упаковывать пользовательские целевые объекты MSBuild и файлы свойств.NuGet has the capability to package custom MSBuild targets and props files. В связи с переходом средств CLI в .NET Core на MSBuild к проектам .NET Core теперь применяется тот же механизм расширяемости.With the move of the .NET Core CLI tools to use MSBuild, the same mechanism of extensibility now applies to .NET Core projects. Этот тип расширяемости можно использовать для расширения процедуры сборки, для получения доступа к любым артефактам в процессе сборки (например, к созданным файлам), для проверки конфигурации, с помощью которой вызывается сборка, и т. д.You would use this type of extensibility when you want to extend the build process, or when you want to access any of the artifacts in the build process, such as generated files, or you want to inspect the configuration under which the build is invoked, etc.

В следующем примере показан целевой файл проекта, в котором используется синтаксис csproj.In the following example, you can see the target's project file using the csproj syntax. С помощью этого синтаксиса определяются объекты, которые будут упакованы командой dotnet pack: целевые файлы, а также сборки помещаются в каталог build внутри пакета.This instructs the dotnet pack command what to package, placing the targets files as well as the assemblies into the build folder inside the package. Обратите внимание, что свойство Label для элемента <ItemGroup> установлено в значение dotnet pack instructions, и под ним определен целевой объект.Notice the <ItemGroup> element that has the Label property set to dotnet pack instructions, and the Target defined beneath it.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <Description>Sample Packer</Description>
    <VersionPrefix>0.1.0-preview</VersionPrefix>
    <TargetFramework>netstandard1.3</TargetFramework>
    <DebugType>portable</DebugType>
    <AssemblyName>SampleTargets.PackerTarget</AssemblyName>
  </PropertyGroup>
  <ItemGroup>
    <EmbeddedResource Include="Resources\Pkg\dist-template.xml;compiler\resources\**\*" Exclude="bin\**;obj\**;**\*.xproj;packages\**" />
    <None Include="build\SampleTargets.PackerTarget.targets" />
  </ItemGroup>
  <ItemGroup Label="dotnet pack instructions">
    <Content Include="build\*.targets">
      <Pack>true</Pack>
      <PackagePath>build\</PackagePath>
    </Content>
  </ItemGroup>
  <Target Name="CollectRuntimeOutputs" BeforeTargets="_GetPackageFiles">
    <!-- Collect these items inside a target that runs after build but before packaging. -->
    <ItemGroup>
      <Content Include="$(OutputPath)\*.dll;$(OutputPath)\*.json">
        <Pack>true</Pack>
        <PackagePath>build\</PackagePath>
      </Content>
    </ItemGroup>
  </Target>
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="1.0.1-beta-000933"/>
    <PackageReference Include="Microsoft.Build.Framework" Version="0.1.0-preview-00028-160627" />
    <PackageReference Include="Microsoft.Build.Utilities.Core" Version="0.1.0-preview-00028-160627" />
    <PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
  </ItemGroup>
  <ItemGroup />
  <PropertyGroup Label="Globals">
    <ProjectGuid>463c66f0-921d-4d34-8bde-7c9d0bffaf7b</ProjectGuid>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
    <DefineConstants>$(DefineConstants);NETSTANDARD1_3</DefineConstants>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
    <DefineConstants>$(DefineConstants);RELEASE</DefineConstants>
  </PropertyGroup>
</Project>

Для использования пользовательских целевых объектов задается элемент <PackageReference>, указывающий на пакет и его версию внутри проекта, который расширяется.Consuming custom targets is done by providing a <PackageReference> that points to the package and its version inside the project that is being extended. В отличие от средств пакет пользовательских целевых объектов входит в замыкание зависимостей исходного проекта.Unlike the tools, the custom targets package does get included into the consuming project's dependency closure.

Использование пользовательского целевого объекта зависит только от способа настройки.Using the custom target depends solely on how you configure it. Так как это целевой объект MSBuild, он может зависеть от заданного целевого объекта, запускаться после другого целевого объекта, а также быть вызван вручную с помощью команды dotnet msbuild -t:<target-name>.Since it's an MSBuild target, it can depend on a given target, run after another target and can also be manually invoked using the dotnet msbuild -t:<target-name> command.

Однако для удобства пользователей можно объединить средства для отдельных проектов и пользовательские целевые объекты.However, if you want to provide a better user experience to your users, you can combine per-project tools and custom targets. В этом случае средство для отдельного проекта будет принимать все необходимые параметры и преобразовывать их в необходимый вызов dotnet msbuild для целевого объекта.In this scenario, the per-project tool would essentially just accept whatever needed parameters and would translate that into the required dotnet msbuild invocation that would execute the target. Пример подобного типа синергии можно увидеть в репозитории примеров хакатона MVP Summit 2016 в проекте dotnet-packer.You can see a sample of this kind of synergy on the MVP Summit 2016 Hackathon samples repo in the dotnet-packer project.

Расширяемость на основе путиPATH-based extensibility

Расширяемость на основе пути обычно используется на компьютерах разработки, на которых требуется средство, которое охватывает более одного проекта.PATH-based extensibility is usually used for development machines where you need a tool that conceptually covers more than a single project. Основным недостатком такого механизма расширения является его привязка к компьютеру, на котором размещается средство.The main drawback of this extension mechanism is that it's tied to the machine where the tool exists. Если средство требуется на другом компьютере, его необходимо развернуть.If you need it on another machine, you would have to deploy it.

Такая схема расширяемости набора средств CLI очень проста.This pattern of CLI toolset extensibility is very simple. Как указано в обзоре CLI .NET Core, драйвер dotnet может выполнять любую команду, имя которой соответствует соглашению dotnet-<command>.As covered in the .NET Core CLI overview, dotnet driver can run any command that is named after the dotnet-<command> convention. Логика разрешения по умолчанию сначала проверяет несколько расположений и в конечном итоге переключается на системный путь.The default resolution logic first probes several locations and finally falls back to the system PATH. Если запрошенная команда существует по системному пути и является двоичным файлом, который можно вызвать, драйвер dotnet вызовет ее.If the requested command exists in the system PATH and is a binary that can be invoked, dotnet driver will invoke it.

Файл должен быть исполняемым.The file must be executable. В системах Unix это означает любой файл с битом выполнения, заданным посредством chmod +x.On Unix systems, this means anything that has the execute bit set via chmod +x. В Windows можно использовать файлы cmd.On Windows, you can use cmd files.

В качестве примера рассмотрим очень простую реализацию программы "Hello World".Let's take a look at the very simple implementation of a "Hello World" tool. В Windows мы будем использовать как bash, так и cmd.We will use both bash and cmd on Windows. Следующая команда просто выводит текст "Hello World" в консоль.The following command will simply echo "Hello World" to the console.

#!/bin/bash

echo "Hello World!"
echo "Hello World"

В Mac OS можно сохранить этот скрипт как dotnet-hello и задать его бит выполнения с помощью chmod +x dotnet-hello.On macOS, we can save this script as dotnet-hello and set its executable bit with chmod +x dotnet-hello. Затем можно создать символьную ссылку на него в /usr/local/bin с помощью команды ln -s <full_path>/dotnet-hello /usr/local/bin/.We can then create a symbolic link to it in /usr/local/bin using the command ln -s <full_path>/dotnet-hello /usr/local/bin/. Это позволит вызывать команду с использованием синтаксиса dotnet hello.This will make it possible to invoke the command using the dotnet hello syntax.

В Windows можно сохранить этот сценарий как dotnet-hello.cmd и поместить его в расположение, которое находится в системном пути (или добавить в папку, которая уже находится в системном пути).On Windows, we can save this script as dotnet-hello.cmd and put it in a location that is in a system path (or you can add it to a folder that is already in the path). После этого для запуска этого примера достаточно использовать команду dotnet hello.After this, you can just use dotnet hello to run this example.