# 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 { } , gopkgs , ... }: let inherit (builtins) attrNames baseNameOf dirOf elemAt filter listToAttrs map match readDir replaceStrings toString; inherit (pkgs) lib runCommand runCommandCC fetchFromGitHub protobuf symlinkJoin go; goStdlib = buildStdlib go; # 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; srcDest = path: src: "$out/${path}/${srcBasename src}"; srcCopy = path: src: "cp ${src} ${srcDest path src}"; srcList = path: srcs: lib.concatStringsSep "\n" (map (srcCopy path) srcs); allDeps = deps: lib.unique (lib.flatten (deps ++ (map (d: d.goDeps) deps))); anyCgo = allDeps: lib.any (d: d.cgo) allDeps; allLDFLAGS = allDeps: lib.unique (lib.flatten (map (d: d.cgoLDFLAGS) allDeps)); allCgoBuildInputs = allDeps: lib.unique (lib.flatten (map (d: d.cgoBuildInputs) allDeps)); 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)); }; buildStdlib = go: runCommandCC "go-stdlib-${go.version}" { nativeBuildInputs = [ go ]; } '' HOME=$NIX_BUILD_TOP/home mkdir $HOME goroot="$(go env GOROOT)" cp -R "$goroot/src" "$goroot/pkg" . chmod -R +w . GODEBUG=installgoroot=all GOROOT=$NIX_BUILD_TOP go install -v --trimpath std mkdir $out cp -r pkg/*_*/* $out find $out -name '*.a' | while read -r ARCHIVE_FULL; do ARCHIVE="''${ARCHIVE_FULL#"$out/"}" PACKAGE="''${ARCHIVE%.a}" echo "packagefile $PACKAGE=$ARCHIVE_FULL" done > $out/importcfg ''; importcfgCmd = { name, deps, out ? "importcfg" }: '' echo "# nix buildGo ${name}" > "${out}" cat "${goStdlib}/importcfg" >> "${out}" ${lib.concatStringsSep "\n" (map (dep: '' find "${dep}" -name '*.a' | while read -r pkgp; do relpath="''${pkgp#"${dep}/"}" pkgname="''${relpath%.a}" echo "packagefile $pkgname=$pkgp" done >> "${out}" '') deps)} ''; # High-level build functions # Build a Go program out of the specified files and dependencies. program = { name, srcs, deps ? [ ], x_defs ? { } }: let uniqueDeps = allDeps (map (d: d.gopkg) deps); cgo = anyCgo uniqueDeps; cgoLDFLAGS = allLDFLAGS uniqueDeps; cgoBuildInputs = allCgoBuildInputs uniqueDeps; runCommand = if cgo then runCommandCC else pkgs.runCommand; in runCommand name { buildInputs = cgoBuildInputs; } '' ${importcfgCmd { inherit name; deps = uniqueDeps; }} ${go}/bin/go tool compile -o ${name}.a -importcfg=importcfg -trimpath=$PWD -trimpath=${go} -p main ${includeSources uniqueDeps} ${spaceOut srcs} mkdir -p $out/bin export GOROOT_FINAL=go ${go}/bin/go tool link -o $out/bin/${name} -importcfg=importcfg -buildid nix \ -extldflags '${toString cgoLDFLAGS}' \ ${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, packageName ? builtins.baseNameOf path, sfiles ? [ ], cgofiles ? [ ], cfiles ? [ ], cxxfiles ? [ ], cgodeps ? [ ], cgocflags ? [ ], cgoldflags ? [ ] }: let uniqueDeps = allDeps (map (d: d.gopkg) deps); ifCgo = do: lib.optionalString (cgofiles != [ ]) do; cgoBuild = ifCgo '' BUILDGO_CFLAGS="${spaceOut (lib.unique (map (d: "-I ${builtins.dirOf d}") cgofiles))} ${lib.concatStringsSep " " cgocflags}" BUILDGO_LDFLAGS="${spaceOut cgoldflags}" ${go}/bin/go tool cgo -trimpath=$PWD -trimpath=${go} -trimpath=$out/${path} -importpath=${path} -- $BUILDGO_CFLAGS ${spaceOut cgofiles} for f in $PWD/_obj/*.cgo2.c $PWD/_obj/_cgo_export.c; do cc $BUILDGO_CFLAGS -c $f -o ''${f}.o done cc $BUILDGO_CFLAGS -c $PWD/_obj/_cgo_main.c -o $PWD/_obj/_cgo_main.o cc $BUILDGO_CFLAGS -o $PWD/_obj/_cgo_.o $PWD/_obj/_cgo_main.o $PWD/_obj/*.cgo2.c.o $BUILDGO_LDFLAGS ${go}/bin/go tool cgo -dynpackage ${packageName} -dynimport $PWD/_obj/_cgo_.o -dynout $PWD/_obj/_cgo_imports.go EXTRAGO=$PWD/_obj/*.go ''; cgoPack = ifCgo '' ${go}/bin/go tool pack r $out/${path}.a $PWD/_obj/*.cgo2.c.o $PWD/_obj/_cgo_export.c.o ''; # 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: lib.optionalString (sfiles != [ ]) do; asmBuild = ifAsm '' ${go}/bin/go tool asm -trimpath $PWD -I $PWD -I ${go}/share/go/pkg/include -D GOOS_linux -D GOARCH_amd64 -p ${path} -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 -p ${path} -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 ''; runCommand = if (cgofiles != [ ]) then runCommandCC else pkgs.runCommand; gopkg = (runCommand "golib-${name}" { buildInputs = cgodeps; } '' mkdir -p $out/${path} EXTRAGO="" ${importcfgCmd { inherit name; deps = uniqueDeps; }} ${srcList path (map (s: "${s}") srcs)} ${asmBuild} ${cgoBuild} ${go}/bin/go tool compile -pack ${asmLink} -o $out/${path}.a -importcfg=importcfg -trimpath=$PWD -trimpath=${go} -trimpath=$out/${path} -p ${path} ${includeSources uniqueDeps} ${spaceOut (map (srcDest path) srcs)} $EXTRAGO ${asmPack} ${cgoPack} '').overrideAttrs (_: { passthru = { inherit gopkg; goDeps = uniqueDeps; cgo = cgofiles != [ ]; cgoBuildInputs = cgodeps; cgoLDFLAGS = cgoldflags; goImportPath = path; }; }); in gopkg; # 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 gopkgs; }; # Build a Go library out of the specified protobuf definition. proto = { name, proto ? null, protos ? [ proto ], path ? name, goPackage ? name, withGrpc ? false, extraSrcs ? [], extraDeps ? [] }: let protosDir = runCommand "protos" {} '' mkdir $out ${lib.concatMapStrings (p: "cp ${p} $out/${baseNameOf p}\n") protos} ''; mname = prefix: lib.concatMapStrings (p: "${prefix}M${baseNameOf p}=${path} ") protos; in (makeOverridable package) { inherit name path; deps = [ protoLibs.goProto.proto.gopkg ] ++ extraDeps; srcs = lib.concatMap (proto: lib.singleton (runCommand "goproto-${name}-${baseNameOf proto}.pb.go" {} '' ${protobuf}/bin/protoc \ -I ${protosDir} \ --plugin=${protoLibs.goProto.cmd.protoc-gen-go.gopkg}/bin/protoc-gen-go \ --go_out=. \ --go_opt=paths=source_relative \ ${mname "--go_opt="} \ ${protosDir}/${baseNameOf proto} mv ./*.pb.go $out '') ++ lib.optional withGrpc (runCommand "gogrpcproto-${name}-${baseNameOf proto}.pb.go" {} '' ${protobuf}/bin/protoc \ -I ${protosDir} \ --plugin=${protoLibs.goGrpc.cmd.protoc-gen-go-grpc.gopkg}/bin/protoc-gen-go-grpc \ --go-grpc_out=. \ --go-grpc_opt=paths=source_relative \ ${mname "--go-grpc_opt="} \ ${protosDir}/${baseNameOf proto} mv ./*.pb.go $out 2>/dev/null || echo "package ${goPackage}" >> $out '')) protos ++ extraSrcs; }; # Build a Go library out of the specified gRPC definition. grpc = { extraDeps ? [], ... }@args: proto (args // { withGrpc = true; extraDeps = 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; }