You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.3 KiB
152 lines
5.3 KiB
{ pkgs ? import <nixpkgs> {} }: |
|
let |
|
lib = pkgs.lib; |
|
inherit (builtins) baseNameOf dirOf length genList pathExists isString match |
|
replaceStrings readDir; |
|
inherit (lib) take splitString last foldl foldr head tail removePrefix |
|
hasSuffix removeSuffix flatten crossLists reverseList all any unique hasPrefix |
|
cleanSourceWith; |
|
|
|
inherit (pkgs) runCommand symlinkJoin; |
|
|
|
butlast = list: take (length list - 1) list; |
|
|
|
doFileSuffix = "do"; |
|
|
|
genPatterns = path: let |
|
fn = baseNameOf path; |
|
in (map (x: "default." + x) |
|
(foldr (a: b: let x = (head b); in [ (a + "." + x) ] ++ b) |
|
[ doFileSuffix ] |
|
(tail (splitString "." fn)))); |
|
|
|
genPathlist = path: let |
|
pathlist = (splitString "/" path); |
|
foldedpaths = foldr (a: b: |
|
let |
|
h = if length b == 0 then "" else head b; |
|
in [ (h + a + "/") ] ++ b) [] (tail (reverseList pathlist)); |
|
in foldedpaths; |
|
|
|
searchPath = path: |
|
let |
|
pathlist = (splitString "/" path); |
|
pat = (last pathlist); |
|
doFile = path + "." + doFileSuffix; |
|
patlist = genPatterns pat; |
|
foldedpaths = genPathlist path; |
|
pths = crossLists (p: pat: p + pat) [ foldedpaths patlist ]; |
|
in [ doFile ] ++ pths; |
|
|
|
whichdo = path: let |
|
pathlist = searchPath path; |
|
in foldl (res: p: if res == "" then if pathExists p then p else res else res) "" pathlist; |
|
|
|
# Redo's $1, the full name of the target |
|
d1 = path: pat: let |
|
dir = (dirOf pat) + "/"; |
|
in removePrefix dir path; |
|
|
|
# Redo's $2, the full name of the target or the name stripped by a suffix for |
|
# a default.* target. |
|
d2 = path: pat: let |
|
dir = (dirOf pat) + "/"; |
|
_path = removePrefix dir path; |
|
_pat = removeSuffix ".${doFileSuffix}" (removePrefix "${dir}default" pat); |
|
_out = removeSuffix _pat _path; |
|
in if _out == "" then _path else _out; |
|
|
|
# The varargs capture combinator used by getSrc. |
|
# Fuctions calling this are expected to supply a function treating one |
|
# argument to the final function and a second function that operates on the |
|
# whole list of arguments. Call the final function with literal null to |
|
# trigger evaluation. |
|
captureFunc = argFun: f: { |
|
args = []; |
|
__functor = self: a: let |
|
_a = argFun a; |
|
in if a == null |
|
then f self.args |
|
else self // { args = self.args ++ [ _a ]; }; |
|
__toString = self: f self.args; |
|
}; |
|
|
|
# Resolve a src given as string relative to cwd and root. |
|
resolveSrc = root: rcwd: a: let |
|
sanitize = s: let _s = replaceStrings [ "/./" ] ["/"] s; in if s != _s then sanitize _s else s; |
|
_a = toString (/. + (root + "/" + rcwd + "/" + (sanitize a))); |
|
in |
|
if isString a then _a else toString a; |
|
|
|
# Add all preceding paths to a src down to the root. Otherwise |
|
# builtins.filterSource will reject it. |
|
augmentedSrcs = root: srcs: let |
|
aug = map (s: (map (p: root + (removeSuffix "/" p)) (genPathlist (removePrefix root s)))) srcs; |
|
in unique (flatten aug); |
|
|
|
# The source tree resolver combinator. |
|
# Takes the source root and cwd of the current builder and a list of paths |
|
# that shall always be rejected. |
|
# Also takes a resolver to derive unknown paths and a function to which the |
|
# resulting set is applied to. |
|
# |
|
# Returns a filter that accepts zero to n paths relative to cwd. Captures the |
|
# whole source tree when called with zero arguments. |
|
getSrc = root: rcwd: rejected: resolver: outFunc: |
|
captureFunc (resolveSrc root rcwd) (srcs: let |
|
|
|
rejector = path: (all (x: path != x) rejected); |
|
|
|
# Filter non-existant sources and build them with resolver. |
|
# Recreate the source directory structure. |
|
nonExistantSrcs = builtins.filter (x: let |
|
isLink = x: let |
|
base = baseNameOf x; |
|
parent = dirOf x; |
|
type = (readDir parent).${base} or null; |
|
in (type == "symlink"); |
|
in !pathExists x || isLink x) srcs; |
|
|
|
builtSrcs = map (s: let |
|
relPath = removePrefix (root + "/") s; |
|
filePath = resolver s; |
|
in runCommand relPath { preferLocalBuild = true; allowSubstitutes = false; } '' |
|
mkdir -p "$out/${dirOf relPath}" |
|
ln -s "${filePath}" "$out/${relPath}" |
|
'') nonExistantSrcs; |
|
|
|
# If a src in srcs is a prefix to a that (it must be a dir, then, or the |
|
# file itself), allow all sub-paths. |
|
srcFilter = path: |
|
if srcs == [] |
|
then true |
|
else (any (x: (hasPrefix x path)) (builtins.filter pathExists srcs)); |
|
|
|
# Allow all directories lying on the way to specified srcs. |
|
_srcs = (augmentedSrcs root srcs); |
|
srcDirFilter = path: if srcs == [] |
|
then true # No explicit srcs means: Take the whole source tree! |
|
else (any (x: path == x) (_srcs)); |
|
|
|
# Filter the sources by our defined criteria and create a source tree |
|
# derivation. |
|
filter = (path: type: |
|
(baseNameOf path != ".git") && |
|
(baseNameOf path != ".envrc") && |
|
(rejector path) && |
|
((srcFilter path) || |
|
(srcDirFilter path))); |
|
filteredSrcs = cleanSourceWith { inherit filter; src = root; }; |
|
|
|
# Merge the filtered sources and the newly built dependencies. |
|
mergedSrcs = symlinkJoin { name = "srcs"; paths = [ filteredSrcs ] ++ builtSrcs; }; |
|
|
|
in outFunc mergedSrcs); |
|
|
|
self = { |
|
inherit doFileSuffix whichdo d1 d2 getSrc; |
|
# For testing. To be removed. |
|
inherit searchPath augmentedSrcs genPathlist captureFunc; |
|
}; |
|
|
|
in self
|
|
|