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

Go has gained widespread adoption for cloud infrastructure, network services, and command-line tools. The Go toolchain includes built-in dependency management through Go modules, which downloads dependencies from version control systems at build time. Nixpkgs provides robust support for Go projects through helper functions that integrate the Go build system with Nix’s reproducibility requirements.

Go and Nix: special concerns

Go modules and dependency management

Go uses modules for dependency management, specified in go.mod with exact versions locked in go.sum. During a normal build, the Go toolchain downloads dependencies from their source repositories (GitHub, GitLab, etc.).

This conflicts with Nix’s offline, reproducible build model. Nix solves this by:

  1. Pre-fetching dependencies: Nix downloads all Go module dependencies before the build
  2. Local module cache: Dependencies are placed in a local cache that the Go toolchain uses
  3. Hash verification: A hash of all dependencies (vendorHash) ensures reproducibility

This means Go packages in Nix require an additional hash (vendorHash or vendorSha256) that covers all dependencies listed in go.sum.

The vendor directory alternative

Some Go projects include a vendor/ directory with all dependencies committed to the repository. For these projects, you can skip vendoring by setting vendorHash = null.

Go’s opinionated build system

Go has strong conventions about project structure and import paths:

  • Import paths typically match repository URLs (e.g., github.com/user/project)
  • The main package defines executable programs
  • Module path must match the repository structure

These conventions generally work well with Nix, but occasionally require workarounds for projects with non-standard layouts.

Basic Go package

Go packages in nixpkgs use buildGoModule. Here’s a minimal example:

{ lib, buildGoModule, fetchFromGitHub }:

buildGoModule rec {
  pname = "hugo";
  version = "0.121.0";

  src = fetchFromGitHub {
    owner = "gohugoio";
    repo = "hugo";
    rev = "v${version}";
    sha256 = "sha256-...";
  };

  vendorHash = "sha256-...";

  meta = with lib; {
    description = "A fast and modern static website engine";
    homepage = "https://gohugo.io/";
    license = licenses.asl20;
    mainProgram = "hugo";
  };
}

The buildGoModule function handles:

  • Setting up the Go toolchain
  • Fetching and caching module dependencies
  • Running go build
  • Installing binaries to $out/bin

Common Go build options

vendorHash (previously vendorSha256)

vendorHash (preferred, modern):

vendorHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";

To get the hash, set it to an empty string or fake hash and let Nix tell you the correct one:

vendorHash = "";  # or lib.fakeHash

For projects with vendored dependencies:

vendorHash = null;  # Use committed vendor/ directory

Controlling the build

Build specific packages:

# Build a specific subpackage
subPackages = [ "cmd/mytool" ];

# Build multiple subpackages
subPackages = [ "cmd/tool1" "cmd/tool2" ];

Disable Go module verification:

# Skip checksum verification (rarely needed)
proxyVendor = true;

Set build flags:

# Pass flags to go build
ldflags = [
  "-s"  # Strip debug symbols
  "-w"  # Strip DWARF symbols
  "-X main.version=${version}"  # Set variables
];

tags = [ "sqlite" "json1" ];  # Build tags

Testing

Go tests run by default during the check phase:

# Disable tests entirely
doCheck = false;

# Skip specific tests
checkFlags = [
  "-skip=TestRequiresNetwork"
];

# Exclude test packages
excludedPackages = [
  "integration"
  "e2e"
];

Module path and package name

For most projects, the module path is detected automatically from go.mod. Occasionally you need to override it:

# Override detected module path
modRoot = "subdir";

# For multi-module repositories
modRoot = "./sdk/go";

Common issues and fixes

Missing go.sum

If the source doesn’t include go.sum:

postPatch = ''
  # Generate go.sum if missing
  go mod tidy
'';

However, most projects should include go.sum in their repository.

Build-time dependencies

Some Go packages use cgo and need C libraries:

{ lib, buildGoModule, fetchFromGitHub, pkg-config, sqlite }:

buildGoModule rec {
  pname = "tool-with-cgo";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "example";
    repo = pname;
    rev = "v${version}";
    sha256 = "sha256-...";
  };

  vendorHash = "sha256-...";

  nativeBuildInputs = [ pkg-config ];
  buildInputs = [ sqlite ];

  meta = with lib; {
    description = "Tool using cgo with SQLite";
    license = licenses.mit;
  };
}

Tests require network or external resources

Disable problematic tests:

# Skip tests that need network
checkFlags = [
  "-skip=TestDownload|TestAPI"
];

# Or disable all tests
doCheck = false;

# Exclude entire test packages
excludedPackages = [ "integration" "e2e" ];

vendorHash changes frequently

If dependencies change often during development, use a variable:

vendorHash = "sha256-...";  # Update this when go.mod changes

Some projects use go.sum for vendoring which is more stable.

Subpackage builds

For projects with multiple commands:

# Build all binaries in cmd/
subPackages = [ "cmd/..." ];

# Build specific binaries
subPackages = [ "cmd/server" "cmd/client" ];

# Install with custom names
postInstall = ''
  mv $out/bin/server $out/bin/myserver
'';

Setting version information

Go projects often use linker flags to embed version info:

ldflags = [
  "-s" "-w"
  "-X main.version=${version}"
  "-X main.commit=${src.rev}"
  "-X main.date=1970-01-01T00:00:00Z"
];

CGO_ENABLED issues

Some packages need to enable or disable cgo explicitly:

# Disable cgo for static binaries
env.CGO_ENABLED = "0";

