xx
.md

Build consistently. Keep the build simple.

xx is a build system for projects that want more consistency than a collection of shell scripts without adopting a large, fully hermetic build environment.

Build definitions are small Starlark files. They declare tools, environment changes, and commands. xx provisions versioned tools, gives each task a clean environment, and runs independent work concurrently.

xx is not fully hermetic. It deliberately chooses a practical middle ground: reproducible tool versions and controlled task environments, with familiar files and commands.

Why xx?

Managed CMake, Go, Ninja, Node.js, Protobuf, and Zig packages are supported. More language and tool packages can be added without changing the core model.

Install

Using Go

go install withxx.dev/xx@latest

Install Scripts

Unix

curl --proto '=https' --tlsv1.3 -sSf https://withxx.dev/scripts/install_xx.sh | sh

The installer upgrades an existing xx found on PATH. New installations use $HOME/.local/bin.

Set XX_INSTALL_DIR to override both locations:

export XX_INSTALL_DIR="$HOME/bin"
curl --proto '=https' --tlsv1.3 -sSf https://withxx.dev/scripts/install_xx.sh | sh

If the selected directory is not on PATH, the installer prints a warning but does not modify shell configuration.

Windows

Invoke-WebRequest -UseBasicParsing https://withxx.dev/scripts/install_xx.ps1 | Select-Object -ExpandProperty Content | Invoke-Expression

The installer upgrades an existing xx.exe found on PATH. New installations use %LOCALAPPDATA%\Programs\xx\bin.

Set XX_INSTALL_DIR to override both locations:

$env:XX_INSTALL_DIR = "$HOME\bin"
Invoke-WebRequest -UseBasicParsing https://withxx.dev/scripts/install_xx.ps1 | Select-Object -ExpandProperty Content | Invoke-Expression

If necessary, the installer adds the selected directory to the user PATH. Machine-wide PATH is not modified.

If no platform user directory can be determined, installation fails. Set XX_INSTALL_DIR to provide the destination explicitly.

First Project

From a project directory, initialize xx:

xx init

This creates .xx/main.star. Entrypoints live in .xx/ and can use .star, .build, .starlark, .bzl, or .bazel as their extension.

For a Go program in main.go, use:

load("@[email protected]", "go_binary")

go_binary(
    entry_point="main.go",
    flags=["-trimpath"],
)

Run the entrypoint by its base name:

xx run main

On first use, xx downloads the requested official Go SDK. It writes the binary to .xx/out/main (.xx/out/main.exe on Windows).

xx run exits 0 on success and 1 on failure. Pressing Ctrl-C cancels and drains running tasks, prints no interrupt diagnostic, and exits 130 unless another error is retained.

Install Cobra's generated shell completion with xx completion bash, xx completion zsh, xx completion fish, or xx completion powershell. Completion for xx run suggests unambiguous entrypoint names from .xx/; for example, xx run b completes to xx run build when build is the only matching entrypoint.

Project Secrets

See the builtin secret manager for its project scope, storage model, and task workflow.

Store a secret for the current project and retrieve it later:

xx secrets set TOKEN "secret value"
xx secrets get TOKEN

Omit the value to read it exactly from standard input:

pbpaste | xx secrets set STRIPE_WEBHOOK_SECRET

Piped or redirected standard input is stored unchanged, including any trailing newline. xx secrets get writes the stored value unchanged. When standard input is a terminal, provide the value argument instead.

List the keys stored for the current project without exposing their values:

xx secrets list

Each key is printed on its own line. Keys containing control characters, quotes, or backslashes are quoted and escaped.

Print the SQLite database location with xx secrets db.

Remove a secret from the current project with xx secrets rm TOKEN. The command fails if the key is not set.

Build definitions can load stored secrets into dependent task environments with xx_secrets.

Secrets are scoped to the canonical path of the current project. Values are encrypted in xx's operating-system application-data database, and the encryption key is kept in the operating-system keyring. Setting an existing key replaces its value.

The database is stored at $XDG_DATA_HOME/xx/secrets.db on Linux (or $HOME/.local/share/xx/secrets.db when XDG_DATA_HOME is unset), ~/Library/Application Support/xx/secrets.db on macOS, and %AppData%\xx\secrets.db on Windows.

The value argument passed to xx secrets set may remain visible in shell history or process inspection provided by the operating system. Use standard input to avoid exposing the value as a command-line argument. Quote argument values containing spaces or shell metacharacters.

Interactive Runs

xx run opens an interactive terminal UI when CI is empty or unset and both input and output are connected to a terminal. Setting CI to a non-empty value or redirecting input or output uses plain mode instead. In plain mode, command standard output and standard error are merged and streamed to standard output; commands do not receive input.

Interactive mode groups tasks under each requested entrypoint, preserving argument order and duplicates. Every built-in function invocation appears as a command or progress row, and tool setup may add another progress row. Commands are selectable; progress rows are not. Starlark print output gets its own row. Rows show compact labels such as [email protected] > go_binary: .xx/out/app and one of these states:

The first running command is selected automatically. Click any command row, including a completed command, to view its retained output. Progress rows are not selectable. The mouse wheel scrolls the task list or selected task output, depending on pointer position. Commands can use terminal colors and prompts.

Keyboard input and paste go only to the selected running command. Mouse input is not forwarded to commands, and task selection requires the mouse. Ctrl-C cancels the whole run rather than the selected command. xx restores the terminal and exits 130 unless another error occurred; errors exit 1 with a normal diagnostic.

After success, xx restores the terminal and prints every command and progress row in a compact summary grouped by entrypoint. After failure, it prints output from the selected failed task, or the first failed task with output, followed by the diagnostic. Pass -v or --verbose to print every task heading and its retained output instead of only one failed command after the UI closes. Verbose mode does not change plain-mode output. Task output is available only for the current run and retains up to 10,000 lines per task.

Where Next?

Project Status

xx is early software. Managed CMake, Go, Ninja, Node.js, Protobuf, and Zig packages support Windows x86-64, Windows ARM64, Linux x86-64, Linux ARM64, and macOS ARM64. Tool release availability can impose additional version-specific limits.

Source and issue tracking are on Codeberg. xx is available under the Zlib License.