DevTeam / csharp-interactive

Build automation system for .NET

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Build automation system for .NET

NuGet csi GitHub GitHub Build

C# interactive build automation system makes it easy to build .NET projects. It can be part of your solution as a regular console cross-platform .NET application, run C# scripts without compiling them, or even run in REPL mode - allowing you to run C# interactively.

Advantages

  • 3 compatible operating modes
  • .NET console application project
  • Part of .NET solution
  • C#
  • Cross-platform
  • Debugging capability
  • No model binding (Task, Target, DependsOn, etc.)
    • no restrictions
    • no learning curve
    • can use common .NET development practices
  • Simple and powerful API for building .NET projects
  • Passing named parameters as in MSBuild
  • Summarised statistics as in MSBuild
    • Child processes
    • Warnings and errors
    • Tests
    • Execution time
  • CI/CD integration
    • GitLab/GitHub Actions/JetBrains Space etc.
    • TeamCity
      • Special runner
      • Report on warnings and errors
      • Test statistics
      • Execution progress
      • Real-time integration
      • Parameters passing between build configuration steps

Operating modes

These modes are practically compatible, i.e., for example, a script can be run as a .NET project, and vice versa, with minimal or no changes.

API

  • Output, logging and tracing
  • Arguments and parameters
  • Command line
  • Docker
  • Microsoft DI API to resolve dependencies
  • NuGet
  • .NET CLI
// This directive in a script allows you to use host API types
// without specifying the fully qualified namespace of these types
using HostApi;

// Output, logging and tracing API
WriteLine("Hello");
WriteLine("Hello !!!", Color.Highlighted);
Error("Error details", "ErrorId");
Warning("Warning");
Info("Some info");
Trace("Trace message");

// API for arguments and parameters
Info("First argument: " + (Args.Count > 0 ? Args[0] : "empty"));
Info("Version: " + Props.Get("version", "1.0.0"));
Props["version"] = "1.0.1";

var configuration = Props.Get("configuration", "Release");
Info($"Configuration: {configuration}");

// Command line API
var cmd = new CommandLine("whoami");

cmd.Run().EnsureSuccess();

// Asynchronous way
await cmd.RunAsync().EnsureSuccess();

// API for Docker CLI
await new DockerRun("ubuntu")
    .WithCommandLine(cmd)
    .WithPull(DockerPullType.Always)
    .WithAutoRemove(true)
    .RunAsync()
    .EnsureSuccess();

// Microsoft DI API to resolve dependencies
var nuget = GetService<INuGet>();

// Creating a custom service provider
var serviceCollection = GetService<IServiceCollection>();
serviceCollection.AddSingleton<MyTool>();

var myServiceProvider = serviceCollection.BuildServiceProvider();
var tool = myServiceProvider.GetRequiredService<MyTool>();

// API for NuGet
var settings = new NuGetRestoreSettings("MySampleLib")
    .WithVersionRange(VersionRange.Parse("[1.0.14, 1.1)"))
    .WithTargetFrameworkMoniker("net6.0")
    .WithPackagesPath(".packages");

var packages = nuget.Restore(settings);
foreach (var package in packages)
{
    Info(package.Path);
}

// API for .NET CLI
var buildResult = new DotNetBuild().WithConfiguration(configuration).WithNoLogo(true)
    .Build().EnsureSuccess();

var warnings = buildResult.Warnings
    .Where(warn => Path.GetFileName(warn.File) == "Calculator.cs")
    .Select(warn => $"{warn.Code}({warn.LineNumber}:{warn.ColumnNumber})")
    .Distinct();

foreach (var warning in warnings)
{
    await new HttpClient().GetAsync(
        "https://api.telegram.org/bot7102686717:AAEHw7HZinme_5kfIRV7TwXK4Xql9WPPpM3/" +
        "sendMessage?chat_id=878745093&text="
        + HttpUtility.UrlEncode(warning));
}

