about summary refs log tree commit diff
path: root/nix
diff options
context:
space:
mode:
Diffstat (limited to 'nix')
-rw-r--r--nix/buildGo/.skip-subtree2
-rw-r--r--nix/buildGo/README.md140
-rw-r--r--nix/buildGo/default.nix128
-rw-r--r--nix/buildGo/example/default.nix47
-rw-r--r--nix/buildGo/example/lib.go9
-rw-r--r--nix/buildGo/example/main.go25
-rw-r--r--nix/buildGo/example/thing.proto10
-rw-r--r--nix/buildGo/external/default.nix95
-rw-r--r--nix/buildGo/external/main.go186
-rw-r--r--nix/buildGo/proto.nix84
-rw-r--r--nix/yants/README.md84
-rw-r--r--nix/yants/default.nix298
-rw-r--r--nix/yants/screenshots/enums.pngbin0 -> 41305 bytes
-rw-r--r--nix/yants/screenshots/functions.pngbin0 -> 32907 bytes
-rw-r--r--nix/yants/screenshots/nested-structs.pngbin0 -> 70264 bytes
-rw-r--r--nix/yants/screenshots/simple.pngbin0 -> 43010 bytes
-rw-r--r--nix/yants/screenshots/structs.pngbin0 -> 69499 bytes
-rw-r--r--nix/yants/tests/default.nix94
18 files changed, 1202 insertions, 0 deletions
diff --git a/nix/buildGo/.skip-subtree b/nix/buildGo/.skip-subtree
new file mode 100644
index 000000000000..8db1f814f653
--- /dev/null
+++ b/nix/buildGo/.skip-subtree
@@ -0,0 +1,2 @@
+Subdirectories of this folder should not be imported since they are
+internal to buildGo.nix and incompatible with readTree.
diff --git a/nix/buildGo/README.md b/nix/buildGo/README.md
new file mode 100644
index 000000000000..e84ede663bf8
--- /dev/null
+++ b/nix/buildGo/README.md
@@ -0,0 +1,140 @@
+buildGo.nix
+===========
+
+This is an alternative [Nix][] build system for [Go][]. It supports building Go
+libraries and programs, and even automatically generating Protobuf & gRPC
+libraries.
+
+*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
+├── api.proto    <-- gRPC API definition
+├── main.go      <-- program implementation
+└── default.nix  <-- build instructions
+```
+
+The contents of `default.nix` could look like this:
+
+```nix
+{ buildGo }:
+
+let
+  api = buildGo.grpc {
+    name  = "someapi";
+    proto = ./api.proto;
+  };
+
+  lib = buildGo.package {
+    name = "somelib";
+    srcs = [
+      ./lib/bar.go
+      ./lib/foo.go
+    ];
+  };
+in buildGo.program {
+  name = "my-program";
+  deps = [ api lib ];
+
+  srcs = [
+    ./main.go
+  ];
+}
+```
+
+(If you don't know how to read Nix, check out [nix-1p][])
+
+## Usage
+
+`buildGo` exposes five different functions:
+
+* `buildGo.program`: Build a Go binary out of the specified source files.
+
+  | parameter | type                    | use                                            | required? |
+  |-----------|-------------------------|------------------------------------------------|-----------|
+  | `name`    | `string`                | Name of the program (and resulting executable) | yes       |
+  | `srcs`    | `list<path>`            | List of paths to source files                  | yes       |
+  | `deps`    | `list<drv>`             | List of dependencies (i.e. other Go libraries) | no        |
+  | `x_defs`  | `attrs<string, string>` | Attribute set of linker vars (i.e. `-X`-flags) | no        |
+
+* `buildGo.package`: Build a Go library out of the specified source files.
+
+  | parameter | type         | use                                            | required? |
+  |-----------|--------------|------------------------------------------------|-----------|
+  | `name`    | `string`     | Name of the library (and resulting executable) | yes       |
+  | `srcs`    | `list<path>` | List of paths to source files                  | yes       |
+  | `deps`    | `list<drv>`  | List of dependencies (i.e. other Go libraries) | no        |
+  | `path`    | `string`     | Go import path for the resulting library       | no        |
+
+* `buildGo.external`: Build an externally defined Go library or program.
+
+  This function performs analysis on the supplied source code (which
+  can use the standard Go tooling layout) and creates a tree of all
+  the packages contained within.
+
+  This exists for compatibility with external libraries that were not
+  defined using buildGo.
+
+  | parameter | type           | use                                           | required? |
+  |-----------|----------------|-----------------------------------------------|-----------|
+  | `path`    | `string`       | Go import path for the resulting package      | yes       |
+  | `src`     | `path`         | Path to the source **directory**              | yes       |
+  | `deps`    | `list<drv>`    | List of dependencies (i.e. other Go packages) | no        |
+
+  For some examples of how `buildGo.external` is used, check out
+  [`proto.nix`](./proto.nix).
+
+* `buildGo.proto`: Build a Go library out of the specified Protobuf definition.
+
+  | parameter   | type        | use                                              | required? |
+  |-------------|-------------|--------------------------------------------------|-----------|
+  | `name`      | `string`    | Name for the resulting library                   | yes       |
+  | `proto`     | `path`      | Path to the Protobuf definition file             | yes       |
+  | `path`      | `string`    | Import path for the resulting Go library         | no        |
+  | `extraDeps` | `list<drv>` | Additional Go dependencies to add to the library | no        |
+
+* `buildGo.grpc`: Build a Go library out of the specified gRPC definition.
+
+  The parameters are identical to `buildGo.proto`.
+
+## Current status
+
+This project is work-in-progress. Crucially it is lacking the following features:
+
+* feature flag parity with Bazel's Go rules
+* documentation building
+* test execution
+
+There are still some open questions around how to structure some of those
+features in Nix.
+
+[Nix]: https://nixos.org/nix/
+[Go]: https://golang.org/
+[Nixery]: https://github.com/google/nixery
+[Bazel]: https://bazel.build/
+[like Google]: https://ai.google/research/pubs/pub45424
+[nix-1p]: https://github.com/tazjin/nix-1p
diff --git a/nix/buildGo/default.nix b/nix/buildGo/default.nix
new file mode 100644
index 000000000000..140cbf2d9d16
--- /dev/null
+++ b/nix/buildGo/default.nix
@@ -0,0 +1,128 @@
+# Copyright 2019 Google LLC.
+# SPDX-License-Identifier: Apache-2.0
+#
+# buildGo provides Nix functions to build Go packages in the style of Bazel's
+# rules_go.
+
+{ pkgs ? import <nixpkgs> {}
+, ... }:
+
+let
+  inherit (builtins)
+    attrNames
+    baseNameOf
+    dirOf
+    elemAt
+    filter
+    listToAttrs
+    map
+    match
+    readDir
+    replaceStrings
+    toString;
+
+  inherit (pkgs) lib go runCommand fetchFromGitHub protobuf symlinkJoin;
+
+  # Helpers for low-level Go compiler invocations
+  spaceOut = lib.concatStringsSep " ";
+
+  includeDepSrc = dep: "-I ${dep}";
+  includeSources = deps: spaceOut (map includeDepSrc deps);
+
+  includeDepLib = dep: "-L ${dep}";
+  includeLibs = deps: spaceOut (map includeDepLib deps);
+
+  srcBasename = src: elemAt (match "([a-z0-9]{32}\-)?(.*\.go)" (baseNameOf src)) 1;
+  srcCopy = path: src: "cp ${src} $out/${path}/${srcBasename src}";
+  srcList = path: srcs: lib.concatStringsSep "\n" (map (srcCopy path) srcs);
+
+  allDeps = deps: lib.unique (lib.flatten (deps ++ (map (d: d.goDeps) deps)));
+
+  xFlags = x_defs: spaceOut (map (k: "-X ${k}=${x_defs."${k}"}") (attrNames x_defs));
+
+  pathToName = p: replaceStrings ["/"] ["_"] (toString p);
+
+  # Add an `overrideGo` attribute to a function result that works
+  # similar to `overrideAttrs`, but is used specifically for the
+  # arguments passed to Go builders.
+  makeOverridable = f: orig: (f orig) // {
+    overrideGo = new: makeOverridable f (orig // (new orig));
+  };
+
+  # High-level build functions
+
+  # Build a Go program out of the specified files and dependencies.
+  program = { name, srcs, deps ? [], x_defs ? {} }:
+  let uniqueDeps = allDeps deps;
+  in runCommand name {} ''
+    ${go}/bin/go tool compile -o ${name}.a -trimpath=$PWD -trimpath=${go} ${includeSources uniqueDeps} ${spaceOut srcs}
+    mkdir -p $out/bin
+    ${go}/bin/go tool link -o $out/bin/${name} -buildid nix ${xFlags x_defs} ${includeLibs uniqueDeps} ${name}.a
+  '';
+
+  # Build a Go library assembled out of the specified files.
+  #
+  # This outputs both the sources and compiled binary, as both are
+  # needed when downstream packages depend on it.
+  package = { name, srcs, deps ? [], path ? name, sfiles ? [] }:
+  let
+    uniqueDeps = allDeps deps;
+
+    # The build steps below need to be executed conditionally for Go
+    # assembly if the analyser detected any *.s files.
+    #
+    # This is required for several popular packages (e.g. x/sys).
+    ifAsm = do: if sfiles == [] then "" else do;
+    asmBuild = ifAsm ''
+      ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -gensymabis -o ./symabis ${spaceOut sfiles}
+      ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -o ./asm.o ${spaceOut sfiles}
+    '';
+    asmLink = ifAsm "-symabis ./symabis -asmhdr $out/go_asm.h";
+    asmPack = ifAsm ''
+      ${go}/bin/go tool pack r $out/${path}.a ./asm.o
+    '';
+  in (runCommand "golib-${name}" {} ''
+    mkdir -p $out/${path}
+    ${srcList path (map (s: "${s}") srcs)}
+    ${asmBuild}
+    ${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -trimpath=$PWD -trimpath=${go} -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs}
+    ${asmPack}
+  '') // { goDeps = uniqueDeps; goImportPath = path; };
+
+  # Build a tree of Go libraries out of an external Go source
+  # directory that follows the standard Go layout and was not built
+  # with buildGo.nix.
+  #
+  # The derivation for each actual package will reside in an attribute
+  # named "gopkg", and an attribute named "gobin" for binaries.
+  external = import ./external { inherit pkgs program package; };
+
+  # Import support libraries needed for protobuf & gRPC support
+  protoLibs = import ./proto.nix {
+    inherit external;
+  };
+
+  # Build a Go library out of the specified protobuf definition.
+  proto = { name, proto, path ? name, extraDeps ? [] }: (makeOverridable package) {
+    inherit name path;
+    deps = [ protoLibs.goProto.proto.gopkg ] ++ extraDeps;
+    srcs = lib.singleton (runCommand "goproto-${name}.pb.go" {} ''
+      cp ${proto} ${baseNameOf proto}
+      ${protobuf}/bin/protoc --plugin=${protoLibs.goProto.protoc-gen-go.gopkg}/bin/protoc-gen-go \
+        --go_out=plugins=grpc,import_path=${baseNameOf path}:. ${baseNameOf proto}
+      mv *.pb.go $out
+    '');
+  };
+
+  # Build a Go library out of the specified gRPC definition.
+  grpc = args: proto (args // { extraDeps = [ protoLibs.goGrpc.gopkg ]; });
+
+in {
+  # Only the high-level builder functions are exposed, but made
+  # overrideable.
+  program = makeOverridable program;
+  package = makeOverridable package;
+  proto = makeOverridable proto;
+  grpc = makeOverridable grpc;
+  external = makeOverridable external;
+}
diff --git a/nix/buildGo/example/default.nix b/nix/buildGo/example/default.nix
new file mode 100644
index 000000000000..5abed1fbbcb5
--- /dev/null
+++ b/nix/buildGo/example/default.nix
@@ -0,0 +1,47 @@
+# Copyright 2019 Google LLC.
+# SPDX-License-Identifier: Apache-2.0
+
+# This file provides examples for how to use the various builder
+# functions provided by `buildGo`.
+#
+# The features used in the example are not exhaustive, but should give
+# users a quick introduction to how to use buildGo.
+
+let
+  buildGo = import ../buildGo.nix {};
+
+  # Example use of buildGo.package, which creates an importable Go
+  # package from the specified source files.
+  examplePackage = buildGo.package {
+    name = "example";
+    srcs = [
+      ./lib.go
+    ];
+  };
+
+  # Example use of buildGo.proto, which generates a Go library from a
+  # Protobuf definition file.
+  exampleProto = buildGo.proto {
+    name = "exampleproto";
+    proto = ./thing.proto;
+  };
+
+  # Example use of buildGo.program, which builds an executable using
+  # the specified name and dependencies (which in turn must have been
+  # created via buildGo.package etc.)
+in buildGo.program {
+  name = "example";
+
+  srcs = [
+    ./main.go
+  ];
+
+  deps = [
+    examplePackage
+    exampleProto
+  ];
+
+  x_defs = {
+    "main.Flag" = "successfully";
+  };
+}
diff --git a/nix/buildGo/example/lib.go b/nix/buildGo/example/lib.go
new file mode 100644
index 000000000000..8a61370e994c
--- /dev/null
+++ b/nix/buildGo/example/lib.go
@@ -0,0 +1,9 @@
+// Copyright 2019 Google LLC.
+// SPDX-License-Identifier: Apache-2.0
+
+package example
+
+// UUID returns a totally random, carefully chosen UUID
+func UUID() string {
+	return "3640932f-ad40-4bc9-b45d-f504a0f5910a"
+}
diff --git a/nix/buildGo/example/main.go b/nix/buildGo/example/main.go
new file mode 100644
index 000000000000..bbcedbff8726
--- /dev/null
+++ b/nix/buildGo/example/main.go
@@ -0,0 +1,25 @@
+// Copyright 2019 Google LLC.
+// SPDX-License-Identifier: Apache-2.0
+//
+// Package main provides a tiny example program for the Bazel-style
+// Nix build system for Go.
+
+package main
+
+import (
+	"example"
+	"exampleproto"
+	"fmt"
+)
+
+var Flag string = "unsuccessfully"
+
+func main() {
+	thing := exampleproto.Thing{
+		Id:          example.UUID(),
+		KindOfThing: "test thing",
+	}
+
+	fmt.Printf("The thing is a %s with ID %q\n", thing.Id, thing.KindOfThing)
+	fmt.Printf("The flag has been %s set\n", Flag)
+}
diff --git a/nix/buildGo/example/thing.proto b/nix/buildGo/example/thing.proto
new file mode 100644
index 000000000000..0cb34124dfb9
--- /dev/null
+++ b/nix/buildGo/example/thing.proto
@@ -0,0 +1,10 @@
+// Copyright 2019 Google LLC.
+// SPDX-License-Identifier: Apache-2.0
+
+syntax = "proto3";
+package exampleProto;
+
+message Thing {
+  string id = 1;
+  string kind_of_thing = 2;
+}
diff --git a/nix/buildGo/external/default.nix b/nix/buildGo/external/default.nix
new file mode 100644
index 000000000000..48f678688eec
--- /dev/null
+++ b/nix/buildGo/external/default.nix
@@ -0,0 +1,95 @@
+# Copyright 2019 Google LLC.
+# SPDX-License-Identifier: Apache-2.0
+{ pkgs, program, package }:
+
+let
+  inherit (builtins)
+    elemAt
+    foldl'
+    fromJSON
+    head
+    length
+    listToAttrs
+    readFile
+    replaceStrings
+    tail
+    throw;
+
+  inherit (pkgs) lib runCommand go jq ripgrep;
+
+  pathToName = p: replaceStrings ["/"] ["_"] (toString p);
+
+  # Collect all non-vendored dependencies from the Go standard library
+  # into a file that can be used to filter them out when processing
+  # dependencies.
+  stdlibPackages = runCommand "stdlib-pkgs.json" {} ''
+    export HOME=$PWD
+    export GOPATH=/dev/null
+    ${go}/bin/go list all | \
+      ${ripgrep}/bin/rg -v 'vendor' | \
+      ${jq}/bin/jq -R '.' | \
+      ${jq}/bin/jq -c -s 'map({key: ., value: true}) | from_entries' \
+      > $out
+  '';
+
+  analyser = program {
+    name = "analyser";
+
+    srcs = [
+      ./main.go
+    ];
+
+    x_defs = {
+      "main.stdlibList" = "${stdlibPackages}";
+    };
+  };
+
+  mkset = path: value:
+    if path == [] then { gopkg = value; }
+    else { "${head path}" = mkset (tail path) value; };
+
+  last = l: elemAt l ((length l) - 1);
+
+  toPackage = self: src: path: depMap: entry:
+    let
+      localDeps = map (d: lib.attrByPath (d ++ [ "gopkg" ]) (
+        throw "missing local dependency '${lib.concatStringsSep "." d}' in '${path}'"
+      ) self) entry.localDeps;
+
+      foreignDeps = map (d: lib.attrByPath [ d ] (
+        throw "missing foreign dependency '${d}' in '${path}'"
+      ) depMap) entry.foreignDeps;
+
+      args = {
+        srcs = map (f: src + ("/" + f)) entry.files;
+        deps = localDeps ++ foreignDeps;
+      };
+
+      libArgs = args // {
+        name = pathToName entry.name;
+        path = lib.concatStringsSep "/" ([ path ] ++ entry.locator);
+        sfiles = map (f: src + ("/" + f)) entry.sfiles;
+      };
+
+      binArgs = args // {
+        name = (last ((lib.splitString "/" path) ++ entry.locator));
+      };
+    in if entry.isCommand then (program binArgs) else (package libArgs);
+
+in { src, path, deps ? [] }: let
+  # Build a map of dependencies (from their import paths to their
+  # derivation) so that they can be conditionally imported only in
+  # sub-packages that require them.
+  depMap = listToAttrs (map (d: {
+    name = d.goImportPath;
+    value = d;
+  }) deps);
+
+  name = pathToName path;
+  analysisOutput = runCommand "${name}-structure.json" {} ''
+    ${analyser}/bin/analyser -path ${path} -source ${src} > $out
+  '';
+  analysis = fromJSON (readFile analysisOutput);
+in lib.fix(self: foldl' lib.recursiveUpdate {} (
+  map (entry: mkset entry.locator (toPackage self src path depMap entry)) analysis
+))
diff --git a/nix/buildGo/external/main.go b/nix/buildGo/external/main.go
new file mode 100644
index 000000000000..aa4a813d32bd
--- /dev/null
+++ b/nix/buildGo/external/main.go
@@ -0,0 +1,186 @@
+// Copyright 2019 Google LLC.
+// SPDX-License-Identifier: Apache-2.0
+
+// This tool analyses external (i.e. not built with `buildGo.nix`) Go
+// packages to determine a build plan that Nix can import.
+package main
+
+import (
+	"encoding/json"
+	"flag"
+	"fmt"
+	"go/build"
+	"io/ioutil"
+	"log"
+	"os"
+	"path"
+	"path/filepath"
+	"strings"
+)
+
+// Path to a JSON file describing all standard library import paths.
+// This file is generated and set here by Nix during the build
+// process.
+var stdlibList string
+
+// pkg describes a single Go package within the specified source
+// directory.
+//
+// Return information includes the local (relative from project root)
+// and external (none-stdlib) dependencies of this package.
+type pkg struct {
+	Name        string     `json:"name"`
+	Locator     []string   `json:"locator"`
+	Files       []string   `json:"files"`
+	SFiles      []string   `json:"sfiles"`
+	LocalDeps   [][]string `json:"localDeps"`
+	ForeignDeps []string   `json:"foreignDeps"`
+	IsCommand   bool       `json:"isCommand"`
+}
+
+// findGoDirs returns a filepath.WalkFunc that identifies all
+// directories that contain Go source code in a certain tree.
+func findGoDirs(at string) ([]string, error) {
+	dirSet := make(map[string]bool)
+
+	err := filepath.Walk(at, func(path string, info os.FileInfo, err error) error {
+		name := info.Name()
+		// Skip folders that are guaranteed to not be relevant
+		if info.IsDir() && (name == "testdata" || name == ".git") {
+			return filepath.SkipDir
+		}
+
+		// If the current file is a Go file, then the directory is popped
+		// (i.e. marked as a Go directory).
+		if !info.IsDir() && strings.HasSuffix(name, ".go") && !strings.HasSuffix(name, "_test.go") {
+			dirSet[filepath.Dir(path)] = true
+		}
+
+		return nil
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	goDirs := []string{}
+	for k, _ := range dirSet {
+		goDirs = append(goDirs, k)
+	}
+
+	return goDirs, nil
+}
+
+// analysePackage loads and analyses the imports of a single Go
+// package, returning the data that is required by the Nix code to
+// generate a derivation for this package.
+func analysePackage(root, source, importpath string, stdlib map[string]bool) (pkg, error) {
+	ctx := build.Default
+	ctx.CgoEnabled = false
+
+	p, err := ctx.ImportDir(source, build.IgnoreVendor)
+	if err != nil {
+		return pkg{}, err
+	}
+
+	local := [][]string{}
+	foreign := []string{}
+
+	for _, i := range p.Imports {
+		if stdlib[i] {
+			continue
+		}
+
+		if i == importpath {
+			local = append(local, []string{})
+		} else if strings.HasPrefix(i, importpath) {
+			local = append(local, strings.Split(strings.TrimPrefix(i, importpath+"/"), "/"))
+		} else {
+			foreign = append(foreign, i)
+		}
+	}
+
+	prefix := strings.TrimPrefix(source, root+"/")
+
+	locator := []string{}
+	if len(prefix) != len(source) {
+		locator = strings.Split(prefix, "/")
+	} else {
+		// Otherwise, the locator is empty since its the root package and
+		// no prefix should be added to files.
+		prefix = ""
+	}
+
+	files := []string{}
+	for _, f := range p.GoFiles {
+		files = append(files, path.Join(prefix, f))
+	}
+
+	sfiles := []string{}
+	for _, f := range p.SFiles {
+		sfiles = append(sfiles, path.Join(prefix, f))
+	}
+
+	return pkg{
+		Name:        path.Join(importpath, prefix),
+		Locator:     locator,
+		Files:       files,
+		SFiles:      sfiles,
+		LocalDeps:   local,
+		ForeignDeps: foreign,
+		IsCommand:   p.IsCommand(),
+	}, nil
+}
+
+func loadStdlibPkgs(from string) (pkgs map[string]bool, err error) {
+	f, err := ioutil.ReadFile(from)
+	if err != nil {
+		return
+	}
+
+	err = json.Unmarshal(f, &pkgs)
+	return
+}
+
+func main() {
+	source := flag.String("source", "", "path to directory with sources to process")
+	path := flag.String("path", "", "import path for the package")
+
+	flag.Parse()
+
+	if *source == "" {
+		log.Fatalf("-source flag must be specified")
+	}
+
+	stdlibPkgs, err := loadStdlibPkgs(stdlibList)
+	if err != nil {
+		log.Fatalf("failed to load standard library index from %q: %s\n", stdlibList, err)
+	}
+
+	goDirs, err := findGoDirs(*source)
+	if err != nil {
+		log.Fatalf("failed to walk source directory '%s': %s\n", source, err)
+	}
+
+	all := []pkg{}
+	for _, d := range goDirs {
+		analysed, err := analysePackage(*source, d, *path, stdlibPkgs)
+
+		// If the Go source analysis returned "no buildable Go files",
+		// that directory should be skipped.
+		//
+		// This might be due to `+build` flags on the platform and other
+		// reasons (such as test files).
+		if _, ok := err.(*build.NoGoError); ok {
+			continue
+		}
+
+		if err != nil {
+			log.Fatalf("failed to analyse package at %q: %s", d, err)
+		}
+		all = append(all, analysed)
+	}
+
+	j, _ := json.Marshal(all)
+	fmt.Println(string(j))
+}
diff --git a/nix/buildGo/proto.nix b/nix/buildGo/proto.nix
new file mode 100644
index 000000000000..2ece948ebd84
--- /dev/null
+++ b/nix/buildGo/proto.nix
@@ -0,0 +1,84 @@
+# Copyright 2019 Google LLC.
+# SPDX-License-Identifier: Apache-2.0
+#
+# This file provides derivations for the dependencies of a gRPC
+# service in Go.
+
+{ external }:
+
+let
+  inherit (builtins) fetchGit map;
+in rec {
+  goProto = external {
+    path = "github.com/golang/protobuf";
+    src = fetchGit {
+      url = "https://github.com/golang/protobuf";
+      rev = "ed6926b37a637426117ccab59282c3839528a700";
+    };
+  };
+
+  xnet = external {
+    path = "golang.org/x/net";
+
+    src = fetchGit {
+      url = "https://go.googlesource.com/net";
+      rev = "ffdde105785063a81acd95bdf89ea53f6e0aac2d";
+    };
+
+    deps = map (p: p.gopkg) [
+      xtext.secure.bidirule
+      xtext.unicode.bidi
+      xtext.unicode.norm
+    ];
+  };
+
+  xsys = external {
+    path = "golang.org/x/sys";
+    src = fetchGit {
+      url = "https://go.googlesource.com/sys";
+      rev = "bd437916bb0eb726b873ee8e9b2dcf212d32e2fd";
+    };
+  };
+
+  xtext = external {
+    path = "golang.org/x/text";
+    src = fetchGit {
+      url = "https://go.googlesource.com/text";
+      rev = "cbf43d21aaebfdfeb81d91a5f444d13a3046e686";
+    };
+  };
+
+  genproto = external {
+    path = "google.golang.org/genproto";
+    src = fetchGit {
+      url = "https://github.com/google/go-genproto";
+      rev = "83cc0476cb11ea0da33dacd4c6354ab192de6fe6";
+    };
+
+    deps = with goProto; map (p: p.gopkg) [
+      proto
+      ptypes.any
+    ];
+  };
+
+  goGrpc = external {
+    path = "google.golang.org/grpc";
+    deps = map (p: p.gopkg) ([
+      xnet.trace
+      xnet.http2
+      xsys.unix
+      xnet.http2.hpack
+      genproto.googleapis.rpc.status
+    ] ++ (with goProto; [
+      proto
+      ptypes
+      ptypes.duration
+      ptypes.timestamp
+    ]));
+
+    src = fetchGit {
+      url = "https://github.com/grpc/grpc-go";
+      rev = "d8e3da36ac481ef00e510ca119f6b68177713689";
+    };
+  };
+}
diff --git a/nix/yants/README.md b/nix/yants/README.md
new file mode 100644
index 000000000000..5d551e5a49ab
--- /dev/null
+++ b/nix/yants/README.md
@@ -0,0 +1,84 @@
+yants
+=====
+
+This is a tiny type-checker for data in Nix, written in Nix.
+
+# Features
+
+* Checking of primitive types (`int`, `string` etc.)
+* Checking polymorphic types (`option`, `list`, `either`)
+* Defining & checking struct/record types
+* Defining & matching enum types
+* Defining & matching sum types
+* Defining function signatures (including curried functions)
+* Types are composable! `option string`! `list (either int (option float))`!
+* Type errors also compose!
+
+Currently lacking:
+
+* Any kind of inference
+* Convenient syntax for attribute-set function signatures
+
+## Primitives & simple polymorphism
+
+![simple](screenshots/simple.png)
+
+## Structs
+
+![structs](screenshots/structs.png)
+
+## Nested structs!
+
+![nested structs](screenshots/nested-structs.png)
+
+## Enums!
+
+![enums](screenshots/enums.png)
+
+## Functions!
+
+![functions](screenshots/functions.png)
+
+# Usage
+
+Yants can be imported from its `default.nix`. A single attribute (`lib`) can be
+passed, which will otherwise be imported from `<nixpkgs>`.
+
+Examples for the most common import methods would be:
+
+1. Import into scope with `with`:
+    ```nix
+    with (import ./default.nix {});
+    # ... Nix code that uses yants ...
+    ```
+
+2. Import as a named variable:
+    ```nix
+    let yants = import ./default.nix {};
+    in yants.string "foo" # or other uses ...
+    ````
+
+3. Overlay into `pkgs.lib`:
+    ```nix
+    # wherever you import your package set (e.g. from <nixpkgs>):
+    import <nixpkgs> {
+      overlays = [
+        (self: super: {
+          lib = super.lib // { yants = import ./default.nix { inherit (super) lib; }; };
+        })
+      ];
+    }
+
+    # yants now lives at lib.yants, besides the other library functions!
+    ```
+
+Please see my [Nix one-pager](https://github.com/tazjin/nix-1p) for more generic
+information about the Nix language and what the above constructs mean.
+
+# Stability
+
+The current API of Yants is **not yet** considered stable, but it works fine and
+should continue to do so even if used at an older version.
+
+Yants' tests use Nix versions above 2.2 - compatibility with older versions is
+not guaranteed.
diff --git a/nix/yants/default.nix b/nix/yants/default.nix
new file mode 100644
index 000000000000..aacc156b433b
--- /dev/null
+++ b/nix/yants/default.nix
@@ -0,0 +1,298 @@
+# Copyright 2019 Google LLC
+# SPDX-License-Identifier: Apache-2.0
+#
+# Provides a "type-system" for Nix that provides various primitive &
+# polymorphic types as well as the ability to define & check records.
+#
+# All types (should) compose as expected.
+
+{ lib ?  (import <nixpkgs> {}).lib, ... }:
+
+with builtins; let
+  prettyPrint = lib.generators.toPretty {};
+
+  # typedef' :: struct {
+  #   name = string;
+  #   checkType = function; (a -> result)
+  #   checkToBool = option function; (result -> bool)
+  #   toError = option function; (a -> result -> string)
+  #   def = option any;
+  #   match = option function;
+  # } -> type
+  #           -> (a -> b)
+  #           -> (b -> bool)
+  #           -> (a -> b -> string)
+  #           -> type
+  #
+  # This function creates an attribute set that acts as a type.
+  #
+  # It receives a type name, a function that is used to perform a
+  # check on an arbitrary value, a function that can translate the
+  # return of that check to a boolean that informs whether the value
+  # is type-conformant, and a function that can construct error
+  # messages from the check result.
+  #
+  # This function is the low-level primitive used to create types. For
+  # many cases the higher-level 'typedef' function is more appropriate.
+  typedef' = { name, checkType
+             , checkToBool ? (result: result.ok)
+             , toError ? (_: result: result.err)
+             , def ? null
+             , match ? null }: {
+    inherit name checkToBool toError;
+
+    # check :: a -> bool
+    #
+    # This function is used to determine whether a given type is
+    # conformant.
+    check = value: checkToBool (checkType value);
+
+    # checkType :: a -> struct { ok = bool; err = option string; }
+    #
+    # This function checks whether the passed value is type conformant
+    # and returns an optional type error string otherwise.
+    inherit checkType;
+
+    # __functor :: a -> a
+    #
+    # This function checks whether the passed value is type conformant
+    # and throws an error if it is not.
+    #
+    # The name of this function is a special attribute in Nix that
+    # makes it possible to execute a type attribute set like a normal
+    # function.
+    __functor = self: value:
+    let result = self.checkType value;
+    in if checkToBool result then value
+       else throw (toError value result);
+  };
+
+  typeError = type: val:
+  "expected type '${type}', but value '${prettyPrint val}' is of type '${typeOf val}'";
+
+  # typedef :: string -> (a -> bool) -> type
+  #
+  # typedef is the simplified version of typedef' which uses a default
+  # error message constructor.
+  typedef = name: check: typedef' {
+    inherit name;
+    checkType = check;
+    checkToBool = r: r;
+    toError = value: _result: typeError name value;
+  };
+
+  checkEach = name: t: l: foldl' (acc: e:
+    let res = t.checkType e;
+        isT = t.checkToBool res;
+    in {
+      ok = acc.ok && isT;
+      err = if isT
+        then acc.err
+        else acc.err + "${prettyPrint e}: ${t.toError e res}\n";
+    }) { ok = true; err = "expected type ${name}, but found:\n"; } l;
+in lib.fix (self: {
+  # Primitive types
+  any      = typedef "any" (_: true);
+  int      = typedef "int" isInt;
+  bool     = typedef "bool" isBool;
+  float    = typedef "float" isFloat;
+  string   = typedef "string" isString;
+  path     = typedef "path" (x: typeOf x == "path");
+  drv      = typedef "derivation" (x: isAttrs x && x ? "type" && x.type == "derivation");
+  function = typedef "function" (x: isFunction x || (isAttrs x && x ? "__functor"
+                                                 && isFunction x.__functor));
+
+  # Type for types themselves. Useful when defining polymorphic types.
+  type = typedef "type" (x:
+    isAttrs x
+    && hasAttr "name" x && self.string.check x.name
+    && hasAttr "checkType" x && self.function.check x.checkType
+    && hasAttr "checkToBool" x && self.function.check x.checkToBool
+    && hasAttr "toError" x && self.function.check x.toError
+  );
+
+  # Polymorphic types
+  option = t: typedef' rec {
+    name = "option<${t.name}>";
+    checkType = v:
+      let res = t.checkType v;
+      in {
+        ok = isNull v || (self.type t).checkToBool res;
+        err = "expected type ${name}, but value does not conform to '${t.name}': "
+         + t.toError v res;
+      };
+  };
+
+  eitherN = tn: typedef "either<${concatStringsSep ", " (map (x: x.name) tn)}>"
+    (x: any (t: (self.type t).check x) tn);
+
+  either = t1: t2: self.eitherN [ t1 t2 ];
+
+  list = t: typedef' rec {
+    name = "list<${t.name}>";
+
+    checkType = v: if isList v
+      then checkEach name (self.type t) v
+      else {
+        ok = false;
+        err = typeError name v;
+      };
+  };
+
+  attrs = t: typedef' rec {
+    name = "attrs<${t.name}>";
+
+    checkType = v: if isAttrs v
+      then checkEach name (self.type t) (attrValues v)
+      else {
+        ok = false;
+        err = typeError name v;
+      };
+  };
+
+  # Structs / record types
+  #
+  # Checks that all fields match their declared types, no optional
+  # fields are missing and no unexpected fields occur in the struct.
+  #
+  # Anonymous structs are supported (e.g. for nesting) by omitting the
+  # name.
+  #
+  # TODO: Support open records?
+  struct =
+    # Struct checking is more involved than the simpler types above.
+    # To make the actual type definition more readable, several
+    # helpers are defined below.
+    let
+      # checkField checks an individual field of the struct against
+      # its definition and creates a typecheck result. These results
+      # are aggregated during the actual checking.
+      checkField = def: name: value: let result = def.checkType value; in rec {
+        ok = def.checkToBool result;
+        err = if !ok && isNull value
+          then "missing required ${def.name} field '${name}'\n"
+          else "field '${name}': ${def.toError value result}\n";
+      };
+
+      # checkExtraneous determines whether a (closed) struct contains
+      # any fields that are not part of the definition.
+      checkExtraneous = def: has: acc:
+        if (length has) == 0 then acc
+        else if (hasAttr (head has) def)
+          then checkExtraneous def (tail has) acc
+          else checkExtraneous def (tail has) {
+            ok = false;
+            err = acc.err + "unexpected struct field '${head has}'\n";
+          };
+
+      # checkStruct combines all structure checks and creates one
+      # typecheck result from them
+      checkStruct = def: value:
+        let
+          init = { ok = true; err = ""; };
+          extraneous = checkExtraneous def (attrNames value) init;
+
+          checkedFields = map (n:
+            let v = if hasAttr n value then value."${n}" else null;
+            in checkField def."${n}" n v) (attrNames def);
+
+          combined = foldl' (acc: res: {
+            ok = acc.ok && res.ok;
+            err = if !res.ok then acc.err + res.err else acc.err;
+          }) init checkedFields;
+        in {
+          ok = combined.ok && extraneous.ok;
+          err = combined.err + extraneous.err;
+        };
+
+      struct' = name: def: typedef' {
+        inherit name def;
+        checkType = value: if isAttrs value
+          then (checkStruct (self.attrs self.type def) value)
+          else { ok = false; err = typeError name value; };
+
+          toError = _: result: "expected '${name}'-struct, but found:\n" + result.err;
+      };
+    in arg: if isString arg then (struct' arg) else (struct' "anon" arg);
+
+  # Enums & pattern matching
+  enum =
+  let
+    plain = name: def: typedef' {
+      inherit name def;
+
+      checkType = (x: isString x && elem x def);
+      checkToBool = x: x;
+      toError = value: _: "'${prettyPrint value} is not a member of enum ${name}";
+    };
+    enum' = name: def: lib.fix (e: (plain name def) // {
+      match = x: actions: deepSeq (map e (attrNames actions)) (
+      let
+        actionKeys = attrNames actions;
+        missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] def;
+      in if (length missing) > 0
+        then throw "Missing match action for members: ${prettyPrint missing}"
+        else actions."${e x}");
+    });
+  in arg: if isString arg then (enum' arg) else (enum' "anon" arg);
+
+  # Sum types
+  #
+  # The representation of a sum type is an attribute set with only one
+  # value, where the key of the value denotes the variant of the type.
+  sum =
+  let
+    plain = name: def: typedef' {
+      inherit name def;
+      checkType = (x:
+        let variant = elemAt (attrNames x) 0;
+        in if isAttrs x && length (attrNames x) == 1 && hasAttr variant def
+          then let t = def."${variant}";
+                   v = x."${variant}";
+                   res = t.checkType v;
+               in if t.checkToBool res
+                  then { ok = true; }
+                  else {
+                    ok = false;
+                    err = "while checking '${name}' variant '${variant}': "
+                          + t.toError v res;
+                  }
+          else { ok = false; err = typeError name x; }
+      );
+    };
+    sum' = name: def: lib.fix (s: (plain name def) // {
+    match = x: actions:
+    let variant = deepSeq (s x) (elemAt (attrNames x) 0);
+        actionKeys = attrNames actions;
+        defKeys = attrNames def;
+        missing = foldl' (m: k: if (elem k actionKeys) then m else m ++ [ k ]) [] defKeys;
+    in if (length missing) > 0
+      then throw "Missing match action for variants: ${prettyPrint missing}"
+      else actions."${variant}" x."${variant}";
+    });
+    in arg: if isString arg then (sum' arg) else (sum' "anon" arg);
+
+  # Typed function definitions
+  #
+  # These definitions wrap the supplied function in type-checking
+  # forms that are evaluated when the function is called.
+  #
+  # Note that typed functions themselves are not types and can not be
+  # used to check values for conformity.
+  defun =
+    let
+      mkFunc = sig: f: {
+        inherit sig;
+        __toString = self: foldl' (s: t: "${s} -> ${t.name}")
+                                  "λ :: ${(head self.sig).name}" (tail self.sig);
+        __functor = _: f;
+      };
+
+      defun' = sig: func: if length sig > 2
+        then mkFunc sig (x: defun' (tail sig) (func ((head sig) x)))
+        else mkFunc sig (x: ((head (tail sig)) (func ((head sig) x))));
+
+    in sig: func: if length sig < 2
+      then (throw "Signature must at least have two types (a -> b)")
+      else defun' sig func;
+})
diff --git a/nix/yants/screenshots/enums.png b/nix/yants/screenshots/enums.png
new file mode 100644
index 000000000000..71673e7ab63c
--- /dev/null
+++ b/nix/yants/screenshots/enums.png
Binary files differdiff --git a/nix/yants/screenshots/functions.png b/nix/yants/screenshots/functions.png
new file mode 100644
index 000000000000..30ed50f8327b
--- /dev/null
+++ b/nix/yants/screenshots/functions.png
Binary files differdiff --git a/nix/yants/screenshots/nested-structs.png b/nix/yants/screenshots/nested-structs.png
new file mode 100644
index 000000000000..6b03ed65ceb7
--- /dev/null
+++ b/nix/yants/screenshots/nested-structs.png
Binary files differdiff --git a/nix/yants/screenshots/simple.png b/nix/yants/screenshots/simple.png
new file mode 100644
index 000000000000..05a302cc6b9d
--- /dev/null
+++ b/nix/yants/screenshots/simple.png
Binary files differdiff --git a/nix/yants/screenshots/structs.png b/nix/yants/screenshots/structs.png
new file mode 100644
index 000000000000..fcbcf6415fad
--- /dev/null
+++ b/nix/yants/screenshots/structs.png
Binary files differdiff --git a/nix/yants/tests/default.nix b/nix/yants/tests/default.nix
new file mode 100644
index 000000000000..ae144db45a6e
--- /dev/null
+++ b/nix/yants/tests/default.nix
@@ -0,0 +1,94 @@
+{ pkgs, ... }:
+
+with builtins;
+with pkgs.nix.yants;
+
+# Note: Derivations are not included in the tests below as they cause
+# issues with deepSeq.
+
+deepSeq rec {
+  # Test that all primitive types match
+  primitives = [
+    (int 15)
+    (bool false)
+    (float 13.37)
+    (string "Hello!")
+    (function (x: x * 2))
+    (path /nix)
+  ];
+
+  # Test that polymorphic types work as intended
+  poly = [
+    (option int null)
+    (list string [ "foo" "bar" ])
+    (either int float 42)
+  ];
+
+  # Test that structures work as planned.
+  person = struct "person" {
+    name = string;
+    age  = int;
+
+    contact = option (struct {
+      email = string;
+      phone = option string;
+    });
+  };
+
+  testPerson = person {
+    name = "Brynhjulf";
+    age  = 42;
+    contact.email = "brynhjulf@yants.nix";
+  };
+
+  # Test enum definitions & matching
+  colour = enum "colour" [ "red" "blue" "green" ];
+  testMatch = colour.match "red" {
+    red = "It is in fact red!";
+    blue = throw "It should not be blue!";
+    green = throw "It should not be green!";
+  };
+
+  # Test sum type definitions
+  creature = sum "creature" {
+    human = struct {
+      name = string;
+      age = option int;
+    };
+
+    pet = enum "pet" [ "dog" "lizard" "cat" ];
+  };
+
+  testSum = creature {
+    human = {
+      name = "Brynhjulf";
+      age = 42;
+    };
+  };
+
+  testSumMatch = creature.match testSum {
+    human = v: "It's a human named ${v.name}";
+    pet = v: throw "It's not supposed to be a pet!";
+  };
+
+  # Test curried function definitions
+  func = defun [ string int string ]
+  (name: age: "${name} is ${toString age} years old");
+
+  testFunc = func "Brynhjulf" 42;
+
+  # Test that all types are types.
+  testTypes = map type [
+    any bool drv float int string path
+
+    (attrs int)
+    (eitherN [ int string bool ])
+    (either int string)
+    (enum [ "foo" "bar" ])
+    (list string)
+    (option int)
+    (option (list string))
+    (struct { a = int; b = option string; })
+    (sum { a = int; b = option string; })
+  ];
+} (pkgs.writeText "yants-tests" "All tests passed!")