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

Go Support

Turnkey provides comprehensive Go support with Buck2 integration.

Setup

Add to toolchain.toml:

[toolchains]
go = {}
godeps-gen = {}

Enable Go dependencies in flake.nix:

turnkey.toolchains.buck2.go = {
  enable = true;
  depsFile = ./go-deps.toml;
};

Project Structure

my-project/
├── go.mod
├── go.sum
├── go-deps.toml          # Generated from go.mod
├── cmd/
│   └── myapp/
│       ├── main.go
│       └── rules.star
└── pkg/
    └── mylib/
        ├── lib.go
        └── rules.star

Build Rules

In rules.star:

load("@prelude//go:go.bzl", "go_binary", "go_library")

go_binary(
    name = "myapp",
    srcs = ["main.go"],
    deps = ["//pkg/mylib:mylib"],
)

External Dependencies

Reference third-party packages via the godeps cell:

go_library(
    name = "mylib",
    srcs = ["lib.go"],
    deps = ["godeps//github.com/pkg/errors:errors"],
)

Auto-Sync

The go command is wrapped to auto-sync dependencies:

go get github.com/some/package  # Triggers sync
go mod tidy                      # Triggers sync

Local Replace Directives

Turnkey supports replace directives in go.mod that point to local paths within your monorepo. This is useful for:

  • Internal packages shared across multiple modules
  • Local development overrides
  • Monorepo setups with multiple Go modules

How It Works

  1. In go.mod, declare the local replacement:

    module github.com/company/myapp
    
    require github.com/company/shared-lib v1.0.0
    
    replace github.com/company/shared-lib => ../shared-lib
    
  2. Run godeps-gen to update go-deps.toml. The local replace will be output:

    [replace."github.com/company/shared-lib"]
    import_path = "github.com/company/shared-lib"
    local_path = "../shared-lib"
    
  3. Configure the target mapping in your flake.nix. You need to tell Turnkey which Buck2 target corresponds to each local replacement:

    turnkey.toolchains.buck2.go = {
      enable = true;
      depsFile = ./go-deps.toml;
      localReplaces = {
        # Map import path -> Buck2 target
        "github.com/company/shared-lib" = "//src/shared-lib:shared-lib";
      };
    };
    
  4. Write the local target's rules.star. The target must export the package with the correct import path:

    # src/shared-lib/rules.star
    go_library(
        name = "shared-lib",
        package_name = "github.com/company/shared-lib",
        srcs = glob(["*.go"]),
        visibility = ["PUBLIC"],
    )
    

Subpackages

Local replacements automatically handle subpackages. If you replace github.com/company/shared-lib, then imports of github.com/company/shared-lib/subpkg will resolve to //src/shared-lib/subpkg:subpkg.

Example: Monorepo Setup

my-monorepo/
├── go.mod                    # Root module with replace directives
├── go-deps.toml              # Generated by godeps-gen
├── flake.nix                 # Configure localReplaces here
├── src/
│   ├── app/
│   │   ├── main.go           # imports github.com/company/shared-lib
│   │   └── rules.star
│   └── shared-lib/
│       ├── lib.go            # The local replacement
│       ├── subpkg/
│       │   └── sub.go
│       └── rules.star

In rules.star for the app:

go_binary(
    name = "app",
    srcs = ["main.go"],
    deps = [
        "//src/shared-lib:shared-lib",  # Resolved from local replace
    ],
)

External Fork Replacements

Turnkey supports replace directives that point to external forks (not local paths). This is useful when:

  • Using a forked version of a dependency with bug fixes
  • Using a maintained fork of an abandoned project
  • Testing changes before upstreaming

How It Works

When godeps-gen encounters an external replace directive like:

replace github.com/original/pkg => github.com/myfork/pkg v1.2.3

It will:

  1. Set the dependency's import_path to github.com/original/pkg (for correct imports)
  2. Set the fetch_path to github.com/myfork/pkg (where to actually fetch from)
  3. Use the replacement version

In go.mod

module github.com/company/myapp

require github.com/original/pkg v1.0.0

replace github.com/original/pkg => github.com/myfork/pkg v1.2.3

Generated go-deps.toml

[deps."github.com/original/pkg@v1.2.3"]
import_path = "github.com/original/pkg"
fetch_path = "github.com/myfork/pkg"
version = "v1.2.3"
hash = "sha256-..."

How the Cell Builder Uses This

The Nix cell builder:

  1. Fetches the source from fetch_path (the fork)
  2. Stores it in the vendor directory under import_path (the original path)
  3. Generates Buck2 rules using the original import path

This means your code continues to import from the original path (github.com/original/pkg), but the actual source comes from your fork.

Version Handling

External replaces can change the version:

go.mod replaceResult
=> github.com/fork v1.2.3Uses v1.2.3 from fork
=> github.com/fork (no version)Uses the required version from fork

Version-specific replaces are also supported:

// Only replace v1.0.0, not other versions
replace github.com/pkg v1.0.0 => github.com/fork/pkg v1.0.1

Common Use Cases

Using a fork with a fix:

// Your fork has a critical bug fix not yet merged upstream
replace github.com/upstream/logger => github.com/you/logger v1.0.1-patched

Using a maintained fork:

// Original project abandoned, using community fork
replace github.com/old/abandoned => github.com/community/maintained v2.0.0

Testing before upstreaming:

// Test your changes before creating a PR
replace github.com/original/pkg => github.com/you/pkg v0.0.0-20240101