/* * \brief Tools and utility functions * \author Emery Hemingway * \date 2014-09-30 */ { nixpkgs ? import { } }: with builtins; rec { inherit nixpkgs; inherit (nixpkgs) fetchurl; ## # Add a prefix to a list of strings. addPrefix = prefix: map (s: prefix+s); ## # Determine if any of the following libs are shared. anyShared = libs: let h = head libs; in if libs == [] then false else if h.shared or false then true else anyShared (tail libs); ## # Drop a suffix from the end of a string. dropSuffix = suf: str: let strL = stringLength str; sufL = stringLength suf; in if lessThan strL sufL || substring (sub strL sufL) strL str != suf then abort "${str} does not have suffix ${suf}" else substring 0 (sub strL sufL) str; ## # Generate a list of file paths from a directory and # filenames. fromDir = dir: map (s: dir+("/"+s)); ## # Utility functions for gathering sources. fromGlob = dir: glob: let dirName = dir.name or baseNameOf (toString dir); in import (shellDerivation { name = "${dirName}-glob.nix"; script = ./from-glob.sh; #PATH="${nixpkgs.coreutils}/bin"; inherit dir glob; }); fromPath = path: [ [ path (baseNameOf (toString path)) ] ]; fromPaths = paths: map (p: [ p (baseNameOf (toString p)) ]) paths; ## # Filter out libs that are not derivations filterFakeLibs = libs: filter (lib: hasAttr "shared" lib) libs; ## # Test if a string ends in '.h'. hasDotH = s: substring (sub (stringLength s) 2) 2 s == ".h"; hasDotHH = s: substring (sub (stringLength s) 3) 3 s == ".hh"; hasDotHPP = s: substring (sub (stringLength s) 4) 4 s == ".hpp"; ## # Filter out everything but *.h on a path. # Prevents files that exist alongside headers from changing header path hash. filterHeaders = dir: filterSource (path: type: hasDotH path || hasDotHH path || hasDotHPP path || type == "directory") dir; ## # Find a filename in a search path. findFile = fn: searchPath: if searchPath == [] then [] else let sp = head searchPath; fn' = sp + "/${fn}"; in if builtins.typeOf fn' != "path" then [] else if pathExists fn' then [ { key = fn'; relative = fn; } ] else findFile fn (tail searchPath); findIncludes = main: path: map (x: [ x.key x.relative ]) (genericClosure { startSet = [ { key = main; relative = baseNameOf (toString main); } ]; operator = { key, ... }: let includes = import (includesOf key); includesFound = nixpkgs.lib.concatMap (fn: findFile fn ([ (dirOf main) ] ++ path)) includes; in includesFound; }); ## # Recursively find libraries. findLibraries = libs: let list = map (lib: { key = lib.name; inherit lib; }); in map (x: x.lib) (genericClosure { startSet = list libs; operator = { key, lib }: list lib.libs or []; }); ## # Recursively find libraries to link. findLinkLibraries = libs: let list = libs: map (lib: { key = lib.name; inherit lib; }) (filter (lib: hasAttr "drvPath" lib) libs); in map (x: x.lib) (genericClosure { startSet = list libs; operator = { key, lib }: if lib.shared then [] else list lib.libs or []; }); findLocalIncludes = main: path: let path' = [ (dirOf main) ] ++ path; in map (x: [ x.key x.relative ]) (genericClosure { startSet = [ { key = main; relative = baseNameOf (toString main); } ]; operator = { key, ... }: let includes = import (localIncludesOf key); includesFound = nixpkgs.lib.concatMap (fn: findFile fn path') includes; in includesFound; }); ## # Recursively find shared libraries. findRuntimeLibraries = libs: let filter = libs: builtins.filter (lib: lib.shared) libs; list = libs: map (lib: { key = lib.name; inherit lib; }) libs; in filter (map (x: x.lib) (genericClosure { startSet = list libs; operator = { key, lib }: list ([lib ] ++ lib.libs); })); ## # Determine if a string has the given suffix. hasSuffix = suf: str: let strL = stringLength str; sufL = stringLength suf; in if lessThan strL sufL then false else substring (sub strL sufL) strL str == suf; includesOf = file: import (derivation { name = if typeOf file == "path" then "${baseNameOf (toString file)}-includes" else "includes"; system = currentSystem; preferLocalBuild = true; builder = "${nixpkgs.perl}/bin/perl"; args = [ ./find-includes.pl ]; inherit file; }); ## # Create a bootable ISO. iso = { name, contents, kernel, kernelArgs }: shellDerivation { name = "${name}.iso"; script = ./iso.sh; PATH="${nixpkgs.coreutils}/bin:${nixpkgs.cdrkit}/bin:${nixpkgs.binutils}/bin"; inherit kernel kernelArgs; inherit (nixpkgs) syslinux cdrkit; sources = map (x: x.source) contents; targets = map (x: x.target) contents; }; ## # Generate a contents list of runtime libraries for a package. # This will go away as tool.runtime matures. libContents = contents: builtins.concatLists (map ( content: map (source: { target = "/"; inherit source; }) content.source.runtime.libs or [] ) contents); localIncludesOf = main: derivation { name = if typeOf main == "path" then "${baseNameOf (toString main)}-local-includes" else "local-includes"; system = currentSystem; preferLocalBuild = true; builder = "${nixpkgs.perl}/bin/perl"; args = [ ./find-local-includes.pl ]; inherit main; }; ## # Merge an attr between two sets. mergeAttr = name: s1: s2: let a1 = getAttr name s1; a2 = getAttr name s2; type1 = typeOf a1; type2 = typeOf a2; in if type1 == "null" then a2 else if type2 == "null" then a1 else if type1 != type2 then abort "cannot merge ${name}s of type ${type1} and ${type2}" else if type1 == "set" then mergeSet a1 a2 else if type1 == "list" then a1 ++ a2 else if type1 == "string" then "${a1} ${a2}" else #if type1 == "int" then add a1 a2 else abort "cannot merge ${type1} ${name} ${toString a1} ${toString a2}"; ## # Merge two sets together. mergeSet = s1: s2: s1 // s2 // (listToAttrs (map (name: { inherit name; value = mergeAttr name s1 s2; }) (attrNames (intersectAttrs s1 s2)) )); ## # Merge a list of sets. mergeSets = sets: if sets == [] then {} else mergeSet (head sets) (mergeSets (tail sets)); newDir = name: contents: derivation { inherit name contents; system = builtins.currentSystem; preferLocalBuild = true; builder = shell; PATH="${nixpkgs.coreutils}/bin"; args = [ "-e" "-c" '' mkdir -p $out ; \ for i in $contents; do cp -Hr $i $out; done '' ]; }; ## # Generate a list of paths from a path and a shell glob. pathsFromGlob = dir: glob: let path = toString dir; in import (derivation { name = "${baseNameOf path}-glob.nix"; args = [ "-e" "-O" "nullglob" ./path-from-glob.sh ]; inherit dir glob path; preferLocalBuild = true; }); preparePort = import ./prepare-port { inherit nixpkgs; }; # Concatenate the named attr found in pkgs. propagate = attrName: pkgs: let pkg = head pkgs; in if pkgs == [] then [] else ( if hasAttr attrName pkg then getAttr attrName pkg else [] ) ++ (propagate attrName (tail pkgs)); ## # Replace substring a with substring b in string s. replaceInString = a: b: s: let al = stringLength a; bl = stringLength b; sl = stringLength s; in if al == 0 then s else if sl == 0 then "" else if ((substring 0 al s) == a) then b+(replaceInString a b (substring al sl s)) else (substring 0 1 s) + (replaceInString a b (substring 1 sl s)); shell = nixpkgs.bash + "/bin/sh"; # Save some typing when creating derivation that use our shell. shellDerivation = { script, ... } @ args: derivation ( (removeAttrs args [ "script" ]) // { system = builtins.currentSystem; builder = shell; args = [ "-e" script ]; } ); singleton = x: [x]; # Bootability is not assured, so its really system image. systemImage = import ./system-image { inherit nixpkgs; }; bootImage = systemImage; # get rid of this wildcard = path: glob: let relativePaths = import (shellDerivation { name = "files.nix"; PATH="${nixpkgs.coreutils}/bin"; script = ./wildcard.sh; inherit path glob; }); in map (rp: (path+"/${rp}")) relativePaths; # Appends string context from another string addContextFrom = a: b: substring 0 0 a + b; # Compares strings not requiring context equality # Obviously, a workaround but works on all Nix versions eqStrings = a: b: addContextFrom b a == addContextFrom a b; ## # Cut a string with a separator and produces a list of strings which were # separated by this separator. e.g., # `splitString "." "foo.bar.baz"' returns ["foo" "bar" "baz"]. # From nixpkgs. splitString = _sep: _s: let sep = addContextFrom _s _sep; s = addContextFrom _sep _s; sepLen = stringLength sep; sLen = stringLength s; lastSearch = sLen - sepLen; startWithSep = startAt: substring startAt sepLen s == sep; recurse = index: startAt: let cutUntil = i: [(substring startAt (i - startAt) s)]; in if index < lastSearch then if startWithSep index then let restartAt = index + sepLen; in cutUntil index ++ recurse restartAt restartAt else recurse (index + 1) startAt else cutUntil sLen; in recurse 0 0; ## # What I thought builtins.match would do. matchPattern = pat: str: concatLists ( map ( l: let m = match pat l; in if m == null then [] else m ) (splitString "\n" str) ); ## # Generate a set of local ("") and system (<>) # preprocessor include directives from a file. relativeIncludes = file: let matches = pattern: lines: concatLists (filter (x: x != null) (map (match pattern) lines)); lines = splitString "\n" (readFile file); in { local = matches ''.*#include\s*"([^>]*)".*'' lines; system = matches ''.*#include\s*<([^>]*)>.*'' lines; }; ## # Find a file in a set of directories. findFile' = key: dirs: if substring 0 1 key == "!" then builtins.trace "found a !key" key else if dirs == [] then null else let abs = (builtins.head dirs) +"/${key}"; in if builtins.pathExists abs then abs else findFile' key (builtins.tail dirs); ## # Generate a set of relative to absolute include mappings from a file. # This set includes a mapping from the orginal file basename to its # absolute path. # # The genericClosure primative applies an operation to a list of sets that # contain the attribute 'key'. This operation returns a similar list of sets, # and genericClosure appends elements of that list that contain a key # that does not already exist in the previous set. All sets returned by this # operation contain a function to resolve the relative path at 'key' into an # absolute one at 'abs', and a function to parse the absolute path at 'abs' # into a list of relative includes at 'inc'. GenericClosure discards any set # with a relative path at 'key' that it has already been seen, and thus due # to lazy evaulation, no relative path is resolved or parsed twice. # # A ! is prepended to the files of the initial set, to differentiate them from # files with unsolved locations and to satisfy the requirement that strings # not directly reference store paths in findFile' includesOfFiles = files: searchPath: let concat = sets: if sets == [] then {} else let x = head sets; in (if x.abs == null || substring 0 1 x.key == "!" then {} else { "${x.key}" = x.abs; }) // concat (tail sets); in concat (genericClosure { # Can the startSet really be filled with elements sharing a key? startSet = map (abs: { key = "!${abs}"; inherit abs; inc = includesOf abs; }) files; operator = { key, abs, inc }: if abs == null then [] else let abs' = abs; in (map (key: rec { inherit key; abs = (findFile' key searchPath); inc = includesOf abs; }) inc.system) ++ (map (key: rec { inherit key; abs = (findFile' key (if typeOf abs' == "path" then searchPath ++ [ (dirOf abs') ] else searchPath)); inc = relativeIncludes abs; }) inc.local); }); ## # Load expressions from a directory path and apply func. loadExpressions = func: path: let dirSet = builtins.readDir path; default = if builtins.hasAttr "default.nix" dirSet then func (import (path + "/default.nix")) else {}; in default // (builtins.listToAttrs (builtins.filter (x: x != {}) (map (name: let type = builtins.getAttr name dirSet; more = loadExpressions func (path + "/${name}"); in if type == "directory" then { inherit name; value = more; } else if (type == "regular" || type == "symlink") && (hasSuffix ".nix" name) then { name = dropSuffix ".nix" name; value = func (import (path+"/${name}")); } else { } ) (builtins.attrNames dirSet) ))); }