// Asynchronous way
var cts = new CancellationTokenSource();
await new DotNetTest()
    .WithConfiguration(configuration)
    .WithNoLogo(true)
    .WithNoBuild(true)
    .BuildAsync(CancellationOnFirstFailedTest, cts.Token)
    .EnsureSuccess();

void CancellationOnFirstFailedTest(BuildMessage message)
{
    if (message.TestResult is { State: TestState.Failed }) cts.Cancel();
}

// Parallel tests
var tempDir = Directory.CreateTempSubdirectory();
try
{
    new DotNetPublish()
        .WithConfiguration(configuration)
        .WithNoLogo(true)
        .WithNoBuild(true)
        .WithFramework("net8.0")
        .AddProps(("PublishDir", tempDir.FullName))
        .Build().EnsureSuccess();

    var test = new VSTest().WithTestFileNames("*.Tests.dll");

    var tasks = from tagSuffix in new[] {"bookworm-slim", "alpine", "noble"}
        let image = $"mcr.microsoft.com/dotnet/sdk:8.0-{tagSuffix}"
        let dockerRun = new DockerRun(image)
            .WithCommandLine(test)
            .WithAutoRemove(true)
            .WithVolumes((tempDir.FullName, "/app"))
            .WithContainerWorkingDirectory("/app")
        select dockerRun.BuildAsync(CancellationOnFirstFailedTest, cts.Token);

    await Task.WhenAll(tasks).EnsureSuccess();
}
finally { tempDir.Delete(); }

class MyTool(INuGet nuGet);

Important

using HostApi; directive in a script allows you to use host API types without specifying the fully qualified namespace of these types.

Script runner and REPL tool

Please see this page for installation details.

Launch the tool in the interactive mode:

dotnet csi

Run a specified script with a given argument:

dotnet csi Samples/Scripts/hello.csx World 

Run a single script located in the MyDirectory directory:

dotnet csi Samples/Build

Usage:

dotnet csi [options] [--] [script] [script arguments]

Executes a script if specified, otherwise launches an interactive REPL (Read Eval Print Loop).

-- - Indicates that the remaining arguments should not be treated as options.

script - The path to the script file to run. If no such file is found, the command will treat it as a directory and look for a single script file inside that directory.

script arguments - Script arguments are accessible in a script via the global list Args[index] by an argument index.

@file - Read the response file for more options.

Supported options:

Option Description Alternative form
--help Show how to use the command. /?, -h, /h, /help
--version Display the tool version. /version
--source Specify the NuGet package source to use. Supported formats: URL, or a UNC directory path. -s, /s, /source
--property <key=value> Define a key-value pair(s) for the script properties called Props, which is accessible in scripts. -p, /property, /p
--property:<key=value> Define a key-value pair(s) in MSBuild style for the script properties called Props, which is accessible in scripts. -p:<key=value>, /property:<key=value>, /p:<key=value>, --property:key1=val1;key2=val2

.NET build project

Please see this page for details on how to install the project template.

Create a console project Build containing a script from the template build

dotnet new build -o ./Build

The created project contains 2 entry points:

  • Program.csx to run as a script
  • Program.cs to run as .NET application

To run the script from the command line from the directory Build:

dotnet csi Build

To run as a .NET console application:

dotnet run --project Build

Usage Scenarios

Using Args

Args have got from the script arguments.

if (Args.Count > 0)
{
    WriteLine(Args[0]);
}

if (Args.Count > 1)
{
    WriteLine(Args[1]);
}

Using Props

WriteLine(Props["version"]);
WriteLine(Props.Get("configuration", "Release"));

// Some CI/CDs have integration of these properties.
// For example in TeamCity this property with all changes will be available in the next TeamCity steps.
Props["version"] = "1.1.6";

Using the Host property

Host is actually the provider of all global properties and methods.

var packages = Host.GetService<INuGet>();
Host.WriteLine("Hello");

Get services

This method might be used to get access to different APIs like INuGet or ICommandLine.

