238 lines
9 KiB
Nix
238 lines
9 KiB
Nix
# 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> { }
|
|
, 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;
|
|
}
|