Modelo de extensibilidad de las herramientas de la CLI de .NET Core.NET Core CLI tools extensibility model

En este documento se tratan las maneras diferentes en las que puede ampliar las herramientas de la interfaz de la línea de comandos (CLI) de .NET Core y se explican los escenarios que impulsan cada una de ellas.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. Verá cómo usar las herramientas así como la manera de crear los diferentes tipos de herramientas.You'll see how to consume the tools as well as how to build the different types of tools.

Cómo extender las herramientas de la CLIHow to extend CLI tools

Las herramientas de la CLI pueden extenderse de tres maneras principales:The CLI tools can be extended in three main ways:

  1. A través de paquetes NuGet por proyectoVia NuGet packages on a per-project basis

    Las herramientas por proyecto se incluyen en el contexto del proyecto, pero permiten la instalación fácil mediante la restauración.Per-project tools are contained within the project's context, but they allow easy installation through restoration.

  2. A través de paquetes NuGet con destinos personalizadosVia NuGet packages with custom targets

    Los destinos personalizados permiten extender fácilmente el proceso de compilación con tareas personalizadas.Custom targets allow you to easily extend the build process with custom tasks.

  3. A través de la RUTA DE ACCESO del sistemaVia the system's PATH

    Las herramientas basadas en la RUTA DE ACCESO son buenas para herramientas entre proyectos generales que se pueden usar en una sola máquina.PATH-based tools are good for general, cross-project tools that are usable on a single machine.

Los tres mecanismos de extensibilidad descritos anteriormente no son exclusivos.The three extensibility mechanisms outlined above are not exclusive. Puede usar uno, o todos, o una combinación de ellos.You can use one, or all, or a combination of them. La selección de uno u otro depende en gran medida del objetivo que intenta alcanzar con su extensión.Which one to pick depends largely on the goal you are trying to achieve with your extension.

Extensibilidad por proyectoPer-project based extensibility

Las herramientas por proyecto son implementaciones dependientes del marco que se distribuyen como paquetes NuGet.Per-project tools are framework-dependent deployments that are distributed as NuGet packages. Las herramientas solo están disponibles en el contexto del proyecto que les hace referencia y para el que se restauran.Tools are only available in the context of the project that references them and for which they are restored. La invocación fuera del contexto del proyecto (por ejemplo, fuera del directorio que contiene el proyecto) producirá un error porque el comando no puede encontrarse.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.

Estas herramientas son perfectas para servidores de compilación, dado que no se necesita nada fuera del archivo de proyecto.These tools are perfect for build servers, since nothing outside of the project file is needed. El proceso de compilación ejecuta la restauración para el proyecto que se compila y hay herramientas disponibles.The build process runs restore for the project it builds and tools will be available. Los proyectos de lenguajes, como F#, también están en esta categoría ya que cada proyecto solo se puede escribir en un lenguaje específico.Language projects, such as F#, are also in this category since each project can only be written in one specific language.

Finalmente, este modelo de extensibilidad proporciona compatibilidad con la creación de herramientas que necesitan acceso a la salida compilada del proyecto.Finally, this extensibility model provides support for creation of tools that need access to the built output of the project. Por ejemplo, varias herramientas de vista de Razor de aplicaciones ASP.NET MVC se incluyen dentro de esta categoría.For instance, various Razor view tools in ASP.NET MVC applications fall into this category.

Consumo de herramientas por proyectoConsuming per-project tools

Para consumir estas herramientas es necesario agregar un elemento <DotNetCliToolReference> a su archivo del proyecto para cada herramienta que quiera usar.Consuming these tools requires you to add a <DotNetCliToolReference> element to your project file for each tool you want to use. Dentro del elemento <DotNetCliToolReference>, se hace referencia al paquete en el que reside la herramienta y se especifica la versión necesaria.Inside the <DotNetCliToolReference> element, you reference the package in which the tool resides and specify the version you need. Después de ejecutar dotnet restore, se restauran la herramienta y sus dependencias.After running dotnet restore, the tool and its dependencies are restored.

Nota

A partir del SDK de .NET Core 2.0, no es necesario ejecutar dotnet restore porque lo ejecutan implícitamente todos los comandos que requieren que se produzca una restauración, como dotnet new, dotnet build y 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. Sigue siendo un comando válido en algunos escenarios donde tiene sentido realizar una restauración explícita, como las compilaciones de integración continua en Azure DevOps Services o en los sistemas de compilación que necesitan controlar explícitamente la hora a la que se produce la restauración.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.

