# 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 { } , ... }: let inherit (pkgs) lib; go = pkgs.go; goStdlib = buildStdlib { inherit go; }; splitSources = srcs: let # exts: { typeName = [ extension ] }; exts = { go = [ ".go" ]; asm = [ ".s" ".S" ]; headers = [ ".h" ".hh" ".hpp" ".hxx" ".inc" ]; c = [ ".c" ]; cxx = [ ".cc" ".cxx" ".cpp" ]; objc = [ ".m" ".mm" ]; }; # [ extension ] -> { extension = true; } extListToAttrs = exts: builtins.listToAttrs (builtins.map (v: lib.attrsets.nameValuePair v true) exts); # exts': { typeName = { extension = true; } }; exts' = builtins.mapAttrs (_: extListToAttrs) exts; allExts = lib.lists.flatten (builtins.attrValues exts); allExts' = extListToAttrs allExts; matchesExts = exts: src: let srcExtList = builtins.match ".*([.][^.]+)$" (toString src); srcExt = builtins.elemAt srcExtList 0; in srcExtList != null && exts ? "${srcExt}"; splitByExt = exts: builtins.filter (matchesExts exts) srcs; leftovers = builtins.filter (src: !(matchesExts allExts' src)) srcs; in assert (lib.assertMsg (builtins.length leftovers == 0) "uncategorisable files: ${toString leftovers}"); builtins.mapAttrs (name: value: splitByExt value) exts'; importconfig = { name, deps }: let # Go through every dep and generate a packagefile importpath=${output}. depPackagefiles = map (dep: if dep ? importpath then "packagefile ${dep.importpath}=${dep}/pkg.a" else "") deps; in '' # nix buildGo ${name} ${builtins.concatStringsSep "\n" depPackagefiles} ''; buildStdlib = { go }: pkgs.stdenv.mkDerivation { pname = "go-stdlib"; inherit (go) version; nativeBuildInputs = [ go ]; unpackPhase = '' HOME=$NIX_BUILD_TOP/home mkdir $HOME goroot="$(go env GOROOT)" cp -R "$goroot/src" "$goroot/pkg" . ''; dontConfigure = true; buildPhase = '' chmod -R +w . GODEBUG=installgoroot=all GOROOT=$NIX_BUILD_TOP go install -v --trimpath std ''; installPhase = '' 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 ''; dontFixup = true; passthru.go = go; }; package = { name, path, srcs, deps ? [], cgo ? false, cgodeps ? [], go ? goStdlib.go, stdlib ? goStdlib, }@args: assert stdlib != null -> stdlib.go == go; let importcfg = importconfig { inherit name deps; stdlib = go; }; srcs' = splitSources srcs; stdenv = if cgo then pkgs.stdenv else pkgs.stdenvNoCC; cgoSrcs = srcs'.c ++ srcs'.cxx ++ srcs'.objc; noCgoNoC = lib.assertMsg (builtins.length cgoSrcs == 0) "cgo source files present, but cgo not set to true: ${toString cgoSrcs}"; baseNameOnly = map (f: baseNameOf (toString f)); in assert !cgo -> noCgoNoC; stdenv.mkDerivation rec { inherit name; __structuredAttrs = true; buildInputs = cgodeps; nativeBuildInputs = [ go ] ++ (if cgo then [ pkgs.pkg-config ] else []); passthru = { importpath = path; inherit importcfg stdlib; cgo = cgo || lib.lists.any (x: x.cgo) deps; }; importcfg = importconfig { inherit name deps; }; allSrcs = srcs; goSrcs = baseNameOnly srcs'.go; asmSrcs = baseNameOnly srcs'.asm; cSrcs = baseNameOnly srcs'.c; cxxSrcs = baseNameOnly srcs'.cxx; objcSrcs = baseNameOnly srcs'.objc; unpackPhase = '' mkdir src ${builtins.concatStringsSep "\n" (map (f: '' cp "${f}" "src/${baseNameOf (toString f)}" '') srcs)} cd src ''; configurePhase = '' echo "$importcfg" > $NIX_BUILD_TOP/importcfg ${if stdlib != null then '' cat ${stdlib}/importcfg >> $NIX_BUILD_TOP/importcfg '' else ""} for dep in deps; do if [[ ! -f "$dep/importcfg" ]]; then continue; fi cat $dep/importcfg >> $NIX_BUILD_TOP/importcfg done ''; buildPhase = '' mkdir -p $out completeFlag="${if cgo then "" else "-complete"}" outputFlag="-o $out/pkg.a" if [ ''${#asmSrcs[@]} -gt 0 ]; then completeFlag="" mkdir $NIX_BUILD_TOP/pack $NIX_BUILD_TOP/include outputFlag="-symabis $NIX_BUILD_TOP/goasm.abi -o $out/pkg.a -asmhdr $NIX_BUILD_TOP/include/go_asm.h" touch $NIX_BUILD_TOP/include/go_asm.h ASMCMD="go tool asm -trimpath $NIX_BUILD_TOP/src -I $NIX_BUILD_TOP/include -I $(go env GOROOT)/pkg/include -D GOOS=$(go env GOOS) -D GOOS_$(go env GOOS) -D GOARCH=$(go env GOARCH) -D GOARCH_$(go env GOARCH) -p ${path}" $ASMCMD -gensymabis -o $NIX_BUILD_TOP/goasm.abi "''${asmSrcs[@]}" fi ${if cgo then '' mkdir $NIX_BUILD_TOP/cgo $NIX_BUILD_TOP/cgo_built go tool cgo -objdir $NIX_BUILD_TOP/cgo -trimpath $NIX_BUILD_TOP/src -importpath "${path}" -- "''${goSrcs[@]}" for f in "''${cSrcs[@]}"; do $CC -I$NIX_BUILD_TOP/cgo -I. -c "$f" -o "$NIX_BUILD_TOP/cgo_built/''${f}.o" done for f in "''${cxxSrcs[@]}"; do $CXX -I$NIX_BUILD_TOP/cgo -I. -c "$f" -o "$NIX_BUILD_TOP/cgo_built/''${f}.o" done for f in "''${objcSrcs[@]}"; do $OBJC -I$NIX_BUILD_TOP/cgo -I. -c "$f" -o "$NIX_BUILD_TOP/cgo_built/''${f}.o" done pushd $NIX_BUILD_TOP/cgo &>/dev/null for f in *.cgo2.c _cgo_export.c _cgo_main.c; do $CC -I$NIX_BUILD_TOP/src -c "$f" -o ''${f}.o done $CC -o _cgo_.o _cgo_main.c.o _cgo_export.c.o $NIX_BUILD_TOP/cgo_built/*.o *.cgo2.c.o popd &>/dev/null go tool cgo -dynpackage "${name}" -dynimport $NIX_BUILD_TOP/cgo/_cgo_.o -dynout $NIX_BUILD_TOP/cgo/_cgo_imports.go shopt -s nullglob goSrcs=($NIX_BUILD_TOP/cgo/*.go) '' else ""} go tool compile $completeFlag -importcfg $NIX_BUILD_TOP/importcfg -trimpath $NIX_BUILD_TOP/src -pack -p "${path}" $outputFlag "''${goSrcs[@]}" if [ ''${#asmSrcs[@]} -gt 0 ]; then $ASMCMD -o $NIX_BUILD_TOP/pack/goasm.o "''${asmSrcs[@]}" go tool pack r $out/pkg.a $NIX_BUILD_TOP/pack/*.o fi ${if cgo then '' go tool pack r $out/pkg.a $NIX_BUILD_TOP/cgo/*.cgo2.c.o $NIX_BUILD_TOP/cgo/_cgo_export.c.o $NIX_BUILD_TOP/cgo_built/*.o '' else ""} ''; }; program = { name, deps ? [], ... }@args: let pkg = package (args // { name = "${name}-lib"; path = "main"; }); cgo = pkg.cgo; stdenv = if cgo then pkgs.stdenv else pkgs.stdenvNoCC; in stdenv.mkDerivation rec { inherit name; __structuredAttrs = true; nativeBuildInputs = [ go ]; lib = pkg; inherit (pkg) importcfg stdlib; dontUnpack = true; configurePhase = '' echo "$importcfg" > importcfg ${if stdlib != null then '' cat ${stdlib}/importcfg >> importcfg '' else ""} ''; buildPhase = '' mkdir out go tool link -importcfg importcfg -tmpdir "$TMPDIR" -o "out/bin" -s -w $lib/pkg.a ''; installPhase = '' mkdir -p $out/bin cp out/bin "$out/bin/${name}" ''; }; external = { src, path, deps ? [ ], tags ? [ ], cgo ? false, cgodeps ? [ ], cgocflags ? [ ], cgoldflags ? [ ], }: {}; in { #inherit program package proto external; inherit goStdlib package program external; }