Пару недель назад вышел .NET Core 2.1 RC1. Это первая версия SDK, где есть фича под названием "Глобальные утилиты .NET Core" (".NET Core Global Tools"). Она дает простой способ создания кросс-платформенных консольных утилит.
Мы познакомимся с основами использования .NET Core Global Tools и кратко посмотрим, что внутри. А еще вы можете скачать .NET Core 2.1 SDK и попробовать написать собственный пример.
Основы
.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
, происходит следующее:
- Запускается
dotnet restore
со специальными параметрами, чтобы скачать пакет. - Файлы распаковываются в папку
$HOME/.dotnet/.store/<package id>/<version>
. - Генерируется запускаемый файл в папке
$HOME/.dotnet/tools
.
Сгенерированный запускаемый файл — это небольшое консольное приложение (написанное на C++), которое знает, где находится ваш .NET Core DLL файл и автоматически запускает его.
Вы также можете запустить dotnet tool install
с аргументом --tool-path $installDir
. Эта команда делает всё то же самое, но устанавливает консольное приложение в папку $installDir
, а не в $HOME/.dotnet/tools
.
Как создать свой пакет
Для создания глобальных консольных утилит вам нужен .NET Core SDK версии 2.1. В этой версии добавлено несколько дополнительных настроек проектов для управления неймингом и содержимым пакетов с глобальными консольными утилитами.
Минимальные необходимые параметры проекта:
<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, чтобы это улучить.
Что может пойти не так
Глобальные утилиты — глобальные для пользователя, а не для компьютера
.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 на GitHub.
Заключение
Мы познакомились с глобальными консольными утилитами в .NET Core. На мой взгляд, это очень крутая штука. Я крайне счастлив, что команда .NET Core её запилила. Не могу дождаться, когда все начнут её использовать :)
Автор: Дмитрий Андриянов