Para las herramientas que necesitan cargar la salida de compilación del proyecto para su ejecución, hay normalmente otra dependencia que aparece en las dependencias normales del archivo de proyecto.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. Como la CLI usa MSBuild como motor de compilación, recomendamos que estas partes de la herramienta se escriban como destinos y tareas de MSBuild personalizados, ya que pueden formar parte del proceso de compilación general.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. Además, pueden obtener fácilmente todos y cada uno de los datos que se producen mediante la compilación, como la ubicación de los archivos de salida, la configuración actual que se compila, etc. Toda esta información se convierte en un conjunto de propiedades de MSBuild que se puede leer desde cualquier destino.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. Más adelante en este documento puede ver cómo agregar un destino personalizado mediante NuGet.You can see how to add a custom target using NuGet later in this document.

Vamos a ver un ejemplo de cómo agregar una herramienta sencilla tools-only a un proyecto sencillo.Let's review an example of adding a simple tools-only tool to a simple project. Dado un comando de ejemplo llamado dotnet-api-search que le permite examinar los paquetes de NuGet hasta encontrar la API especificada, este es un archivo de proyecto de la aplicación de consola que usa esa herramienta: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>

El elemento <DotNetCliToolReference> está estructurado de forma similar al elemento <PackageReference>.The <DotNetCliToolReference> element is structured in a similar way as the <PackageReference> element. Necesita el identificador del paquete que contiene la herramienta y su versión para que pueda realizarse la restauración.It needs the package ID of the package containing the tool and its version to be able to restore.

Compilación de herramientasBuilding tools

Como se ha mencionado, las herramientas son simples aplicaciones de consola portátiles.As mentioned, tools are just portable console applications. Compila herramientas como lo haría en cualquier otra aplicación de consola.You build tools as you would build any other console application. Después de compilarla, puede usar el comando dotnet pack para crear un paquete de NuGet (archivo .nupkg) que contiene el código e información sobre sus dependencias, entre otros.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. Puede proporcionar cualquier nombre al paquete, pero la aplicación que contiene, el archivo binario de la herramienta real, debe respetar las convenciones de dotnet-<command> para que dotnet pueda invocarlo.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.

Nota

En las versiones anteriores a RC3 de las herramientas de línea de comandos de .NET Core, el comando dotnet pack tenía un error que provocaba que .runtimeconfig.json no se empaquetase con la herramienta.In pre-RC3 versions of the .NET Core command-line tools, the dotnet pack command had a bug that caused the .runtimeconfig.json to not be packed with the tool. La falta de dicho archivo provoca errores en tiempo de ejecución.Lacking that file results in errors at runtime. Si se produce este comportamiento, asegúrese de actualizar a las últimas herramientas y pruebe dotnet pack nuevo.If you encounter this behavior, be sure to update to the latest tooling and try the dotnet pack again.

Como las herramientas son aplicaciones portátiles, el usuario que las consume debe tener la versión de las bibliotecas .NET Core con la que se ha compilado la herramienta para poder ejecutar esta.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. Cualquier otra dependencia que use la herramienta y que no esté contenida en las bibliotecas .NET Core se restauran y colocan en la caché de 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. Por lo tanto, la herramienta entera se ejecuta con los ensamblados de las bibliotecas .NET Core, así como los ensamblados de la caché de NuGet.The entire tool is, therefore, run using the assemblies from the .NET Core libraries as well as assemblies from the NuGet cache.

Estas clases de herramientas tienen un gráfico de dependencias que es completamente independiente del gráfico de dependencias del proyecto que los usa.These kinds of tools have a dependency graph that is completely separate from the dependency graph of the project that uses them. El proceso de restauración restaura primero las dependencias del proyecto y, después, cada una de las herramientas y sus dependencias.The restore process first restores the project's dependencies and then restores each of the tools and their dependencies.

Puede encontrar más ejemplos y diferentes combinaciones de esto en el repositorio de la CLI de .NET Core.You can find richer examples and different combinations of this in the .NET Core CLI repo. También puede ver las herramientas de implementación usadas en el mismo repositorio.You can also see the implementation of tools used in the same repo.

Destinos personalizadosCustom targets

NuGet tiene la capacidad de empaquetar archivos de propiedades y destinos de MSBuild personalizados.NuGet has the capability to package custom MSBuild targets and props files. Con el paso de las herramientas de la CLI de .NET Core para usar MSBuild, el mismo mecanismo de extensibilidad se aplica ahora en proyectos de .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. Este tipo de extensibilidad se usaría cuando quisiera extender el proceso de compilación o quisiera acceder a alguno de los artefactos de dicho proceso, como los archivos generados, o si quiere inspeccionar la configuración bajo la que se invoca la compilación, etc.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.

