Profpatsch / nix / buildGo

buildGo.nix

This is an alternative Nix build system for Go. It supports building Go libraries and programs.

Note: This will probably end up being folded into Nixery.

Background

Most language-specific Nix tooling outsources the build to existing language-specific build tooling, which essentially means that Nix ends up being a wrapper around all sorts of external build systems.

However, systems like Bazel take an alternative approach in which the compiler is invoked directly and the composition of programs and libraries stays within a single homogeneous build system.

Users don't need to learn per-language build systems and especially for companies with large monorepo-setups (like Google) this has huge productivity impact.

This project is an attempt to prove that Nix can be used in a similar style to build software directly, rather than shelling out to other build systems.

Example

Given a program layout like this:

.
├── lib          <-- some library component
│   ├── bar.go
│   └── foo.go
├── main.go      <-- program implementation
└── default.nix  <-- build instructions

The contents of default.nix could look like this:

{ buildGo }:

let
  lib = buildGo.package {
    name = "somelib";
    srcs = [
      ./lib/bar.go
      ./lib/foo.go
    ];
  };
in buildGo.program {
  name = "my-program";
  deps = [ lib ];

  srcs = [
    ./main.go
  ];
}

(If you don't know how to read Nix, check out nix-1p)

Usage

buildGo exposes five different functions:

Working with External Dependencies

For projects with multiple external dependencies, it's recommended to organize them in a separate go-deps.nix file:

# go-deps.nix
{ depot, pkgs, ... }:

rec {
  # Single-package repository - gopkg at root
  varlink-go = depot.nix.buildGo.external {
    path = "github.com/varlink/go";
    src = pkgs.fetchFromGitHub {
      owner = "varlink";
      repo = "go";
      rev = "ec4cb63960126b1102b3c578fa245c90614ad4f4";
      sha256 = "sha256-rIL/ka0mxxsddeEM0cf7HApeLFpLN/ljSP12od/HZCo=";
    };
  };

  # Multi-package repository - navigate to subpackages
  wazero = depot.nix.buildGo.external {
    path = "github.com/tetratelabs/wazero";
    src = pkgs.fetchFromGitHub {
      owner = "tetratelabs";
      repo = "wazero";
      rev = "v1.9.0";
      sha256 = "sha256-yxnHLc0PFxh8NRBgK2hvhKaxRM1w3IZ9TnfJM0+uadg=";
    };
  };
}

Then use them in your default.nix:

# default.nix
{ depot, pkgs, ... }:

let
  goDeps = import ./go-deps.nix { inherit depot pkgs; };
in
depot.nix.buildGo.program {
  name = "my-program";
  srcs = [ ./main.go ];
  deps = [
    goDeps.varlink-go.varlink      # Navigate to varlink subpackage
    goDeps.varlink-go.varlink.idl  # Navigate to idl subpackage
    goDeps.wazero                  # Root package
    goDeps.wazero.api              # api subpackage
  ];
}

go:embed Support

buildGo fully supports //go:embed directives for embedding files into Go binaries. The build system automatically detects embed directives using go list and generates the necessary embedcfg configuration for the compiler.

Example:

package main

import (
    _ "embed"
    "fmt"
)

//go:embed config.txt
var config string

func main() {
    fmt.Println(config)
}

This works transparently with both buildGo.program and buildGo.package. Embedded files must be present in the same directory as the source files that reference them.

Current status

This project is work-in-progress. It is still lacking the following features:

There are still some open questions around how to structure some of those features in Nix.