Thanks to the new .csproj format and the changes to NuGet packages, it’s now easy to create a tool that can run as part of the build process. To try this, we’ll create a solution with two projects – one for the tool and one for the tool to run as part as the build process. Using the console, the following is a simple way to create the initial solution:
> mkdir ToolExample
> cd ToolExample
> dotnet new sln
> dotnet new console -o ConsoleApp
> dotnet new console -o ToolApp
> dotnet sln add ConsoleApp
> dotnet sln add ToolApp
To get our tool to do something as part of the build, when we package it as a NuGet package we need to add a Package.targets file that contains the MSBuild target we want to run as part of the project that the package gets installed to. To prove our tool is running we’ll start off with a simple message that gets output when our target is invoked. Add the following file called Package.targets inside the ToolApp directory:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ToolTarget"
AfterTargets="AfterBuild">
<Message Text="Message from ToolApp" Importance="High" />
</Target>
</Project>
In order for NuGet to use the file, it must have the same name as the package that contains it. We can use the $(PackageId) MSBuild property to ensure when the file is packages inside the nupkg file is has the expected name. Alter the ToolApp.csproj file to match the following, which also marks it as a developer dependency:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<DevelopmentDependency>true</DevelopmentDependency>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.0.1</Version>
</PropertyGroup>
<ItemGroup>
<Content Include="Package.targets">
<Pack>true</Pack>
<PackagePath>build/$(PackageId).targets</PackagePath>
<Visible>true</Visible>
</Content>
</ItemGroup>
</Project>
Since we’ve set it to build the package on build, running dotnet build ToolApp will create our nupkg file that can be consumed by our test console application. To consume it inside the console app you can either host it on a NuGet server or, more conveniently during testing, tell NuGet the folder that contains the package via the NuGet.config file inside the ConsoleApp directory:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="LocalDebug" value="../ToolApp/bin/Debug" />
</packageSources>
</configuration>
Install the package inside the ConsoleApp.csproj by editing it to look like the following:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ToolApp" Version="0.0.1" />
</ItemGroup>
</Project>
If you now build the console application you should now see the message from the tool we created:
> dotnet build ConsoleApp
> ConsoleApp -> .../ConsoleApp.dll
> Message from ToolApp
Running our own code
The above created the foundation for running a task when your package is installed in another project. Now if we wanted to run our own code instead, that can be done using the same technique. First we’ll put the code we want to run inside the Program.cs file inside the ToolApp project:
namespace ToolApp
{
using System;
public static class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello from ToolApp!");
}
}
}
Can’t get simpler than that. However, if we build it and package it now when the package gets installed the target project would have a reference to our ToolApp.dll. This is because the output of a build by default gets packed inside the lib folder inside the nupkg, which in turn causes it to get added as a reference. To get around this we’ll force the output to be packed inside another folder inside the nupkg, however, this will generate the warning an assembly file that is not in lib folder so we’ll suppress that too. Edit ToolApp.csproj with the following added:
<PropertyGroup>
...
<BuildOutputTargetFolder>tasks</BuildOutputTargetFolder>
<NoWarn>$(NoWarn);NU5100</NoWarn>
...
</PropertyGroup>
Finally, change the Package.target to run our new code by changed the Message element to an Exec element, using the MSBuild property to find the current location of where our package was installed:
<Exec Command="dotnet $(MSBuildThisFileDirectory)../tasks/netcoreapp2.1/ToolApp.dll" />
Finally, if you bump the version of the tool package (in both csproj files) and build the console application you should see the following output:
> dotnet build ConsoleApp
> ConsoleApp -> .../ConsoleApp.dll
> Hello from ToolApp!