# Or enable it when needed
env.CGO_ENABLED = "1";
buildInputs = [ gcc ];

Module replace directives

Projects with replace directives in go.mod may need special handling:

postPatch = ''
  # Remove replace directives that point to local paths
  substituteInPlace go.mod \
    --replace "replace example.com/module => ../module" ""
'';

Cross-compilation

Go has excellent cross-compilation support:

# Cross-compile for different platforms
# Nix handles this automatically based on the target platform
# Just ensure CGO is disabled if cross-compiling
env.CGO_ENABLED = "0";

Detailed example

Here’s a comprehensive example of a Go CLI application with multiple binaries, cgo dependencies, and version embedding:

{ lib
, buildGoModule
, fetchFromGitHub
, pkg-config
, installShellFiles
, stdenv
, darwin
, sqlite
, zlib
}:

buildGoModule rec {
  pname = "example-go-tool";
  version = "3.2.0";

  src = fetchFromGitHub {
    owner = "example";
    repo = "go-tool";
    rev = "v${version}";
    hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
  };

  vendorHash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";

  nativeBuildInputs = [
    pkg-config
    installShellFiles
  ];

  buildInputs = [
    sqlite
    zlib
  ] ++ lib.optionals stdenv.isDarwin [
    darwin.apple_sdk.frameworks.Security
  ];

  # Build only the main CLI, not helper tools
  subPackages = [ "cmd/example-tool" ];

  # Build tags for optional features
  tags = [ "sqlite" "json1" ];

  # Set version info and optimization flags
  ldflags = [
    "-s"
    "-w"
    "-X main.version=${version}"
    "-X main.commit=${src.rev}"
    "-X main.buildDate=1970-01-01T00:00:00Z"
  ];

  # Skip tests that require network access
  checkFlags = [
    "-skip=TestHTTPClient|TestDownload"
  ];

  # Don't test integration packages
  excludedPackages = [
    "integration"
    "testutil"
  ];

  # Some tests need CGO
  preCheck = ''
    export CGO_ENABLED=1
  '';

  postInstall = ''
    # Rename binary if needed
    mv $out/bin/example-tool $out/bin/etool

    # Install shell completions
    installShellCompletion --cmd etool \
      --bash <($out/bin/etool completion bash) \
      --fish <($out/bin/etool completion fish) \
      --zsh <($out/bin/etool completion zsh)

    # Install man page if available
    if [ -f docs/etool.1 ]; then
      installManPage docs/etool.1
    fi
  '';

  meta = with lib; {
    description = "Example Go CLI tool for data processing";
    longDescription = ''
      A comprehensive command-line tool written in Go, demonstrating
      best practices for packaging Go applications in nixpkgs including
      cgo dependencies, build tags, version embedding, and shell completions.
    '';
    homepage = "https://github.com/example/go-tool";
    changelog = "https://github.com/example/go-tool/releases/tag/v${version}";
    license = licenses.mit;
    maintainers = with maintainers; [ ];
    mainProgram = "etool";
    platforms = platforms.unix;
  };
}

This example demonstrates:

  • Using buildGoModule for a CLI application
  • Cross-platform dependencies (macOS frameworks)
  • Native library dependencies via cgo (SQLite, zlib)
  • Selective binary building with subPackages
  • Build tags for conditional compilation
  • Version embedding with ldflags
  • Test filtering and exclusions
  • CGO configuration for tests
  • Post-install tasks (renaming, shell completions, man pages)
  • Comprehensive metadata

Building Go libraries

For Go libraries (not executables):

buildGoModule rec {
  pname = "go-library";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "example";
    repo = pname;
    rev = "v${version}";
    hash = "sha256-...";
  };

  vendorHash = "sha256-...";

  # Don't install any binaries
  subPackages = [ ];

  # Libraries should run tests
  doCheck = true;

  # Make source available for other packages
  postInstall = ''
    mkdir -p $out/share/go
    cp -r . $out/share/go/
  '';

  meta = with lib; {
    description = "A reusable Go library";
    license = licenses.asl20;
  };
}

Most Go libraries in nixpkgs are included for their binaries or as dependencies, not installed directly.

Using buildGo118Module and other variants

For projects requiring specific Go versions:

# Use Go 1.21 specifically
buildGo121Module rec {
  # ... same as buildGoModule
}

# For the latest Go version
buildGoModule rec {
  # Uses the default Go version for nixpkgs
}

However, buildGoModule typically uses a recent Go version and works for most projects.

Handling monorepos

For projects with multiple modules:

buildGoModule rec {
  pname = "tool-from-monorepo";
  version = "1.0.0";

  src = fetchFromGitHub {
    owner = "example";
    repo = "monorepo";
    rev = "v${version}";
    hash = "sha256-...";
  };

  # Specify subdirectory containing the module
  modRoot = "tools/mytool";

  vendorHash = "sha256-...";

  # Build from the module root
  subPackages = [ "." ];

  meta = with lib; {
    description = "Tool from a monorepo";
    license = licenses.mit;
  };
}

Summary

Go packaging in Nix leverages Go modules while maintaining reproducibility:

  • Use buildGoModule for most Go projects
  • Specify vendorHash to lock dependency versions (or null for vendored)
  • Use subPackages to build specific binaries from the project
  • Set ldflags for version embedding and optimization
  • Handle cgo dependencies through nativeBuildInputs and buildInputs
  • Use tags for conditional compilation features
  • Cross-platform dependencies should use conditionals for macOS frameworks
  • Check existing nixpkgs Go packages for patterns and solutions