GetService<INuGet>();

var serviceProvider = GetService<IServiceProvider>();
serviceProvider.GetService(typeof(INuGet));

Besides that, it is possible to get an instance of System.IServiceProvider to access APIs.

Service collection

public void Run()
{
    var serviceProvider = 
        GetService<IServiceCollection>()
        .AddTransient<MyTask>()
        .BuildServiceProvider();

    var myTask = serviceProvider.GetRequiredService<MyTask>();
    var exitCode = myTask.Run();
    exitCode.ShouldBe(0);
}

private class MyTask(ICommandLineRunner runner)
{
    public int? Run() => runner
        .Run(new CommandLine("whoami"))
        .EnsureSuccess()
        .ExitCode;
}

Write a line to a build log

WriteLine("Hello");

Write an empty line to a build log

WriteLine();

Write a line highlighted with "Header" color to a build log

WriteLine("Hello", Header);

Log an error to a build log

Error("Error info", "Error identifier");

Log a warning to a build log

Warning("Warning info");

Log information to a build log

Info("Some info");

Log trace information to a build log

Trace("Some trace info");

Build command lines

// Adds the namespace "Script.Cmd" to use Command Line API
using HostApi;

// Creates and run a simple command line 
"whoami".AsCommandLine().Run().EnsureSuccess();

// Creates and run a simple command line 
new CommandLine("whoami").Run().EnsureSuccess();

// Creates and run a command line with arguments 
new CommandLine("cmd", "/c", "echo", "Hello").Run();

// Same as previous statement
new CommandLine("cmd", "/c")
    .AddArgs("echo", "Hello")
    .Run()
    .EnsureSuccess();

(new CommandLine("cmd") + "/c" + "echo" + "Hello")
    .Run()
    .EnsureSuccess();

"cmd".AsCommandLine("/c", "echo", "Hello")
    .Run()
    .EnsureSuccess();

("cmd".AsCommandLine() + "/c" + "echo" + "Hello")
    .Run()
    .EnsureSuccess();

// Just builds a command line with multiple environment variables
var cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .AddVars(("Var1", "val1"), ("var2", "Val2"));

// Same as previous statement
cmd = new CommandLine("cmd") + "/c" + "echo" + "Hello" + ("Var1", "val1") + ("var2", "Val2");

// Builds a command line to run from a specific working directory 
cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .WithWorkingDirectory("MyDyrectory");

// Builds a command line and replaces all command line arguments
cmd = new CommandLine("cmd", "/c", "echo", "Hello")
    .WithArgs("/c", "echo", "Hello !!!");

Run a command line

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

GetService<ICommandLineRunner>()
    .Run(new CommandLine("cmd", "/c", "DIR"))
    .EnsureSuccess();

// or the same thing using the extension method
new CommandLine("cmd", "/c", "DIR")
    .Run()
    .EnsureSuccess();

// using operator '+'
var cmd = new CommandLine("cmd") + "/c" + "DIR";
cmd.Run().EnsureSuccess();

// with environment variables
cmd = new CommandLine("cmd") + "/c" + "DIR" + ("MyEnvVar", "Some Value");
cmd.Run().EnsureSuccess();

Run a command line asynchronously

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

await GetService<ICommandLineRunner>()
    .RunAsync(new CommandLine("cmd", "/C", "DIR"))
    .EnsureSuccess();

// or the same thing using the extension method
var result = await new CommandLine("cmd", "/c", "DIR")
    .RunAsync()
    .EnsureSuccess();

Run and process output

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var lines = new List<string>();
var result = new CommandLine("cmd", "/c", "SET")
    .AddVars(("MyEnv", "MyVal"))
    .Run(output => lines.Add(output.Line))
    .EnsureSuccess();

lines.ShouldContain("MyEnv=MyVal");

Run asynchronously in parallel

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var task = new CommandLine("cmd", "/c", "DIR")
    .RunAsync()
    .EnsureSuccess();

var result = new CommandLine("cmd", "/c", "SET")
    .Run()
    .EnsureSuccess();

await task;

Cancellation of asynchronous run

Cancellation will destroy the process and its child processes.

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

var cancellationTokenSource = new CancellationTokenSource();
var task = new CommandLine("cmd", "/c", "TIMEOUT", "/T", "120")
    .RunAsync(default, cancellationTokenSource.Token);

cancellationTokenSource.CancelAfter(TimeSpan.FromMilliseconds(100));
task.IsCompleted.ShouldBeFalse();

Run timeout

If timeout expired a process will be killed.

// Adds the namespace "HostApi" to use Command Line API
using HostApi;

int? exitCode = new CommandLine("cmd", "/c", "TIMEOUT", "/T", "120")
    .Run(default, TimeSpan.FromMilliseconds(1))
    .EnsureSuccess()
    .ExitCode;

exitCode.HasValue.ShouldBeFalse();

Build a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
new DotNetNew("xunit", "-n", "MyLib", "--force")
    .Build()
    .EnsureSuccess();

// Builds the library project, running a command like: "dotnet build" from the directory "MyLib"
var result = new DotNetBuild()
    .WithWorkingDirectory("MyLib")
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

// Runs tests in docker
result = new DotNetTest()
    .WithWorkingDirectory("MyLib")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);
result.Summary.Tests.ShouldBe(1);
result.Tests.Count(test => test.State == TestState.Finished).ShouldBe(1);

Clean a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Builds the library project, running a command like: "dotnet build" from the directory "MyLib"
result = new DotNetBuild()
    .WithWorkingDirectory("MyLib")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Clean the project, running a command like: "dotnet clean" from the directory "MyLib"
result = new DotNetClean()
    .WithWorkingDirectory("MyLib")
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);

Run a custom .NET command

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Gets the dotnet version, running a command like: "dotnet --version"
NuGetVersion? version = default;
new DotNetCustom("--version")
    .Run(message => NuGetVersion.TryParse(message.Line, out version))
    .EnsureSuccess();

version.ShouldNotBeNull();

