about summary refs log blame commit diff
path: root/buildGo.nix
blob: 366be770a959a0c9f688243d05ea23e1914432cb (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13












                                                                             
         

          
               

         
           

                  
 
                                                                        













                                                                                   

                                                                              

                                                                                     

                                                          






                                                                 


                                                                   
                                                   



                                                                                                                       
                                                                                                                












                                                                                                                                       

                                                                
                                                                 
                      
                                              

                                                                  

                                                                                  



                     


                                                                     


































                                                                             






                                                                    
                                                                            














                                                          
                                                                          










                                                   
                                                    

                                      
                                   
 

                                  
    
    






                                                               







                                                  
 
# 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 }:
  let uniqueDeps = allDeps deps;
  in (runCommand "golib-${name}" {} ''
    mkdir -p $out/${path}
    ${srcList path (map (s: "${s}") srcs)}
    ${go}/bin/go tool compile -o $out/${path}.a -trimpath=$PWD -trimpath=${go} -p ${path} ${includeSources uniqueDeps} ${spaceOut srcs}
  '') // { goDeps = uniqueDeps; };

  # Build a Go library out of the specified protobuf definition.
  proto = { name, proto, path ? name, extraDeps ? [] }: package {
    inherit name path;
    deps = [ protoLibs.goProto ] ++ extraDeps;
    srcs = lib.singleton (runCommand "goproto-${name}.pb.go" {} ''
      cp ${proto} ${baseNameOf proto}
      ${protobuf}/bin/protoc --plugin=${protoLibs.goProto}/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 ]; });

  # Traverse an externally defined Go library to build up a tree of
  # its packages.
  #
  # TODO(tazjin): Automatically infer which packages depend on which
  # other packages, which currently requires overriding.
  #
  # TODO(tazjin): Add support for rewriting package paths.
  external' = { src, path, deps ? [] }:
    let
      dir = readDir src;
      isGoFile = f: (match ".*\.go" f) != null;
      isGoTest = f: (match ".*_test\.go" f) != null;
      goFileFilter = k: v: (v == "regular") && (isGoFile k) && (!isGoTest k);
      goSources =
        let goFiles = filter (f: goFileFilter f dir."${f}") (attrNames dir);
        in map (f: src + ("/" + f)) goFiles;

      subDirs = filter (n: dir."${n}" == "directory") (attrNames dir);
      subPackages = map (name: {
        inherit name;
        value = external' {
          inherit deps;
          src = src + ("/" + name);
          path = path + ("/" + name);
        };
      }) subDirs;
      subAttrs = listToAttrs (filter (p: p.value != {}) subPackages);

      current = package {
        inherit deps path;
        name = pathToName path;
        srcs = goSources;
      };
    in if goSources == [] then subAttrs else (current // subAttrs);

  # Build an externally defined Go library using `go build` itself.
  #
  # Libraries built this way can be included in any standard buildGo
  # build.
  #
  # Contrary to other functions, `src` is expected to point at a
  # single directory containing the root of the external library.
  external = { path, src, deps ? [], srcOnly ? false, targets ? [ "..." ] }:
    let
      name = pathToName path;
      uniqueDeps = allDeps deps;
      srcDir = runCommand "goext-src-${name}" {} ''
        mkdir -p $out/${dirOf path}
        cp -r ${src} $out/${dirOf path}/${baseNameOf path}
      '';
      gopathSrc = symlinkJoin {
        name = "gopath-${name}";
        paths = uniqueDeps ++ [ srcDir ];
      };
      gopathPkg = runCommand "goext-pkg-${name}" {} ''
        mkdir -p gopath $out
        export GOPATH=$PWD/gopath
        ln -s ${gopathSrc} gopath/src
        ${go}/bin/go install ${spaceOut (map (t: path + "/" + t) targets)}

        if [[ -d gopath/pkg/linux_amd64 ]]; then
          echo "Installing Go packages for ${path}"
          mv gopath/pkg/linux_amd64/* $out
        fi

        if [[ -d gopath/bin ]]; then
          echo "Installing Go binaries for ${path}"
          mv gopath/bin $out/bin
        fi
      '';
    in (if srcOnly then gopathSrc else symlinkJoin {
      name = "goext-${name}";
      paths = [ gopathSrc gopathPkg ];
    }) // { goDeps = uniqueDeps; };

  protoLibs = import ./proto.nix {
    inherit external;
  };
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;

  # TODO: remove
  inherit external';

  extTest = external' {
    src = /home/tazjin/go/src/cloud.google.com/go;
    path = "cloud.google.com/go";
  };
}