From 442b4154e2041f1976289685d2ae74683baf62e8 Mon Sep 17 00:00:00 2001 From: adisbladis Date: Mon, 29 Mar 2021 16:38:03 +0200 Subject: [PATCH] Add support for local replace directives This fixes relative path modules not being correctly vendored by parsing go.mod in Nix and vendoring them into the correct directory in the Nix sandbox. Ideally this should also come with a pluggable source filtering mechanism and/or maybe use any present gitignore files like poetry2nix does. To use this new feature you have to pass a new `pwd` param to `buildGoApplication` as such: ``` buildGoApplication { pname = "foo"; version = "bar"; pwd = ./.; src = lib.cleanSource ./.; } ``` Fixes #2 --- builder/default.nix | 22 ++++++++ builder/parser.nix | 134 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 builder/parser.nix diff --git a/builder/default.nix b/builder/default.nix index 75f0043..bfe3298 100644 --- a/builder/default.nix +++ b/builder/default.nix @@ -8,11 +8,14 @@ }: let + parseGoMod = import ./parser.nix; + removeExpr = refs: ''remove-references-to ${lib.concatMapStrings (ref: " -t ${ref}") refs}''; buildGoApplication = { modules , src + , pwd ? null , CGO_ENABLED ? "0" , nativeBuildInputs ? [ ] , allowGoReference ? false @@ -23,6 +26,22 @@ let let modulesStruct = builtins.fromTOML (builtins.readFile modules); + goMod = parseGoMod (builtins.readFile "${builtins.toString pwd}/go.mod"); + localReplaceCommands = + let + localReplaceAttrs = lib.filterAttrs (n: v: lib.hasAttr "path" v) goMod.replace; + commands = ( + lib.mapAttrsToList + (name: value: ( + '' + mkdir -p $(dirname vendor/${name}) + ln -s ${pwd + "/${value.path}"} vendor/${name} + '' + )) + localReplaceAttrs); + in + if pwd != null then commands else [ ]; + vendorEnv = runCommand "vendor-env" { nativeBuildInputs = [ go ]; @@ -50,6 +69,9 @@ let export GOPATH="$TMPDIR/go" go run ${./symlink.go} + ${lib.concatStringsSep "\n" localReplaceCommands} + + find vendor mv vendor $out '' diff --git a/builder/parser.nix b/builder/parser.nix new file mode 100644 index 0000000..3c6131d --- /dev/null +++ b/builder/parser.nix @@ -0,0 +1,134 @@ +# 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; + + # 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: + let + m = match "(.*)( |)//.*" l; + hasComment = m != null; + in + stripStr (if hasComment then elemAt m 0 else l)) + 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 == "(" then { + ${inDirective} = { }; + } else if inDirective != null then { + ${inDirective} = acc.data.${inDirective} // { ${directive} = rest; }; + } else { + ${directive} = rest; + } + ); + inherit inDirective; + }) + { + inDirective = null; + data = { }; + } + 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 + (n: v: + let + m = match "=> (.+?) (.+)" v; + m2 = match "=> (.*+)" v; + in + if m != null then { + goPackagePath = elemAt m 0; + version = parseVersion (elemAt m 1); + } else { + path = elemAt m2 0; + }) + data.replace; + } + ); + + parseRequire = data: ( + data // { + require = mapAttrs (n: v: parseVersion v) data.require; + } + ); + + splitString = sep: s: filter (t: t != [ ]) (split sep s); + +in +contents: +foldl' (acc: f: f acc) (splitString "\n" contents) [ + stripLines + parseLines + normaliseDirectives + parseReplace + parseRequire +]