Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Project Setup

This guide covers how to create a new Turnkey project or add Turnkey to an existing project.

New Project

Create a new Buck2 project using the Turnkey flake template:

mkdir my-project && cd my-project
nix flake init -t github:firefly-engineering/turnkey
direnv allow  # If using direnv

This creates:

  • flake.nix - Nix flake configuration with Turnkey enabled
  • toolchain.toml - Toolchain declaration (Go enabled by default)
  • .envrc - direnv configuration with symlink sync
  • .gitignore - Ignores Turnkey-managed files
  • rules.star - Root build file (template)

Existing Project

Add Turnkey to an existing Nix flake project:

1. Add Turnkey Input to flake.nix

{
  inputs = {
    # ... existing inputs ...
    turnkey.url = "github:firefly-engineering/turnkey";
  };

  outputs = inputs@{ flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      imports = [
        inputs.devenv.flakeModule
        inputs.turnkey.flakeModules.turnkey
      ];

      # ... rest of config ...

      perSystem = { pkgs, ... }: {
        turnkey = {
          enable = true;
          declarationFile = ./toolchain.toml;
        };

        devenv.shells.default = {
          turnkey.buck2.enable = true;
        };
      };
    };
}

2. Create toolchain.toml

[toolchains]
go = {}
# Add more as needed: rust, python, cxx

3. Update .gitignore

# Turnkey managed files
.buckconfig
.buckroot
.turnkey/
buck-out/

4. Create .envrc (if using direnv)

Turnkey provides a direnv library that handles all symlink management automatically:

use flake . --no-pure-eval

# Source the turnkey library and activate
source "$TURNKEY_DIRENV_LIB"
use_turnkey

The use_turnkey function handles:

  • Buck2 symlink management (.buckconfig, the prelude and toolchains cells) and the deps cells' directories
  • Re-evaluating the flake when a deps file changed since its cells were built
  • watch_file declarations for automatic reloads
  • Optional dependency file regeneration

Then allow it:

direnv allow

Directory Structure

A typical Turnkey project has this structure:

my-project/
├── .buckconfig           # Buck2 configuration (generated symlink)
├── .buckroot             # Empty file marking project boundary
├── .envrc                # direnv configuration
├── .turnkey/             # Generated cells (gitignored)
│   ├── prelude/          # Buck2 prelude
│   ├── toolchains/       # Language toolchains
│   ├── godeps/           # Go dependency cell (if configured)
│   └── rustdeps/         # Rust dependency cell (if configured)
├── flake.nix             # Nix flake configuration
├── flake.lock            # Locked dependencies
├── toolchain.toml        # Toolchain declarations
├── go-deps.toml          # Go dependencies (if using Go)
├── rust-deps.toml        # Rust dependencies (if using Rust)
└── rules.star            # Root build file

Generated Files

When you enter the devenv shell, Turnkey generates:

FileDescription
.buckconfigSymlink to Nix-managed Buck2 configuration
.buckrootEmpty file marking project boundary
.turnkey/toolchainsSymlink to generated toolchains cell
.turnkey/godepsGo dependencies cell, a directory tk materialize maintains (if configured)
.turnkey/preludeSymlink to the prelude

The Prelude

The prelude is always turnkey's: the prelude built with turnkey's pinned buck2 release, with turnkey's patches and extensions applied. It isn't configurable, for the same reason buck2 itself isn't: they are one release (ADR 0002).

If you really need a prelude of your own, prelude.path takes a derivation or a path. That is off the supported path, and it turns test result caching off, since turnkey can't know which of that prelude's test rules are cache-safe:

turnkey.toolchains.buck2.prelude.path = ./my-prelude;

direnv Integration

For automatic environment activation with full Turnkey support, create .envrc:

use flake . --no-pure-eval

source "$TURNKEY_DIRENV_LIB"
use_turnkey

Then allow it:

direnv allow

use_turnkey runs tk sync to regenerate stale dependency files (the same rules as tk sync everywhere else, from .turnkey/sync.toml), keeps the cells current and watches the rules' files so that a changed deps file reloads the shell. When a deps file's content differs from the one the shell's cells were built from (regenerated by that tk sync, or earlier by tw or a tk sync of your own), it evaluates the flake again and loads the .envrc once more, so the cells are rebuilt in the same load. It takes options:

  • use_turnkey --skip-regen - Skip dependency file regeneration
  • use_turnkey --skip-sync - Skip symlink synchronization
  • use_turnkey --only-<rule> / --skip-<rule> - Sync only, or all but, the named deps rules (go, rust, pylock, python, javascript, solidity)
  • Environment variables like TURNKEY_SKIP_ALL=1 (with TURNKEY_ENABLE_<RULE>=1 to add rules back) or TURNKEY_SKIP_<RULE>=1

Buck2 Configuration

The .buckconfig is generated automatically. For project-specific settings, create .buckconfig.local:

[build]
# Custom build settings

[project]
# Project-specific settings

Verifying Setup

After entering the shell, verify Buck2 is configured:

# Check toolchains
buck2 targets toolchains//...

# Run Go via toolchain
buck2 run toolchains//:go[go] -- version

# Build a target
buck2 build //...

Common Issues

If .turnkey/ symlinks aren't created:

  1. Check that you're using direnv or manually sourcing the environment
  2. Verify environment variables are set:
    echo $TURNKEY_BUCK2_CONFIG
    echo $TURNKEY_BUCK2_TOOLCHAINS_CELL
    
  3. Re-allow direnv:
    direnv allow
    

Buck2 Can't Find Cells

If Buck2 reports missing cells:

  1. Check .buckconfig is a valid symlink:
    ls -la .buckconfig
    
  2. Verify cell paths in .buckconfig exist:
    cat .buckconfig
    
  3. Ensure you've entered the Nix shell:
    nix develop