Skip to content


Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?


Failed to load latest commit information.



NuGet version

Build status CodeQL analysis Lint Spell check

AppVeyor smoke test status CircleCI smoke test status Cirrus CI smoke test status GitLab CI/CD smoke test status

Bullseye is a .NET library that runs a target dependency graph.

Bullseye targets can do anything. They are not restricted to building .NET projects.

Platform support: .NET Standard 2.1 and later.

Quick start

  • Create a .NET console app named targets and add a reference to Bullseye.

  • Replace the contents of Program.cs with:

    using static System.Console;
    using static Bullseye.Targets;
    Target("default", () => WriteLine("Hello, world!"));
    await RunTargetsAndExitAsync(args);
  • Run the app. E.g. dotnet run or F5 in Visual Studio:

Voilà! You've just written and run your first Bullseye program. You will see output similar to:

For help, run dotnet run -- --help.

Also see the async quick start.

Defining dependencies

Target("make-tea", () => Console.WriteLine("Tea made."));
Target("drink-tea", DependsOn("make-tea"), () => Console.WriteLine("Ahh... lovely!"));
Target("walk-dog", () => Console.WriteLine("Walkies!"));
Target("default", DependsOn("drink-tea", "walk-dog"));

Enumerable inputs

    ForEach("digestives", "chocolate hobnobs"),
    biscuits => Console.WriteLine($"Mmm...{biscuits}! Nom nom."));
dotnet run -- eat-biscuits

Sample wrapper scripts

  • build

    #!/usr/bin/env bash
    set -euo pipefail
    dotnet run --project targets -- "$@"
  • build.cmd

    @echo Off
    dotnet run --project targets -- %*

Command-line arguments

Generally, all the command-line arguments passed to Program.cs should be passed along to Bullseye, as shown in the quick start above (RunTargetsAndExitAsync(args);). This is because Bullseye effectively provides a command-line interface, with options for displaying a list of targets, performing dry runs, suppressing colour, and more. For full details of the command-line options, run your targets project supplying the --help (-h/-?) option:

dotnet run --project targets -- --help
./build --help
./build.cmd --help

You can also handle custom arguments in Program.cs, but you should ensure that only valid arguments are passed along to Bullseye and that the help text contains both your custom arguments and the arguments supported by Bullseye. A good way to do this is to use a command-line parsing package to define your custom arguments, and to provide translation between the package and Bullseye. For example, see the test projects for:

Non-static API

For most cases, the static API described above is sufficient. For more complex scenarios where a number of target collections are required, the non-static API may be used.

var targets1 = new Targets();
targets1.Add("foo", () => Console.Out.WriteLine("foo1"));

var targets2 = new Targets();
targets2.Add("foo", () => Console.Out.WriteLine("foo2"));

await targets1.RunWithoutExitingAsync(args);
await targets2.RunWithoutExitingAsync(args);


Bullseye supports NO_COLOR.


Can I force a pause before exiting when debugging in Visual Studio 2017 (or earlier)?

Yes! Add the following line anywhere before calling RunTargetsAndExitAsync:

AppDomain.CurrentDomain.ProcessExit += (s, e) => Console.ReadKey();

Note that the common way to do this for .NET console apps is to add a line such as the following before the end of the Program.Main method:


This does not work after calling RunTargetsAndExitAsync because that is the final statement that will be executed.

In Visual Studio 2019 and later, .NET console apps pause before exiting by default, so none of this is required.

Who's using Bullseye?

To name a few:

Feel free to send a pull request to add your repository or organisation to this list!

Target by Franck Juncker from the Noun Project.