diff options
-rw-r--r-- | default.nix | 8 | ||||
-rw-r--r-- | nix/builder/default.nix | 387 | ||||
-rw-r--r-- | nix/builder/fetch.sh | 13 | ||||
-rw-r--r-- | nix/builder/install/install.go | 57 | ||||
-rw-r--r-- | nix/builder/parser.nix | 141 | ||||
-rw-r--r-- | nix/builder/symlink/symlink.go | 110 |
6 files changed, 7 insertions, 709 deletions
diff --git a/default.nix b/default.nix index 3cafd8c..cf40d93 100644 --- a/default.nix +++ b/default.nix @@ -4,10 +4,16 @@ let url = "https://github.com/NixOS/nixpkgs/archive/d2db10786f27619d5519b12b03fb10dc8ca95e59.tar.gz"; sha256 = "0s9gigs3ylnq5b94rfcmxvrmmr3kzhs497gksajf638d5bv7zcl5"; }; + gomod2nix = fetchGit { + url = "https://github.com/tweag/gomod2nix.git"; + ref = "master"; + rev = "40d32f82fc60d66402eb0972e6e368aeab3faf58"; + }; + pkgs = import pkgsSrc { overlays = [ (self: super: { - gomod = super.callPackage ./nix/builder { }; + gomod = super.callPackage "${gomod2nix}/builder/" { }; }) ]; }; diff --git a/nix/builder/default.nix b/nix/builder/default.nix deleted file mode 100644 index 8b8ae19..0000000 --- a/nix/builder/default.nix +++ /dev/null @@ -1,387 +0,0 @@ -{ stdenv -, stdenvNoCC -, runCommand -, buildEnv -, lib -, fetchgit -, removeReferencesTo -, jq -, cacert -, pkgs -, pkgsBuildBuild -}: -let - - inherit (builtins) substring toJSON hasAttr trace split readFile elemAt; - inherit (lib) - concatStringsSep replaceStrings removePrefix optionalString pathExists - optional concatMapStrings fetchers filterAttrs mapAttrs mapAttrsToList - warnIf optionalAttrs platforms - ; - - parseGoMod = import ./parser.nix; - - removeExpr = refs: ''remove-references-to ${concatMapStrings (ref: " -t ${ref}") refs}''; - - # Internal only build-time attributes - internal = - let - mkInternalPkg = name: src: pkgsBuildBuild.runCommand "gomod2nix-${name}" - { - inherit (pkgsBuildBuild.go) GOOS GOARCH; - nativeBuildInputs = [ pkgsBuildBuild.go ]; - } '' - export HOME=$(mktemp -d) - cp ${src} src.go - go build -o $out src.go - ''; - in - { - - # Create a symlink tree of vendored sources - symlink = mkInternalPkg "symlink" ./symlink/symlink.go; - - # Install development dependencies from tools.go - install = mkInternalPkg "symlink" ./install/install.go; - - }; - - fetchGoModule = - { hash - , goPackagePath - , version - , go ? pkgs.go - }: - stdenvNoCC.mkDerivation { - name = "${baseNameOf goPackagePath}_${version}"; - builder = ./fetch.sh; - inherit goPackagePath version; - nativeBuildInputs = [ go jq ]; - outputHashMode = "recursive"; - outputHashAlgo = null; - outputHash = hash; - SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; - impureEnvVars = fetchers.proxyImpureEnvVars ++ [ "GOPROXY" ]; - }; - - mkVendorEnv = - { go - , modulesStruct - , localReplaceCommands ? [ ] - , defaultPackage ? "" - , goMod - , pwd - }: - let - localReplaceCommands = - let - localReplaceAttrs = filterAttrs (n: v: hasAttr "path" v) goMod.replace; - commands = ( - mapAttrsToList - (name: value: ( - '' - mkdir -p $(dirname vendor/${name}) - ln -s ${pwd + "/${value.path}"} vendor/${name} - '' - )) - localReplaceAttrs); - in - if goMod != null then commands else [ ]; - - sources = mapAttrs - (goPackagePath: meta: fetchGoModule { - goPackagePath = meta.replaced or goPackagePath; - inherit (meta) version hash; - inherit go; - }) - modulesStruct.mod; - in - runCommand "vendor-env" - { - nativeBuildInputs = [ go ]; - json = toJSON (filterAttrs (n: _: n != defaultPackage) modulesStruct.mod); - - sources = toJSON (filterAttrs (n: _: n != defaultPackage) sources); - - passthru = { - inherit sources; - }; - - passAsFile = [ "json" "sources" ]; - } - ( - '' - mkdir vendor - - export GOCACHE=$TMPDIR/go-cache - export GOPATH="$TMPDIR/go" - - ${internal.symlink} - ${concatStringsSep "\n" localReplaceCommands} - - mv vendor $out - '' - ); - - # Select Go attribute based on version specified in go.mod - selectGo = attrs: goMod: attrs.go or (if goMod == null then pkgs.go else - ( - let - goVersion = goMod.go; - goAttr = "go_" + (replaceStrings [ "." ] [ "_" ] goVersion); - in - ( - if hasAttr goAttr pkgs then pkgs.${goAttr} - else trace "go.mod specified Go version ${goVersion} but doesn't exist. Falling back to ${pkgs.go.version}." pkgs.go - ) - )); - - # Strip the rubbish that Go adds to versions, and fall back to a version based on the date if it's a placeholder value - stripVersion = version: - let - parts = elemAt (split "(\\+|-)" (removePrefix "v" version)); - v = parts 0; - d = parts 2; - in - if v != "0.0.0" then v else "unstable-" + (concatStringsSep "-" [ - (substring 0 4 d) - (substring 4 2 d) - (substring 6 2 d) - ]); - - mkGoEnv = - { pwd - }@attrs: - let - goMod = parseGoMod (readFile "${toString pwd}/go.mod"); - modulesStruct = fromTOML (readFile "${toString pwd}/gomod2nix.toml"); - - go = selectGo attrs goMod; - - vendorEnv = mkVendorEnv { - inherit go modulesStruct pwd goMod; - }; - - in - stdenv.mkDerivation (removeAttrs attrs [ "pwd" ] // { - name = "${baseNameOf goMod.module}-env"; - - dontUnpack = true; - dontConfigure = true; - dontInstall = true; - - propagatedNativeBuildInputs = [ go ]; - - GO_NO_VENDOR_CHECKS = "1"; - - GO111MODULE = "on"; - GOFLAGS = "-mod=vendor"; - - preferLocalBuild = true; - - buildPhase = '' - mkdir $out - - export GOCACHE=$TMPDIR/go-cache - export GOPATH="$out" - export GOSUMDB=off - export GOPROXY=off - - '' + optionalString (pathExists (pwd + "/tools.go")) '' - mkdir source - cp ${pwd + "/go.mod"} source/go.mod - cp ${pwd + "/go.sum"} source/go.sum - cp ${pwd + "/tools.go"} source/tools.go - cd source - ln -s ${vendorEnv} vendor - - ${internal.install} - ''; - }); - - buildGoApplication = - { modules ? pwd + "/gomod2nix.toml" - , src ? pwd - , pwd ? null - , nativeBuildInputs ? [ ] - , allowGoReference ? false - , meta ? { } - , passthru ? { } - , tags ? [ ] - - # needed for buildFlags{,Array} warning - , buildFlags ? "" - , buildFlagsArray ? "" - - , ... - }@attrs: - let - modulesStruct = fromTOML (readFile modules); - - goModPath = "${toString pwd}/go.mod"; - - goMod = - if pwd != null && pathExists goModPath - then parseGoMod (readFile goModPath) - else null; - - go = selectGo attrs goMod; - - removeReferences = [ ] ++ optional (!allowGoReference) go; - - defaultPackage = modulesStruct.goPackagePath or ""; - - vendorEnv = mkVendorEnv { - inherit go modulesStruct defaultPackage goMod pwd; - }; - - in - warnIf (buildFlags != "" || buildFlagsArray != "") - "Use the `ldflags` and/or `tags` attributes instead of `buildFlags`/`buildFlagsArray`" - stdenv.mkDerivation - (optionalAttrs (defaultPackage != "") - { - pname = attrs.pname or baseNameOf defaultPackage; - version = stripVersion (modulesStruct.mod.${defaultPackage}).version; - src = vendorEnv.passthru.sources.${defaultPackage}; - } // optionalAttrs (hasAttr "subPackages" modulesStruct) { - subPackages = modulesStruct.subPackages; - } // attrs // { - nativeBuildInputs = [ removeReferencesTo go ] ++ nativeBuildInputs; - - inherit (go) GOOS GOARCH; - - GO_NO_VENDOR_CHECKS = "1"; - - GO111MODULE = "on"; - GOFLAGS = "-mod=vendor"; - - configurePhase = attrs.configurePhase or '' - runHook preConfigure - - export GOCACHE=$TMPDIR/go-cache - export GOPATH="$TMPDIR/go" - export GOSUMDB=off - export GOPROXY=off - cd "$modRoot" - if [ -n "${vendorEnv}" ]; then - rm -rf vendor - ln -s ${vendorEnv} vendor - fi - - runHook postConfigure - ''; - - buildPhase = attrs.buildPhase or '' - runHook preBuild - - exclude='\(/_\|examples\|Godeps\|testdata' - if [[ -n "$excludedPackages" ]]; then - IFS=' ' read -r -a excludedArr <<<$excludedPackages - printf -v excludedAlternates '%s\\|' "''${excludedArr[@]}" - excludedAlternates=''${excludedAlternates%\\|} # drop final \| added by printf - exclude+='\|'"$excludedAlternates" - fi - exclude+='\)' - - buildGoDir() { - local d; local cmd; - cmd="$1" - d="$2" - . $TMPDIR/buildFlagsArray - local OUT - if ! OUT="$(go $cmd $buildFlags "''${buildFlagsArray[@]}" ''${tags:+-tags=${concatStringsSep "," tags}} ''${ldflags:+-ldflags="$ldflags"} -v -p $NIX_BUILD_CORES $d 2>&1)"; then - if echo "$OUT" | grep -qE 'imports .*?: no Go files in'; then - echo "$OUT" >&2 - return 1 - fi - if ! echo "$OUT" | grep -qE '(no( buildable| non-test)?|build constraints exclude all) Go (source )?files'; then - echo "$OUT" >&2 - return 1 - fi - fi - if [ -n "$OUT" ]; then - echo "$OUT" >&2 - fi - return 0 - } - - getGoDirs() { - local type; - type="$1" - if [ -n "$subPackages" ]; then - echo "$subPackages" | sed "s,\(^\| \),\1./,g" - else - find . -type f -name \*$type.go -exec dirname {} \; | grep -v "/vendor/" | sort --unique | grep -v "$exclude" - fi - } - - if (( "''${NIX_DEBUG:-0}" >= 1 )); then - buildFlagsArray+=(-x) - fi - - if [ ''${#buildFlagsArray[@]} -ne 0 ]; then - declare -p buildFlagsArray > $TMPDIR/buildFlagsArray - else - touch $TMPDIR/buildFlagsArray - fi - if [ -z "$enableParallelBuilding" ]; then - export NIX_BUILD_CORES=1 - fi - for pkg in $(getGoDirs ""); do - echo "Building subPackage $pkg" - buildGoDir install "$pkg" - done - '' + optionalString (stdenv.hostPlatform != stdenv.buildPlatform) '' - # normalize cross-compiled builds w.r.t. native builds - ( - dir=$GOPATH/bin/${go.GOOS}_${go.GOARCH} - if [[ -n "$(shopt -s nullglob; echo $dir/*)" ]]; then - mv $dir/* $dir/.. - fi - if [[ -d $dir ]]; then - rmdir $dir - fi - ) - '' + '' - runHook postBuild - ''; - - doCheck = attrs.doCheck or true; - checkPhase = attrs.checkPhase or '' - runHook preCheck - - for pkg in $(getGoDirs test); do - buildGoDir test $checkFlags "$pkg" - done - - runHook postCheck - ''; - - installPhase = attrs.installPhase or '' - runHook preInstall - - mkdir -p $out - dir="$GOPATH/bin" - [ -e "$dir" ] && cp -r $dir $out - - runHook postInstall - ''; - - preFixup = (attrs.preFixup or "") + '' - find $out/{bin,libexec,lib} -type f 2>/dev/null | xargs -r ${removeExpr removeReferences} || true - ''; - - strictDeps = true; - - disallowedReferences = optional (!allowGoReference) go; - - passthru = { inherit go vendorEnv; } // passthru; - - meta = { platforms = go.meta.platforms or platforms.all; } // meta; - }); - -in -{ - inherit buildGoApplication mkGoEnv; -} diff --git a/nix/builder/fetch.sh b/nix/builder/fetch.sh deleted file mode 100644 index 8c6bdb3..0000000 --- a/nix/builder/fetch.sh +++ /dev/null @@ -1,13 +0,0 @@ -source $stdenv/setup - -export HOME=$(mktemp -d) - -# Call once first outside of subshell for better error reporting -go mod download "$goPackagePath@$version" - -dir=$(go mod download --json "$goPackagePath@$version" | jq -r .Dir) - -chmod -R +w $dir -find $dir -iname ".ds_store" | xargs -r rm -rf - -cp -r $dir $out diff --git a/nix/builder/install/install.go b/nix/builder/install/install.go deleted file mode 100644 index 4f770b0..0000000 --- a/nix/builder/install/install.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "go/parser" - "go/token" - "io" - "os" - "os/exec" - "strconv" -) - -const filename = "tools.go" - -func main() { - fset := token.NewFileSet() - - var src []byte - { - f, err := os.Open(filename) - if err != nil { - panic(err) - } - - src, err = io.ReadAll(f) - if err != nil { - panic(err) - } - } - - f, err := parser.ParseFile(fset, filename, src, parser.ImportsOnly) - if err != nil { - fmt.Println(err) - return - } - - for _, s := range f.Imports { - path, err := strconv.Unquote(s.Path.Value) - if err != nil { - panic(err) - } - - cmd := exec.Command("go", "install", path) - - fmt.Printf("Executing '%s'\n", cmd) - - err = cmd.Start() - if err != nil { - panic(err) - } - - err = cmd.Wait() - if err != nil { - panic(err) - } - } -} diff --git a/nix/builder/parser.nix b/nix/builder/parser.nix deleted file mode 100644 index eb6f75e..0000000 --- a/nix/builder/parser.nix +++ /dev/null @@ -1,141 +0,0 @@ -# Parse go.mod in Nix -# Returns a Nix structure with the contents of the go.mod passed in -# in normalised form. - -let - inherit (builtins) elemAt mapAttrs split foldl' match filter typeOf hasAttr length; - - # Strip lines with comments & other junk - stripStr = s: elemAt (split "^ *" (elemAt (split " *$" s) 0)) 2; - stripLines = initialLines: foldl' (acc: f: f acc) initialLines [ - # Strip comments - (lines: map - (l: stripStr (elemAt (splitString "//" l) 0)) - lines) - - # Strip leading tabs characters - (lines: map (l: elemAt (match "(\t)?(.*)" l) 1) lines) - - # Filter empty lines - (filter (l: l != "")) - ]; - - # Parse lines into a structure - parseLines = lines: (foldl' - (acc: l: - let - m = match "([^ )]*) *(.*)" l; - directive = elemAt m 0; - rest = elemAt m 1; - - # Maintain parser state (inside parens or not) - inDirective = - if rest == "(" then directive - else if rest == ")" then null - else acc.inDirective - ; - - in - { - data = (acc.data // ( - if directive == "" && rest == ")" then { } - else if inDirective != null && rest == "(" && ! hasAttr inDirective acc.data then { - ${inDirective} = { }; - } - else if rest == "(" || rest == ")" then { } - else if inDirective != null then { - ${inDirective} = acc.data.${inDirective} // { ${directive} = rest; }; - } else if directive == "replace" then - ( - let - segments = split " => " rest; - getSegment = elemAt segments; - in - assert length segments == 3; { - replace = acc.data.replace // { - ${getSegment 0} = "=> ${getSegment 2}"; - }; - } - ) - else { - ${directive} = rest; - } - ) - ); - inherit inDirective; - }) - { - inDirective = null; - data = { - require = { }; - replace = { }; - exclude = { }; - }; - } - lines - ).data; - - normaliseDirectives = data: ( - let - normaliseString = s: - let - m = builtins.match "([^ ]+) (.+)" s; - in - { - ${elemAt m 0} = elemAt m 1; - }; - require = data.require or { }; - replace = data.replace or { }; - exclude = data.exclude or { }; - in - data // { - require = - if typeOf require == "string" then normaliseString require - else require; - replace = - if typeOf replace == "string" then normaliseString replace - else replace; - } - ); - - parseVersion = ver: - let - m = elemAt (match "([^-]+)-?([^-]*)-?([^-]*)" ver); - v = elemAt (match "([^+]+)\\+?(.*)" (m 0)); - in - { - version = v 0; - versionSuffix = v 1; - date = m 1; - rev = m 2; - }; - - parseReplace = data: ( - data // { - replace = - mapAttrs - (_: v: - let - m = match "=> ([^ ]+) (.+)" v; - m2 = match "=> (.*+)" v; - in - if m != null then { - goPackagePath = elemAt m 0; - version = elemAt m 1; - } else { - path = elemAt m2 0; - }) - data.replace; - } - ); - - splitString = sep: s: filter (t: t != [ ]) (split sep s); - -in -contents: -foldl' (acc: f: f acc) (splitString "\n" contents) [ - stripLines - parseLines - normaliseDirectives - parseReplace -] diff --git a/nix/builder/symlink/symlink.go b/nix/builder/symlink/symlink.go deleted file mode 100644 index 3dbb383..0000000 --- a/nix/builder/symlink/symlink.go +++ /dev/null @@ -1,110 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" -) - -type Package struct { - GoPackagePath string `json:"-"` - Version string `json:"version"` - Hash string `json:"hash"` - ReplacedPath string `json:"replaced,omitempty"` -} - -// type Output struct { -// SchemaVersion int `json:"schema"` -// Mod map[string]*Package `json:"mod"` -// } - -func main() { - - // var output Output - sources := make(map[string]string) - pkgs := make(map[string]*Package) - - { - b, err := ioutil.ReadFile(os.Getenv("sourcesPath")) - if err != nil { - panic(err) - } - - err = json.Unmarshal(b, &sources) - if err != nil { - panic(err) - } - } - - { - b, err := ioutil.ReadFile(os.Getenv("jsonPath")) - if err != nil { - panic(err) - } - - err = json.Unmarshal(b, &pkgs) - if err != nil { - panic(err) - } - } - - keys := make([]string, 0, len(pkgs)) - for key := range pkgs { - keys = append(keys, key) - } - sort.Strings(keys) - - // Iterate, in reverse order - for i := len(keys) - 1; i >= 0; i-- { - key := keys[i] - src := sources[key] - - paths := []string{key} - - for _, path := range paths { - - vendorDir := filepath.Join("vendor", filepath.Dir(path)) - if err := os.MkdirAll(vendorDir, 0755); err != nil { - panic(err) - } - - if _, err := os.Stat(filepath.Join("vendor", path)); err == nil { - files, err := ioutil.ReadDir(src) - if err != nil { - panic(err) - } - - for _, f := range files { - innerSrc := filepath.Join(src, f.Name()) - dst := filepath.Join("vendor", path, f.Name()) - if err := os.Symlink(innerSrc, dst); err != nil { - // assume it's an existing directory, try to link the directory content instead. - // TODO should we do this recursively - files, err := ioutil.ReadDir(innerSrc) - if err != nil { - panic(err) - } - for _, f := range files { - if err := os.Symlink(filepath.Join(innerSrc, f.Name()), filepath.Join(dst, f.Name())); err != nil { - fmt.Println("ignore symlink error", filepath.Join(innerSrc, f.Name()), filepath.Join(dst, f.Name())) - } - } - } - } - - continue - } - - // If the file doesn't already exist, just create a simple symlink - err := os.Symlink(src, filepath.Join("vendor", path)) - if err != nil { - panic(err) - } - - } - } - -} |