Test a project using the MSBuild VSTest target

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var result = new DotNetNew("mstest", "-n", "MyTests", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Runs tests via a command like: "dotnet msbuild /t:VSTest" from the directory "MyTests"
result = new MSBuild()
    .WithTarget("VSTest")
    .WithWorkingDirectory("MyTests")
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Summary.Tests.ShouldBe(1);
result.Tests.Count(test => test.State == TestState.Finished).ShouldBe(1);

Pack a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Creates a NuGet package of version 1.2.3 for the project, running a command like: "dotnet pack /p:version=1.2.3" from the directory "MyLib"
result = new DotNetPack()
        .WithWorkingDirectory("MyLib")
        .AddProps(("version", "1.2.3"))
        .Build()
        .EnsureSuccess();

result.ExitCode.ShouldBe(0);

Publish a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force", "-f", "net8.0")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Publish the project, running a command like: "dotnet publish --framework net6.0" from the directory "MyLib"
result = new DotNetPublish()
    .WithWorkingDirectory("MyLib")
    .WithFramework("net8.0")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

Restore a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Restore the project, running a command like: "dotnet restore" from the directory "MyLib"
result = new DotNetRestore()
    .WithWorkingDirectory("MyLib")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

Run a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new console project, running a command like: "dotnet new console -n MyApp --force"
var result = new DotNetNew("console", "-n", "MyApp", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Runs the console project using a command like: "dotnet run" from the directory "MyApp"
var stdOut = new List<string>();
result = new DotNetRun().WithWorkingDirectory("MyApp")
    .Build(message => stdOut.Add(message.Text))
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Checks StdOut
stdOut.ShouldBe(new[] {"Hello, World!"});

Test a project

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var result = new DotNetNew("mstest", "-n", "MyTests", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Runs tests via a command like: "dotnet test" from the directory "MyTests"
result = new DotNetTest()
    .WithWorkingDirectory("MyTests")
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Summary.Tests.ShouldBe(1);
result.Tests.Count(test => test.State == TestState.Finished).ShouldBe(1);

Run tests under dotCover

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
new DotNetNew("mstest", "-n", "MyTests", "--force")
    .Run()
    .EnsureSuccess();

// Creates the tool manifest and installs the dotCover tool locally
// It is better to run the following 2 commands manually
// and commit these changes to a source control
new DotNetNew("tool-manifest")
    .Run()
    .EnsureSuccess();

new DotNetCustom("tool", "install", "--local", "JetBrains.dotCover.GlobalTool")
    .Run()
    .EnsureSuccess();

// Creates a test command
var test = new DotNetTest()
    .WithProject("MyTests");

var dotCoverSnapshot = Path.Combine("MyTests", "dotCover.dcvr");
var dotCoverReport = Path.Combine("MyTests", "dotCover.html");
// Modifies the test command by putting "dotCover" in front of all arguments
// to have something like "dotnet dotcover test ..."
// and adding few specific arguments to the end
var testUnderDotCover = test.Customize(cmd =>
    cmd.ClearArgs()
    + "dotcover"
    + cmd.Args
    + $"--dcOutput={dotCoverSnapshot}"
    + "--dcFilters=+:module=TeamCity.CSharpInteractive.HostApi;+:module=dotnet-csi"
    + "--dcAttributeFilters=System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage");
    
// Runs tests under dotCover via a command like: "dotnet dotcover test ..."
var result = testUnderDotCover
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Tests.Count(i => i.State == TestState.Finished).ShouldBe(1);

// Generates a HTML code coverage report.
new DotNetCustom("dotCover", "report", $"--source={dotCoverSnapshot}", $"--output={dotCoverReport}", "--reportType=HTML")
    .Run().EnsureSuccess();

// Check for a dotCover report
File.Exists(dotCoverReport).ShouldBeTrue();

Restore local tools

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

var projectDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()[..4]);
Directory.CreateDirectory(projectDir);
    
// Creates a local tool manifest 
new DotNetNew("tool-manifest")
    .WithWorkingDirectory(projectDir)
    .Run()
    .EnsureSuccess();

// Restore local tools
new DotNetToolRestore()
    .WithWorkingDirectory(projectDir)
    .Run()
    .EnsureSuccess();

Test an assembly

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new test project, running a command like: "dotnet new mstest -n MyTests --force"
var result = new DotNetNew("mstest", "-n", "MyTests", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Builds the test project, running a command like: "dotnet build -c Release" from the directory "MyTests"
result = new DotNetBuild()
    .WithWorkingDirectory("MyTests")
    .WithConfiguration("Release")
    .WithOutput("MyOutput")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Runs tests via a command like: "dotnet vstest" from the directory "MyTests"
result = new VSTest()
    .AddTestFileNames(Path.Combine("MyOutput", "MyTests.dll"))
    .WithWorkingDirectory("MyTests")
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.ExitCode.ShouldBe(0);
result.Summary.Tests.ShouldBe(1);
result.Tests.Count(test => test.State == TestState.Finished).ShouldBe(1);

Build a project using MSBuild

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Creates a new library project, running a command like: "dotnet new classlib -n MyLib --force"
var result = new DotNetNew("classlib", "-n", "MyLib", "--force")
    .Build()
    .EnsureSuccess();

result.ExitCode.ShouldBe(0);

// Builds the library project, running a command like: "dotnet msbuild /t:Build -restore /p:configuration=Release -verbosity=detailed" from the directory "MyLib"
result = new MSBuild()
    .WithWorkingDirectory("MyLib")
    .WithTarget("Build")
    .WithRestore(true)
    .AddProps(("configuration", "Release"))
    .WithVerbosity(DotNetVerbosity.Detailed)
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

Shuts down build servers

// Adds the namespace "HostApi" to use .NET build API
using HostApi;

// Shuts down all build servers that are started from dotnet.
new DotNetBuildServerShutdown()
    .Run()
    .EnsureSuccess();

Restore NuGet a package of newest version

// Adds the namespace "HostApi" to use INuGet
using HostApi;

IEnumerable<NuGetPackage> packages = GetService<INuGet>()
    .Restore(new NuGetRestoreSettings("IoC.Container").WithVersionRange(VersionRange.All));

Restore a NuGet package by a version range for the specified .NET and path

// Adds the namespace "HostApi" to use INuGet
using HostApi;

var packagesPath = Path.Combine(
    Path.GetTempPath(),
    Guid.NewGuid().ToString()[..4]);

var settings = new NuGetRestoreSettings("IoC.Container")
    .WithVersionRange(VersionRange.Parse("[1.3, 1.3.8)"))
    .WithTargetFrameworkMoniker("net5.0")
    .WithPackagesPath(packagesPath);

IEnumerable<NuGetPackage> packages = GetService<INuGet>().Restore(settings);

Build a project in a docker container

// Adds the namespace "HostApi" to use .NET build API and Docker API
using HostApi;

// Creates a base docker command line
var dockerRun = new DockerRun()
    .WithAutoRemove(true)
    .WithInteractive(true)
    .WithImage("mcr.microsoft.com/dotnet/sdk")
    .WithPlatform("linux")
    .WithContainerWorkingDirectory("/MyProjects")
    .AddVolumes((ToAbsoluteLinuxPath(Environment.CurrentDirectory), "/MyProjects"));


// Creates a new library project in a docker container
dockerRun
    .WithCommandLine(new DotNetCustom("new", "classlib", "-n", "MyLib", "--force"))
    .Run()
    .EnsureSuccess();

// Builds the library project in a docker container
var result = dockerRun
    .WithCommandLine(new DotNetBuild().WithProject("MyLib/MyLib.csproj"))
    .Build()
    .EnsureSuccess();

// The "result" variable provides details about a build
result.Errors.Any(message => message.State == BuildMessageState.StdError).ShouldBeFalse();
result.ExitCode.ShouldBe(0);

string ToAbsoluteLinuxPath(string path) => 
    "/" + path.Replace(":", "").Replace('\\', '/');

Running in docker

// Adds the namespace "HostApi" to use Command Line API and Docker API
using HostApi;

// Creates some command line to run in a docker container
var cmd = new CommandLine("whoami");

// Runs the command line in a docker container
var result = new DockerRun(cmd, "mcr.microsoft.com/dotnet/sdk")
    .WithAutoRemove(true)
    .Run()
    .EnsureSuccess();

TeamCity integration via service messages

For more details how to use TeamCity service message API please see this page. Instead of creating a root message writer like in the following example:

using JetBrains.TeamCity.ServiceMessages.Write.Special;
using var writer = new TeamCityServiceMessages().CreateWriter(Console.WriteLine);

use this statement:

using JetBrains.TeamCity.ServiceMessages.Write.Special;
using var writer = GetService<ITeamCityWriter>();

This sample opens a block My Tests and reports about two tests:

// Adds a namespace to use ITeamCityWriter
using JetBrains.TeamCity.ServiceMessages.Write.Special;

using var writer = GetService<ITeamCityWriter>();
using (var tests = writer.OpenBlock("My Tests"))
{
    using (var test = tests.OpenTest("Test1"))
    {
        test.WriteStdOutput("Hello");
        test.WriteImage("TestsResults/Test1Screenshot.jpg", "Screenshot");
        test.WriteDuration(TimeSpan.FromMilliseconds(10));
    }

    using (var test = tests.OpenTest("Test2"))
    {
        test.WriteIgnored("Some reason");
    }
}

For more information on TeamCity Service Messages, see this page.

About

Build automation system for .NET

License:MIT License


Languages

Language:C# 99.9%Language:Batchfile 0.1%Language:Shell 0.0%