Skip to content

Repository files navigation

Overview

Official BuildNuGetNuGet

This class library is a fluent interface for generating MSBuild projects and NuGet package repositories. Its primarily for unit tests that need MSBuild projects to do their testing.

Example

You want to test a custom MSBuild task that you are building so your unit tests need to generate a project that you can build with MSBuild. The following code would generate the necessary project:

ProjectCreatorcreator=ProjectCreator.Create("test.proj").UsingTaskAssemblyFile("MyTask",pathToMyTaskAssembly).ItemInclude("CustomItem","abc").Property("CustomProperty","value").Target("Build").Task(name:"MyTask",parameters:newDictionary<string,string>{{"MyProperty","$(CustomProperty)"},{"Items","@(CustomItem)"}});

The resulting project would look like this:

<?xml version="1.0" encoding="utf-8"?>
<Projectxmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTaskTaskName="MyTask"AssemblyFile="..." />
<ItemGroup>
<CustomItemInclude="abc" />
</ItemGroup>
<PropertyGroup>
<CustomProperty>value</CustomProperty>
</PropertyGroup>
<TargetName="Build">
<MyTaskMyProperty="$(CustomProperty)"Items="@(CustomItem)" />
</Target>
</Project>

Building Projects

Use the TryBuild methods to build your projects. TryBuild returns a BuildOutput object which captures the build output for you.

Note: Projects are built in a different process to avoid assembly load conflicts so projects must be saved before being built.

This example creates a project that logs a message, saves the project, executes TryBuild, and asserts some conditions against the build output.

ProjectCreator.Create().TaskMessage("Hello, World!").Save(path).TryBuild(outboolsuccess,outBuildOutputlog);Assert.True(success);Assert.Equal("Hello, World!",log.Messages.Single().Message);

Extensibility

You can extend the ProjectCreator class by adding extension methods.

Custom Item Type

You can implement a method that adds a known item type with metadata. It is recommended that you prefix the method with Item so it is included alphabetically with other methods that add items. Your method should call the ProjectCreator.ItemInclude method with the custom metadata.

publicstaticclassExtensionsMethods{publicstaticProjectCreatorItemMyCustomType(thisProjectCreatorcreator,stringinclude,stringparam1,stringparam2,stringcondition=null){returncreator.ItemInclude("MyCustomType",include,null,newDictionary<string,string>{{"Metadata1",param1},{"Metadata2",param2}},condition);}}

The above extension method would add the following item:

<ItemGroup>
<MyCustomTypeInclude="X">
<Metadata1>Y</Metadata1>
<Metadata2>Y</Metadata2>
</MyCustomType>
</ItemGroup>

Resolving MSBuild

This API does not redistribute MSBuild and so it must be located at run-time. There are two locators to be aware of:

  1. MSBuildAssemblyResolver.Register() provided in this package
  2. MSBuildLocator.RegisterDefaults() in the MSBuildLocator package

In either case, you need to register before running any MSBuild operations. There are three main ways to ensure registration:

1. Module initializer (preferred)

If you're targeting C# 9.0+ / .NET 5+, use a module initializer. MSBuild assemblies will be automatically located when your assembly is loaded:

usingSystem.Runtime.CompilerServices;internalstaticclassMyModuleInitializer{[ModuleInitializer]internalstaticvoidInitializeMSBuild(){MSBuildAssemblyResolver.Register();}}

2. Inherit from MSBuildTestBase

If you're unable or don't want to use a module initializer and your project is a unit test, have your test class inherit from MSBuildTestBase:

/// <summary>/// A base class for all unit tests that inherits from MSBuildTestBase./// </summary>publicclassMyTestBase:MSBuildTestBase{// Custom base class logic.}/// <summary>/// A unit test class that will be able to use this API since MSBuild is located at run-time./// </summary>publicclassMyTest:MyTestBase{}

3. Use a static constructor

Add a static constructor and call the registration API:

publicstaticclassMyApp{publicstaticMyApp(){MSBuildAssemblyResolver.Register();}}

Templates

Several project templates are included for convenience purposes. One template is the SDK-style C# project:

usingMicrosoft.Build;usingMicrosoft.Build.Utilities.ProjectCreation;namespaceMyApplication{publicstaticvoidMain(string[]args){ProjectRootElementproject=ProjectCreator.Templates.SdkCsproj("project1.csproj");}}

In the above example, the generated project looks like this:

<ProjectSdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
</Project>

Custom Templates

You can create your own templates by adding extension methods.

Your extension methods should extend the ProjectCreatorTemplates class so they are available as methods to the ProjectCreator.Templates property.

publicstaticclassExtensionMethods{publicstaticProjectCreatorLogsMessage(thisProjectCreatorTemplatestemplate,stringtext,stringpath=null,MessageImportance?importance=null,stringcondition=null){returnProjectCreator.Create(path).TaskMessage(text,importance,condition);}}

The above extension method can be called like this:

usingMicrosoft.Build;usingMicrosoft.Build.Utilities.ProjectCreation;namespaceMyApplication{publicstaticvoidMain(string[]args){ProjectRootElementproject=ProjectCreator.Templates.LogsMessage("Hello, World!");}}

And the resulting project would look like this:

<?xml version="1.0" encoding="utf-8"?>
<Projectxmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<TargetName="Build">
<MessageText="Hello, World!" />
</Target>
</Project>

Visual Studio Solutions

You can create Visual Studio solutions with the SolutionCreator class. This class is a wrapper around the [VS-SolutionPersistence] library which supports both .sln and .slnx solution file formats.

The following example creates a solution with two projects:

ProjectCreatorproject1=ProjectCreator.Templates.SdkCsproj(path:Path.Combine(Environment.CurrentDirectory,"project1","project1.csproj"));SolutionCreator.Create(Path.Combine(Environment.CurrentDirectory,"solution1.sln")).Configuration("Debug").Configuration("Release").Platform("Any CPU").Project(project1).Save();

Global.json Files

You can create global.json files with the GlobalJsonCreator class to control SDK versions and MSBuild SDK references in your test projects.

Examples

Basic global.json with SDK version

GlobalJsonCreator.Create(directory:Environment.CurrentDirectory,sdkVersion:"8.0.100").Save();

This generates:

{
"sdk": {
"version": "8.0.100"
}
}

With SDK roll-forward policy and prerelease

GlobalJsonCreator.Create(directory:Environment.CurrentDirectory,sdkVersion:"8.0.100",rollForward:GlobalJsonSdkRollForward.LatestMinor,allowPrerelease:true).Save();

This generates:

{
"sdk": {
"version": "8.0.100",
"rollForward": "latestMinor",
"allowPrerelease": true
}
}

With MSBuild SDKs

GlobalJsonCreator.Create(directory:Environment.CurrentDirectory,sdkVersion:"8.0.100").MSBuildSdk("Microsoft.Build.NoTargets","3.7.0").MSBuildSdk("Microsoft.Build.Traversal","4.1.0").Save();

This generates:

{
"sdk": {
"version": "8.0.100"
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.Build.Traversal": "4.1.0"
}
}

Get JSON without saving

stringjson=GlobalJsonCreator.Create(directory:Environment.CurrentDirectory,sdkVersion:"8.0.100").MSBuildSdk("My.Custom.Sdk","1.0.0").ToJson();// Or use implicit string conversionstringjson2=GlobalJsonCreator.Create(directory:Environment.CurrentDirectory,sdkVersion:"8.0.100");

Package Repositories and Feeds

NuGet and MSBuild are very tightly coupled and a lot of times you need packages available when building projects. This API offers two solutions:

  1. Package repository - This allows you to create a repository of restored packages as if NuGet has already installed them.
  2. Package feed - This allows you to create a file-based package feed of actual .nupkg files.

Package Repository

Create a package repository if you want to generate packages as if they've already been installed. If you want to create actual .nupkg packages, see Package Feed

Examples

Create a package repository with a package that supports two target frameworks:

using(PackageRepository.Create(rootPath).Package("MyPackage","1.2.3",outPackageIdentitypackage).Library("net472").Library("netstandard2.0")){// Create projects that reference packages}

The resulting package would have a lib\net472\MyPackage.dll and lib\netstandard2.0\MyPackage.dll class library. This allows you to restore and build projects that consume the packages

using(PackageRepository.Create(rootPath).Package("MyPackage","1.0.0",outPackageIdentitypackage).Library("netstandard2.0")){ProjectCreator.Templates.SdkCsproj().ItemPackageReference(package).Save(Path.Combine(rootPath,"ClassLibraryA","ClassLibraryA.csproj")).TryBuild(restore:true,outboolresult,outBuildOutputbuildOutput);}

The result would be a project that references the MyPackage package and would restore and build accordingly.

By default, all package sources are disabled so that when running unit tests packages are not pulled from the network. This is because the package repository is supposed to simulate that packages have already been restored.

You can add feeds if needed with the following examples:

Add nuget.org as a feed

using(PackageRepository.Create(rootPath,feeds:newUri("https://api.nuget.org/v3/index.json")){}

Add a local directory as a feed

DirectoryInfolocalFeed=newDirectoryInfo("<path to local feed>");using(PackageRepository.Create(rootPath,feeds:newUri(localFeed.FullName)){}

Create a local feed on-the-fly

// Create a local feed with one packagestringTestRootPath="<path to folder used for testing>";stringFeedRootPath=Path.Combine(TestRootPath,"Feed");usingPackageFeedpackageFeed=PackageFeed.Create(FeedRootPath).Package("PackageA","1.0.0",outPackagepackageA).Library(TargetFramework).Save();// Create a package repository that points to the local feedusingPackageRepositorypackageRepository=PackageRepository.Create(TestRootPath,feeds:packageFeed);

Package Feed

Create a package feed if you want to generate .nupkg packages that can be installed by NuGet. If you want to create a repository of packages as if they've already been installed, see Package Repository.

Example

Create a package feed with a package that supports two target frameworks:

PackageFeed.Create(rootPath).Package("MyPackage","1.2.3",outPackagepackage).Library("net472").Library("netstandard2.0")).Save();ProjectCreatorprojectCreator=ProjectCreator.Create().ItemPackageReference(package)

The resulting package would have a lib\net472\MyPackage.dll and lib\netstandard2.0\MyPackage.dll class library. This allows you to restore and build projects that consume the packages

About

A fluent API for creating MSBuild projects for use by unit tests

Resources

Contributing

Stars

51 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages