I need a command line tool to run over some files in a project and generate some code that can then be built with the rest of the project’s source code. However, the documentation to get this to be an automatic step of the build is a bit lacking – there are plenty of articles on creating a .NET Core CLI tool, however, these have to be manually invoked at the command prompt.
.NET Core projects files (the Visual Studio 2017 csproj type) allow the NuGet packages to be specified in the same file as any other project/assembly reference. It also allows a special type of NuGet package that was created as a
DotNetCliTool type to be pulled into a project and integrated with the rest of the
dotnet tooling, for example:
dotnet restore dotnet my-tool
The tool is just a standard .NET Core console application that runs in the projects directory and can receive command line arguments as any other console program can (i.e.
dotnet my-tool argument would pass ‘argument’ to the entry point of the program).
Creating a tool
Creating a tool is simple: create a new .NET Core console app (MUST be a .NET Core App 1.0, not 1.1) and edit the project file to set the package type to be a .NET CLI tool (at the moment there’s no UI for this in Visual Studio, so you’ll have to edit the csproj file, but that can be done from Visual Studio now by right clicking on the project and then there will be an Edit option in the context menu).
Here’s an example project file:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>netcoreapp1.0</TargetFramework> <PackageType>DotNetCliTool</PackageType> </PropertyGroup> </Project>
Another thing that we need to do is ensure the application is prefixed with
dotnet- in order for it to work with dotnet. This can be done via the UI (the Assembly name in the properties) or by editing the csproj:
<PropertyGroup> ... <AssemblyName>dotnet-example-tool</AssemblyName> </PropertyGroup>
You can now build that and then create a NuGet package by running the dotnet pack command in the project’s folder.
Using the tool
For testing purposes, it’s probably easier to put the tool on your local machine and setting up Visual Studio to use the local package source:
In another project, you can now reference the tool as a NuGet package. Unfortunately, at the time of writing the Visual Studio NuGet UI throws an error of “Package ‘ExampleTool’ has a package type ‘DotNetCliTool’ that is not supported by project ‘ExampleConsole’”. This means you’ll need to edit the csproj file to include it manually:
<ItemGroup> <DotNetCliToolReference Include="ExampleTool" Version="1.0.0" /> </ItemGroup>
To test this works, open the command prompt in the project’s directory and run the following:
dotnet restore dotnet example-tool
You should see “Hello World!” displayed (or whatever you made your program do). We’re nearly there, however, I wanted to the tool to run before each build so it can generate some source files that get swept up in the build – at the moment it’s a manual step.
Integration with the build
project.json was nice and compact, one advantage of moving back to MSBuild is we can now take advantage of the exec task to run our tool during the build. To do this, edit the csproj file of the project consuming the tool to add the following:
<Target Name="RunExampleTool" BeforeTargets="Build"> <Exec Command="dotnet example-tool" WorkingDirectory="$(ProjectDir)" /> </Target>
Now when you build the solution, if you check the build output you should see “Hello World!” in there. Success!
Analysing the above, you can see we’ve created a target that runs before the built in Build task (note you used to be able to just name your task BeforeBuild, however, this no longer works – I’m guessing it’s a result of no longer including the common targets?). Next the task the target will run is an exec task, which invokes our tool (via the dotnet command). Here the important thing to note is that we’re setting the working directory to be the project’s directory, which is achieved using the MSBuild macro
$(ProjectDir) – dotnet will only automatically pick up the tool from the NuGet packages is we’re in that directory.
That’s it, quite simple but took me a while to piece everything together as the documentation is missing in some areas and I guess it’s not a common thing I’m trying to achieve.