- PVSM.RU - https://www.pvsm.ru -

.NET Core 2.1 Global Tools

Пару недель назад вышел .NET Core 2.1 RC1 [1]. Это первая версия SDK, где есть фича под названием "Глобальные утилиты .NET Core" (".NET Core Global Tools"). Она дает простой способ создания кросс-платформенных консольных утилит.

.NET Core 2.1 Global Tools - 1

Мы познакомимся с основами использования .NET Core Global Tools и кратко посмотрим, что внутри. А еще вы можете скачать .NET Core 2.1 SDK [2] и попробовать написать собственный пример.

Основы

.NET Core global tool — это специальный пакет NuGet, в котором находится консольное приложение. Когда вы устанавливаете его, .NET Core CLI скачивает пакет и делает его доступным в виде новой глобальной консольной команды.

Пользователи могут устанавливать утилиты с помощью команды dotnet tool install:

dotnet tool install -g <nuget package name>

После установки консольные утилиты, находящиеся в пакете, будут глобально доступны по имени:

<command name>

dotnet tool имеет и другие команды. Например:

dotnet tool list -g
dotnet tool uninstall -g <nuget package name>
dotnet tool update -g <nuget package name>

Под капотом

NuGet пакет с консольной утилитой содержит все файлы, полученные в результате выполнения команды dotnet publish, а также несколько дополнительных файлов с мета-информацией.

Когда вы запускаете dotnet tool install --global, происходит следующее:

  1. Запускается dotnet restore со специальными параметрами [3], чтобы скачать пакет.
  2. Файлы распаковываются в папку $HOME/.dotnet/.store/<package id>/<version>.
  3. Генерируется запускаемый файл в папке $HOME/.dotnet/tools.

Сгенерированный запускаемый файл — это небольшое консольное приложение (написанное на C++ [4]), которое знает, где находится ваш .NET Core DLL файл и автоматически запускает его.

Вы также можете запустить dotnet tool install с аргументом --tool-path $installDir. Эта команда делает всё то же самое, но устанавливает консольное приложение в папку $installDir, а не в $HOME/.dotnet/tools.

Как создать свой пакет

Для создания глобальных консольных утилит вам нужен .NET Core SDK версии 2.1 [2]. В этой версии добавлено несколько дополнительных настроек проектов для управления неймингом и содержимым пакетов с глобальными консольными утилитами.

Минимальные необходимые параметры проекта:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <PackAsTool>true</PackAsTool>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>

</Project>

Дополнительные (не обязательные) параметры, управляющие сборкой пакета:

  • AssemblyName — задает название .dll файла вашего консольного приложения.
  • ToolCommandName — название команды, по которому пользователь будет запускать вашу консольную утилиту. По умолчанию оно совпадает с названием .dll файла (которое задано в параметре AssemblyName).
    • Название команды не обязательно должно начинаться с dotnet-. Можно использовать любое название без пробелов.
    • Если название начинается с dotnet-, то утилиту можно будет запускать как команду утилиты dotnet (убедитесь, что команды с таким именем еще нет). Например, утилита dotnet-say-moo может быть вызвана и как dotnet-say-moo, и как dotnet say-moo.
  • PackageId — идентификатор NuGet пакета. По умолчанию совпадает с названием .csproj файла. Этот идентификатор нужно указывать при установке. При этом он может отличаться от названия команды (ToolCommandName) и названия .dll файла (AssemblyName).
  • PackageVersion — версия NuGet пакета (по умолчанию 1.0.0). Также вместо PackageVersion можно использовать VersionPrefix и VersionSuffix.

Пример использования этих параметров:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <PackAsTool>true</PackAsTool>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <ToolCommandName>pineapple</ToolCommandName>
    <PackageId>dole-cli</PackageId>
    <PackageVersion>1.0.0-alpha-$(BuildNumber)</PackageVersion>
    <AssemblyName>Dole.Cli</AssemblyName>
  </PropertyGroup>
</Project>

Сборка и установка пакета

Сборка пакета происходит как обычно — при помощи команды dotnet pack. SDK увидит, что установлен параметр PackAsTool=true и автоматически сгенерирует нужные дополнительные файлы.

dotnet pack --output ./packages

С помощью параметра --source-feed вы можете установить пакет, который еще не опубликован в репозитории пакетов NuGet. Это может быт ьполезно для проверки, что всё сделано правильно. Также, если версия пакета — не релизная (например, 3.0.0-alpha — содержит что-то, кроме трех чисел), нужно при установке явно указать её.

Например:

dotnet tool install -g my-package-name --version 3.0.0-alpha --source-feed ./packages/

Что внутри пакета

Как я писал выше, команда dotnet pack собирает пакет особым образом, если в файле проекта указан параметр PackAsTool=true.

Зависимости

В пакет попадают не только файлы, полученные через dotnet build, но и все другие зависимости вашего проекта (подключенные сторонние пакеты NuGet). Все файлы, необходимые для работы вашей консольной утилиты, должны быть включены в NuGet пакет. Команда dotnet-tool-install не устанавливает зависимости, указанные в резделе <dependencies> вашего .nuspec файла.

DotnetToolSettings.xml

Генерируется специальный файл DotnetToolSettings.xml, который содержит информацию о вашем консольном приложении. Если этого файла по какой-то причине не окажется в пакете (например, пытаетесь установить произвольный пакет как консольную утилиту), то при установке вы получите ошибку:

The settings file in the tool’s NuGet package is invalid: Settings file ‘DotnetToolSettings.xml’ was not found in the package.

Пример содержимого файла:

<DotNetCliTool Version="1">
  <Commands>
    <Command Name="my-command-name" EntryPoint="my-file.dll" Runner="dotnet" />
  </Commands>
</DotNetCliTool>

Сейчас есть следующие требования к файлу DotnetToolSettings.xml:

  • Файл DotnetToolSettings.xml должен находиться в папке tools/$targetframework/any/. Например: tools/netcoreapp2.1/any/DotnetToolSettings.xml.
  • Пакет должен содержать только один файл DotnetToolSettings.xml.
  • В файле DotnetToolSettings.xml должна быть описана только одна секция <Command>.
  • Значение атрибута Runner должно быть "dotnet".
  • В качестве значения атрибута EntryPoint должно быть указано название .dll файла, который лежит в одной папке с файлом DotnetToolSettings.xml.

<packageType name="DotnetTool" />

В .nuspec файл автоматически добавляется параметр <packageType name="DotnetTool" />. Например:

<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
  <metadata>
    <!-- Следующий фрагмент обязательно должен быть! -->
    <packageTypes>
      <packageType name="DotnetTool" />
    </packageTypes>
    <!-- ... -->
  </metadata>
</package>

Если параметр packageType[name] не указан как DotnetTool, то при установке получите ошибку:

error NU1212: Invalid project-package combination for awesome-tool 1.0.0. DotnetToolReference project style can only contain references of the DotnetTool type

Конечно, это не очень понятное сообщение об ошибке. На Github есть issue [5], чтобы это улучить.

Что может пойти не так

Глобальные утилиты — глобальные для пользователя, а не для компьютера

.NET Core CLI по умолчанию устанавливает глобальные утилиты в папку $HOME/.dotnet/tools (на Linux/macOS) или в папку %USERPROFILE%.dotnettools (на Windows). Это значит, что вы не можете установить пакет глобально для всех пользователей компьютера с помощью dotnet tool install --global. Установленные утилиты доступы только для пользователя, который их установил.

Нехватает пути в переменной PATH

Обычно .NET Core автоматически добавляет путь к папке с установленными утилитами в переменную окружения PATH, чтобы они были доступны по имени, без указания полного пути. Но иногда это может не сработать. Например:

  • если вы добавили переменную окружения DOTNET_SKIP_FIRST_TIME_EXPERIENCE (например, чтобы ускорить первый запуск .NET Core), то значение переменной PATH может быть не установлено при первом использовании
  • macOS: если вы установили CLI из .tar.gz файла (а не из .pkg файла), то у вас может не быть файла /etc/paths.d/dotnet-cli-tool который настраивает переменную PATH.
  • Linux: вам нужно вручную отредактировать свой shell environment file, т.е ~/.bash_profile или ~/.zshrc

В этом случае при запуске своей консолььной утилиты вы получите ошибку. Например:

bash: my-command-name: command not found

Чтобы всё заработало, нужно добавить в переменную PATH путь к папке с утилитами. Например так (после добавления нужно перезапустить терминал):

cat << EOF >> ~/.bash_profile
# Add .NET Core SDK tools
export PATH="$PATH:/Users/<user-name>/.dotnet/tools"
EOF

Или вот так можно добавить для текущей сессии:

export PATH="$PATH:/Users/<user-name>/.dotnet/tools"

Примеры выше — для MacOS. Для других систем всё аналогично. Кроме того, при установке вашей глобальной консольной утилиты команда dotnet tool install проверит, что переменная PATH правильно настроена и предложит варианты решения, если это не так.

.NET Core CLI установлен не в папку по умолчанию

Если вы скачали .NET Core CLI как .zip/.tar.gz архив и распаковали его в папку, которая отличается от папки по умолчанию, то при запуске своей консольной утилиты вы можете получить ошибку:

  • Windows: A fatal error occurred, the required library hostfxr.dll could not be found
  • Linux: A fatal error occurred, the required library libhostfxr.so could not be found
  • macOS: A fatal error occurred, the required library libhostfxr.dylib could not be found

В сообщении об ошибке будет также дополнительная информация:

If this is a self-contained application, that library should exist in [some path here].
If this is a framework-dependent application, install the runtime in the default location [default location] or use the DOTNET_ROOT environment variable to specify the runtime location.

Причина в том, что запускаемы файл, который генерируется командой dotnet tool install при установке пакета, ищет .NET Core в папке по умолчанию. Вы можете переопределить пути по умолчанию, установив переменную окружения DOTNET_ROOT. Например:

# Windows
set DOTNET_ROOT=C:Usersusernamedotnet

# MacOS/Linux
export DOTNET_ROOT=/Users/username/Downloads/dotnet

Подробности в issue [6] на GitHub.

Заключение

Мы познакомились с глобальными консольными утилитами в .NET Core. На мой взгляд, это очень крутая штука. Я крайне счастлив, что команда .NET Core её запилила. Не могу дождаться, когда все начнут её использовать :)

Автор: Дмитрий Андриянов

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/net/280636

Ссылки в тексте:

[1] вышел .NET Core 2.1 RC1: https://blogs.msdn.microsoft.com/dotnet/2018/05/07/announcing-net-core-2-1-rc-1/

[2] скачать .NET Core 2.1 SDK: https://aka.ms/DotNetCore21

[3] специальными параметрами: https://github.com/dotnet/cli/pull/8414

[4] написанное на C++: https://github.com/dotnet/core-setup/tree/release/2.1/src/corehost/cli/exe/apphost

[5] issue: https://github.com/NuGet/Home/issues/6630

[6] issue: https://github.com/dotnet/cli/issues/9114

[7] Источник: https://habr.com/post/359006/?utm_source=habrahabr&utm_medium=rss&utm_campaign=359006