En el ejemplo siguiente, puede ver el archivo del proyecto de destino con la sintaxis csproj.In the following example, you can see the target's project file using the csproj syntax. Esto indica al comando dotnet pack qué empaquetar, colocando los archivos de destinos así como los ensamblados en la carpeta build dentro del paquete.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. Observe el elemento <ItemGroup> que tiene la propiedad Label establecida en dotnet pack instructions, y el destino que se define por debajo.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>

Para consumir destinos personalizados se proporciona un elemento <PackageReference> que apunta al paquete y su versión dentro del proyecto que se extiende.Consuming custom targets is done by providing a <PackageReference> that points to the package and its version inside the project that is being extended. A diferencia de las herramientas, el paquete de destinos personalizados se incluye en el cierre de dependencia del proyecto de consumo.Unlike the tools, the custom targets package does get included into the consuming project's dependency closure.

El uso del destino personalizado depende exclusivamente de cómo se configure.Using the custom target depends solely on how you configure it. Como es un destino de MSBuild, puede depender de un destino dado, ejecutarse después de otro destino e invocarse también manualmente mediante el comando 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.

En cambio, si quiere proporcionar una mejor experiencia de usuario, puede combinar las herramientas por proyecto y los destinos personalizados.However, if you want to provide a better user experience to your users, you can combine per-project tools and custom targets. En este escenario, la herramienta por proyecto básicamente solo aceptaría todos los parámetros necesarios y lo traduciría en la invocación de dotnet msbuild necesaria que ejecutaría el destino.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. Puede ver una muestra de esta clase de sinergia en el repositorio de ejemplos de MVP Summit 2016 Hackathon del proyecto 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.

Extensibilidad basada en la RUTA DE ACCESOPATH-based extensibility

La extensibilidad basada en la RUTA DE ACCESO se suele usar con equipos de desarrollo, donde necesita una herramienta que abarque conceptualmente más de un único proyecto.PATH-based extensibility is usually used for development machines where you need a tool that conceptually covers more than a single project. La principal desventaja de este mecanismo de extensión es que está vinculado a la máquina donde existe la herramienta.The main drawback of this extension mechanism is that it's tied to the machine where the tool exists. Si lo necesita en otro equipo, tendría que implementarlo.If you need it on another machine, you would have to deploy it.

Este patrón de extensibilidad del conjunto de herramientas de la CLI es muy sencillo.This pattern of CLI toolset extensibility is very simple. Como se explica en la información general de la CLI de .NET Core, el controlador dotnet puede ejecutar cualquier comando que se nombre según la convención dotnet-<command>.As covered in the .NET Core CLI overview, dotnet driver can run any command that is named after the dotnet-<command> convention. La lógica de resolución predeterminada sondea primero varias ubicaciones y finalmente vuelve a la RUTA DE ACCESO del sistema.The default resolution logic first probes several locations and finally falls back to the system PATH. Si el comando solicitado existe en la RUTA DE ACCESO del sistema y es un archivo binario que se puede invocar, el controlador dotnet lo invoca.If the requested command exists in the system PATH and is a binary that can be invoked, dotnet driver will invoke it.

El archivo debe ser ejecutable.The file must be executable. En sistemas Unix, esto significa todo lo que tiene el bit de ejecución establecido mediante chmod +x.On Unix systems, this means anything that has the execute bit set via chmod +x. En Windows, puede usar archivos cmd.On Windows, you can use cmd files.

Echemos un vistazo a una implementación muy sencilla de una herramienta "Hola mundo".Let's take a look at the very simple implementation of a "Hello World" tool. Usaremos bash y cmd en Windows.We will use both bash and cmd on Windows. En el siguiente comando simplemente reflejaremos "Hola mundo" en la consola.The following command will simply echo "Hello World" to the console.

#!/bin/bash

echo "Hello World!"
echo "Hello World"

En macOS, podemos guardar este script como dotnet-hello y establecer su bit ejecutable con chmod +x dotnet-hello.On macOS, we can save this script as dotnet-hello and set its executable bit with chmod +x dotnet-hello. Luego, podemos crear un vínculo simbólico a él en /usr/local/bin con el comando 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/. De esta manera se podrá invocar el comando mediante la sintaxis dotnet hello.This will make it possible to invoke the command using the dotnet hello syntax.

En Windows, podemos guardar este script como dotnet-hello.cmd y colocarlo en una ubicación que esté en una ruta de acceso del sistema (o puede agregarlo en una carpeta que ya se encuentre en la ruta de acceso).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). Después de esto, simplemente puede usar dotnet hello para ejecutar este ejemplo.After this, you can just use dotnet hello to run this example.