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
This commit is contained in:
adisbladis 2021-03-29 16:38:03 +02:00
parent 5dffe7b6ef
commit 442b4154e2
No known key found for this signature in database
GPG key ID: 110BFAD44C6249B7
2 changed files with 156 additions and 0 deletions

View file

@ -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
''

134
builder/parser.nix Normal file
View file

@ -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
]