at 21.11-pre 7.1 kB view raw
1# Functions for copying sources to the Nix store. 2{ lib }: 3 4let 5 inherit (builtins) 6 hasContext 7 match 8 readDir 9 split 10 storeDir 11 tryEval 12 ; 13 inherit (lib) 14 filter 15 getAttr 16 isString 17 pathExists 18 readFile 19 ; 20in 21rec { 22 23 # Returns the type of a path: regular (for file), symlink, or directory 24 pathType = p: getAttr (baseNameOf p) (readDir (dirOf p)); 25 26 # Returns true if the path exists and is a directory, false otherwise 27 pathIsDirectory = p: if pathExists p then (pathType p) == "directory" else false; 28 29 # Returns true if the path exists and is a regular file, false otherwise 30 pathIsRegularFile = p: if pathExists p then (pathType p) == "regular" else false; 31 32 # Bring in a path as a source, filtering out all Subversion and CVS 33 # directories, as well as backup files (*~). 34 cleanSourceFilter = name: type: let baseName = baseNameOf (toString name); in ! ( 35 # Filter out version control software files/directories 36 (baseName == ".git" || type == "directory" && (baseName == ".svn" || baseName == "CVS" || baseName == ".hg")) || 37 # Filter out editor backup / swap files. 38 lib.hasSuffix "~" baseName || 39 match "^\\.sw[a-z]$" baseName != null || 40 match "^\\..*\\.sw[a-z]$" baseName != null || 41 42 # Filter out generates files. 43 lib.hasSuffix ".o" baseName || 44 lib.hasSuffix ".so" baseName || 45 # Filter out nix-build result symlinks 46 (type == "symlink" && lib.hasPrefix "result" baseName) 47 ); 48 49 # Filters a source tree removing version control files and directories using cleanSourceWith 50 # 51 # Example: 52 # cleanSource ./. 53 cleanSource = src: cleanSourceWith { filter = cleanSourceFilter; inherit src; }; 54 55 # Like `builtins.filterSource`, except it will compose with itself, 56 # allowing you to chain multiple calls together without any 57 # intermediate copies being put in the nix store. 58 # 59 # lib.cleanSourceWith { 60 # filter = f; 61 # src = lib.cleanSourceWith { 62 # filter = g; 63 # src = ./.; 64 # }; 65 # } 66 # # Succeeds! 67 # 68 # builtins.filterSource f (builtins.filterSource g ./.) 69 # # Fails! 70 # 71 # Parameters: 72 # 73 # src: A path or cleanSourceWith result to filter and/or rename. 74 # 75 # filter: A function (path -> type -> bool) 76 # Optional with default value: constant true (include everything) 77 # The function will be combined with the && operator such 78 # that src.filter is called lazily. 79 # For implementing a filter, see 80 # https://nixos.org/nix/manual/#builtin-filterSource 81 # 82 # name: Optional name to use as part of the store path. 83 # This defaults to `src.name` or otherwise `"source"`. 84 # 85 cleanSourceWith = { filter ? _path: _type: true, src, name ? null }: 86 let 87 isFiltered = src ? _isLibCleanSourceWith; 88 origSrc = if isFiltered then src.origSrc else src; 89 filter' = if isFiltered then name: type: filter name type && src.filter name type else filter; 90 name' = if name != null then name else if isFiltered then src.name else "source"; 91 in { 92 inherit origSrc; 93 filter = filter'; 94 outPath = builtins.path { filter = filter'; path = origSrc; name = name'; }; 95 _isLibCleanSourceWith = true; 96 name = name'; 97 }; 98 99 # Filter sources by a list of regular expressions. 100 # 101 # E.g. `src = sourceByRegex ./my-subproject [".*\.py$" "^database.sql$"]` 102 sourceByRegex = src: regexes: 103 let 104 isFiltered = src ? _isLibCleanSourceWith; 105 origSrc = if isFiltered then src.origSrc else src; 106 in lib.cleanSourceWith { 107 filter = (path: type: 108 let relPath = lib.removePrefix (toString origSrc + "/") (toString path); 109 in lib.any (re: match re relPath != null) regexes); 110 inherit src; 111 }; 112 113 # Get all files ending with the specified suffices from the given 114 # directory or its descendants. E.g. `sourceFilesBySuffices ./dir 115 # [".xml" ".c"]'. 116 sourceFilesBySuffices = path: exts: 117 let filter = name: type: 118 let base = baseNameOf (toString name); 119 in type == "directory" || lib.any (ext: lib.hasSuffix ext base) exts; 120 in cleanSourceWith { inherit filter; src = path; }; 121 122 pathIsGitRepo = path: (tryEval (commitIdFromGitRepo path)).success; 123 124 # Get the commit id of a git repo 125 # Example: commitIdFromGitRepo <nixpkgs/.git> 126 commitIdFromGitRepo = 127 let readCommitFromFile = file: path: 128 let fileName = toString path + "/" + file; 129 packedRefsName = toString path + "/packed-refs"; 130 absolutePath = base: path: 131 if lib.hasPrefix "/" path 132 then path 133 else toString (/. + "${base}/${path}"); 134 in if pathIsRegularFile path 135 # Resolve git worktrees. See gitrepository-layout(5) 136 then 137 let m = match "^gitdir: (.*)$" (lib.fileContents path); 138 in if m == null 139 then throw ("File contains no gitdir reference: " + path) 140 else 141 let gitDir = absolutePath (dirOf path) (lib.head m); 142 commonDir'' = if pathIsRegularFile "${gitDir}/commondir" 143 then lib.fileContents "${gitDir}/commondir" 144 else gitDir; 145 commonDir' = lib.removeSuffix "/" commonDir''; 146 commonDir = absolutePath gitDir commonDir'; 147 refFile = lib.removePrefix "${commonDir}/" "${gitDir}/${file}"; 148 in readCommitFromFile refFile commonDir 149 150 else if pathIsRegularFile fileName 151 # Sometimes git stores the commitId directly in the file but 152 # sometimes it stores something like: «ref: refs/heads/branch-name» 153 then 154 let fileContent = lib.fileContents fileName; 155 matchRef = match "^ref: (.*)$" fileContent; 156 in if matchRef == null 157 then fileContent 158 else readCommitFromFile (lib.head matchRef) path 159 160 else if pathIsRegularFile packedRefsName 161 # Sometimes, the file isn't there at all and has been packed away in the 162 # packed-refs file, so we have to grep through it: 163 then 164 let fileContent = readFile packedRefsName; 165 matchRef = match "([a-z0-9]+) ${file}"; 166 isRef = s: isString s && (matchRef s) != null; 167 # there is a bug in libstdc++ leading to stackoverflow for long strings: 168 # https://github.com/NixOS/nix/issues/2147#issuecomment-659868795 169 refs = filter isRef (split "\n" fileContent); 170 in if refs == [] 171 then throw ("Could not find " + file + " in " + packedRefsName) 172 else lib.head (matchRef (lib.head refs)) 173 174 else throw ("Not a .git directory: " + path); 175 in readCommitFromFile "HEAD"; 176 177 pathHasContext = builtins.hasContext or (lib.hasPrefix storeDir); 178 179 canCleanSource = src: src ? _isLibCleanSourceWith || !(pathHasContext (toString src)); 180}