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
-
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 -
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" -
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"; }; }; -
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:
- Set the dependency's
import_pathtogithub.com/original/pkg(for correct imports) - Set the
fetch_pathtogithub.com/myfork/pkg(where to actually fetch from) - 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:
- Fetches the source from
fetch_path(the fork) - Stores it in the vendor directory under
import_path(the original path) - 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 replace | Result |
|---|---|
=> github.com/fork v1.2.3 | Uses 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