on iko/ghcjs-patch-master help sign in
Support githash package and cross package refs (#843)

* Adds support for cross package refs (with a project). Relative directory references between packages within a project should now work. * Adds `includeSiblings` to `cleanSourceWith`. When `true` it prevents the `subDir` arg from causing filtering of other directories. * Adds `keepGitDir` to `cleanGit` to allow `.git` directory to be kept (useful for components that use the `githash` package).

commit64efc988522494a482034c324b18706a41557ba7 authorHamish Mackenzie <Hamish.Mackenzie@iohk.io> dateThu 14 Jan 2021, 12:34 (6 years ago) parentfaaca67
16 files changed+383−105
builder/comp-builder.nix+46−11 builder/hspkg-builder.nix+1−1 builder/setup-builder.nix+15−3 changelog.md+9 lib/clean-cabal-component.nix+95−57 lib/clean-git.nix+28−26 lib/cover-project.nix+1−1 lib/cover.nix+1−1 lib/default.nix+50−2 lib/import-and-filter-project.nix+10−3 modules/package.nix+5 overlays/nix-prefetch-git-minimal.nix+15 test/default.nix+9 test/githash/default.nix+67 test/githash/githash-test.cabal+16 test/githash/src/Main.hs+15
builder/comp-builder.nix+46−11
@@ -77,7 +77,27 @@ let self =
7777let
7878 # TODO fix cabal wildcard support so hpack wildcards can be mapped to cabal wildcards
7979 canCleanSource = !(cabal-generator == "hpack" && !(package.cleanHpack or false));
80− cleanSrc = if canCleanSource then haskellLib.cleanCabalComponent package component src else src;
80+ # In order to support relative references to other packages we need to use
81+ # the `origSrc` diretory as the root `src` for the derivation.
82+ # We use `rootAndSubDir` here to split the cleaned source into a `cleanSrc.root`
83+ # path (that respects the filtering) and a `cleanSrc.subDir` that
84+ # is the sub directory in that root path that contains the package.
85+ # `cleanSrc.subDir` is used in `prePatch` and `lib/cover.nix`.
86+ cleanSrc = haskellLib.rootAndSubDir (if canCleanSource
87+ then haskellLib.cleanCabalComponent package component "${componentId.ctype}-${componentId.cname}" src
88+ else
89+ # We can clean out the siblings though to at least avoid changes to other packages
90+ # from triggering a rebuild of this one.
91+ # Passing `subDir` but not `includeSiblings = true;` will exclude anything not
92+ # in the `subDir`.
93+ if src ? origSrc && src ? filter && src.origSubDir or "" != ""
94+ then haskellLib.cleanSourceWith {
95+ name = src.name or "source";
96+ src = src.origSrc;
97+ subDir = lib.removePrefix "/" src.origSubDir;
98+ inherit (src) filter;
99+ }
100+ else src);
81101
82102 nameOnly = "${package.identifier.name}-${componentId.ctype}-${componentId.cname}";
83103
@@ -189,7 +209,7 @@ let
189209
190210 # Attributes that are common to both the build and haddock derivations
191211 commonAttrs = {
192− src = cleanSrc;
212+ src = cleanSrc.root;
193213
194214 LANG = "en_US.UTF-8"; # GHC needs the locale configured during the Haddock phase.
195215 LC_ALL = "en_US.UTF-8";
@@ -198,14 +218,27 @@ let
198218
199219 SETUP_HS = setup + /bin/Setup;
200220
201− prePatch = if (cabalFile != null)
202− then ''cat ${cabalFile} > ${package.identifier.name}.cabal''
203− else
204− # When building hpack package we use the internal nix-tools
205− # (compiled with a fixed GHC version)
206− lib.optionalString (cabal-generator == "hpack") ''
207− ${buildPackages.haskell-nix.internal-nix-tools}/bin/hpack
208− '';
221+ prePatch =
222+ # If the package is in a sub directory `cd` there first.
223+ # In some cases the `cleanSrc.subDir` will be empty and the `.cabal`
224+ # file will be in the root of `src` (`cleanSrc.root`). This
225+ # will happen when:
226+ # * the .cabal file is in the projects `src.origSrc or src`
227+ # * the package src was overridden with a value that does not
228+ # include an `origSubDir`
229+ (lib.optionalString (cleanSrc.subDir != "") ''
230+ cd ${lib.removePrefix "/" cleanSrc.subDir}
231+ ''
232+ ) +
233+ (if cabalFile != null
234+ then ''cat ${cabalFile} > ${package.identifier.name}.cabal''
235+ else
236+ # When building hpack package we use the internal nix-tools
237+ # (compiled with a fixed GHC version)
238+ lib.optionalString (cabal-generator == "hpack") ''
239+ ${buildPackages.haskell-nix.internal-nix-tools}/bin/hpack
240+ ''
241+ );
209242 }
210243 # patches can (if they like) depend on the version and revision of the package.
211244 // lib.optionalAttrs (patches != []) {
@@ -244,7 +277,9 @@ let
244277 passthru = {
245278 inherit (package) identifier;
246279 config = component;
247− inherit configFiles executableToolDepends cleanSrc exeName;
280+ srcSubDir = cleanSrc.subDir;
281+ srcSubDirPath = cleanSrc.root + cleanSrc.subDir;
282+ inherit configFiles executableToolDepends exeName;
248283 exePath = drv + "/bin/${exeName}";
249284 env = shellWrappers;
250285 profiled = self (drvArgs // { enableLibraryProfiling = true; });
builder/hspkg-builder.nix+1−1
@@ -118,7 +118,7 @@ in rec {
118118 checks = pkgs.recurseIntoAttrs (builtins.mapAttrs
119119 (_: d: haskellLib.check d)
120120 (lib.filterAttrs (_: d: d.config.doCheck) components.tests));
121− inherit (package) identifier detailLevel isLocal;
121+ inherit (package) identifier detailLevel isLocal isProject;
122122 inherit setup cabalFile;
123123 isHaskell = true;
124124 inherit src;
builder/setup-builder.nix+15−3
@@ -5,10 +5,12 @@
55, prePatch ? null, postPatch ? null
66, preBuild ? component.preBuild , postBuild ? component.postBuild
77, preInstall ? component.preInstall , postInstall ? component.postInstall
8−, cleanSrc ? haskellLib.cleanCabalComponent package component src
8+, cleanSrc ? haskellLib.cleanCabalComponent package component "setup" src
99}:
1010
1111let
12+ cleanSrc' = haskellLib.rootAndSubDir cleanSrc;
13+
1214 fullName = "${name}-setup";
1315
1416 includeGhcPackage = lib.any (p: p.identifier.name == "ghc") component.depends;
@@ -35,7 +37,7 @@ let
3537 drv =
3638 stdenv.mkDerivation ({
3739 name = "${ghc.targetPrefix}${fullName}";
38− src = cleanSrc;
40+ src = cleanSrc'.root;
3941 buildInputs = component.libs
4042 ++ component.frameworks
4143 ++ builtins.concatLists component.pkgconfig;
@@ -44,7 +46,10 @@ let
4446 passthru = {
4547 inherit (package) identifier;
4648 config = component;
47− inherit configFiles cleanSrc;
49+ srcSubDir = cleanSrc'.subDir;
50+ srcSubDirPath = cleanSrc'.root + cleanSrc'.subDir;
51+ cleanSrc = cleanSrc';
52+ inherit configFiles;
4853 };
4954
5055 meta = {
@@ -83,6 +88,13 @@ let
8388 runHook postInstall
8489 '';
8590 }
91+ // (lib.optionalAttrs (cleanSrc'.subDir != "") {
92+ prePatch =
93+ # If the package is in a sub directory `cd` there first
94+ ''
95+ cd ${lib.removePrefix "/" cleanSrc'.subDir}
96+ '';
97+ })
8698 // (lib.optionalAttrs (patches != []) { patches = map (p: if builtins.isFunction p then p { inherit (package.identifier) version; inherit revision; } else p) patches; })
8799 // hooks
88100 );
changelog.md+9
@@ -1,6 +1,15 @@
11This file contains a summary of changes to Haskell.nix and `nix-tools`
22that will impact users.
33
4+## Jan 14, 2021
5+* Added support for cross package refs (with a project). Relative
6+ directory references between packages within a project should now
7+ work.
8+* Added `includeSiblings` to `cleanSourceWith`. When `true` it
9+ prevents the `subDir` arg from causing filtering of other directories.
10+* Added `keepGitDir` to `cleanGit` to allow `.git` directory to be kept
11+ (useful for components that use the `githash` package).
12+
413## Nov 26, 2020
514* Renamed `otherShells` arg for `shellFor` to `inputsFrom
615
lib/clean-cabal-component.nix+95−57
@@ -1,89 +1,127 @@
11# Use cleanSourceWith to filter just the files needed for a particular
22# component of the package
3−{ lib, cleanSourceWith }: package: component: src:
3+{ lib, cleanSourceWith, canCleanSource }: package: component: componentName: src:
44let
5− srcStr' = src.origSrcSubDir or src.origSrc or null;
5+ srcStr' = src.origSrc or null;
6+ subDir = if src.origSubDir or "" == ""
7+ then ""
8+ else lib.removePrefix "/" src.origSubDir + "/";
9+ # Remove a directory for each .. part of a path.
10+ removeDotDots = parts: lib.reverseList (
11+ builtins.foldl' (a: b:
12+ if b == ".."
13+ then builtins.tail a
14+ else builtins.concatLists [ [b] a ]) [] parts);
615 # Transform
7− # "." -> ""
8− # "./." -> ""
9− # "./xyz" -> "xyz"
10− normalizeRelativePath = rel:
11− if rel == "." || rel == "./."
12− then ""
13− else lib.strings.removePrefix "./" rel;
14− # Like normalizeRelativePath but with a trailing / when needed
15− normalizeRelativeDir = dir:
16− let p = normalizeRelativePath dir;
16+ # "." -> ""
17+ # "./." -> ""
18+ # "./xyz" -> "xyz"
19+ # "../abc" -> ERROR
20+ # "abc/.." -> ""
21+ # "abc/../xyz" -> "xyz"
22+ # "abc/./xyz" -> "abc/xyz"
23+ # "abc/./../xyz" -> "xyz"
24+ # "abc/.././xyz" -> "xyz"
25+ # "abc/" -> "abc/"
26+ normalizeRelativePath = path:
27+ let
28+ # Split the path into component parts and remove the empty ones and single dots.
29+ nonEmptyParts = lib.filter (x: x != "" && x != ".") (lib.splitString "/" path);
30+ in lib.concatStringsSep "/" (removeDotDots nonEmptyParts)
31+ # Keep the trailing slash if there was one.
32+ + (if lib.hasSuffix "/" path then "/" else "");
33+ isAbsolutePath = path: lib.hasPrefix "/" path;
34+ isRelativePath = path: !(isAbsolutePath path);
35+ normalizePath = path:
36+ (if isAbsolutePath path
37+ then "/"
38+ else ""
39+ ) + normalizeRelativePath path;
40+ combinePaths = a: b: if isAbsolutePath b
41+ then b
42+ else normalizePath (a + "/" + b);
43+ # Like normalizePath but with a trailing / when needed
44+ normalizeDir = dir:
45+ let p = normalizePath dir;
1746 in if p == "" || p == "/"
1847 then ""
1948 else if lib.hasSuffix "/" p
2049 then p
2150 else p + "/";
2251in
23− if srcStr' == null || package.detailLevel != "FullDetails"
52+ if srcStr' == null || package.detailLevel != "FullDetails" || !canCleanSource src
2453 then src
2554 else
2655 let
2756 srcStr = toString srcStr';
28− dataDir = normalizeRelativeDir package.dataDir;
29− hsSourceDirs = builtins.map normalizeRelativeDir component.hsSourceDirs
30− ++ (if component.hsSourceDirs == [] then [""] else []);
31− includeDirs = builtins.map normalizeRelativeDir component.includeDirs;
32− dirsNeeded = [dataDir]
57+ dataDir = combinePaths subDir package.dataDir;
58+ hsSourceDirs = builtins.map (d: combinePaths subDir d) component.hsSourceDirs
59+ ++ (if component.hsSourceDirs == [] then [subDir] else []);
60+ includeDirs = builtins.map (d: combinePaths subDir d) component.includeDirs;
61+ dirsNeeded = builtins.map (d: combinePaths subDir d) (
62+ [dataDir]
3363 ++ hsSourceDirs
34− ++ includeDirs;
64+ ++ includeDirs
65+ ++ package.licenseFiles
66+ ++ package.extraSrcFiles
67+ ++ component.extraSrcFiles
68+ ++ package.extraDocFiles
69+ ++ builtins.map (f: dataDir + f) package.dataFiles
70+ ++ otherSourceFiles);
3571 fileMatch = dir: list:
3672 let
37− prefixes = builtins.map (f: dir + f) (
73+ prefixes = builtins.map (f: combinePaths dir f) (
3874 lib.lists.remove null (lib.lists.flatten (
3975 builtins.map (f: builtins.match "([^*]*)[*].*" f) list)));
40− exactMatches = builtins.map (f: dataDir + f) (
76+ exactMatches = builtins.map (f: combinePaths dir f) (
4177 lib.lists.remove null (lib.lists.flatten (
4278 builtins.map (f: builtins.match "([^*]*)" f) list)));
4379 in rPath: lib.any (d: lib.strings.hasPrefix d rPath) prefixes
4480 || lib.any (d: d == rPath) exactMatches;
4581 dataFileMatch = fileMatch dataDir package.dataFiles;
46− licenseMatch = fileMatch "" package.licenseFiles;
47− extraSrcMatch = fileMatch "" (
82+ licenseMatch = fileMatch subDir package.licenseFiles;
83+ extraSrcMatch = fileMatch subDir (
4884 package.extraSrcFiles
4985 ++ component.extraSrcFiles);
50− extraDocMatch = fileMatch "" package.extraDocFiles;
51− otherSourceFiles =
86+ extraDocMatch = fileMatch subDir package.extraDocFiles;
87+ otherSourceFiles = builtins.map (f: combinePaths subDir f) (
5288 component.asmSources
5389 ++ component.cmmSources
5490 ++ component.cSources
5591 ++ component.cxxSources
56− ++ component.jsSources;
92+ ++ component.jsSources);
5793 in cleanSourceWith {
58− inherit src;
59− filter = path: type:
60− assert (if !lib.strings.hasPrefix (srcStr + "/") (path + "/")
61− then throw ("Unexpected path " + path + " (expected something in " + srcStr + "/)")
62− else true);
63− let
64− srcStrLen = lib.strings.stringLength srcStr;
65− rPath = lib.strings.substring (srcStrLen + 1) (lib.strings.stringLength path - srcStrLen - 1) path;
66− # This is a handy way to find out why different files are included
67− # traceReason = reason: v: if v then builtins.trace (rPath + " : " + reason) true else false;
68− traceReason = reason: v: v;
69− in
70− traceReason "directory is needed" (
71− lib.any (d: lib.strings.hasPrefix (rPath + "/") d) (
72− dirsNeeded
73− ++ package.licenseFiles
74− ++ package.extraSrcFiles
75− ++ component.extraSrcFiles
76− ++ package.extraDocFiles
77− ++ builtins.map (f: dataDir + f) package.dataFiles
78− ++ otherSourceFiles))
79− || traceReason "cabal package definition" (lib.strings.hasSuffix ".cabal" rPath)
80− || traceReason "hpack package defintion" (rPath == "package.yaml")
81− || traceReason "data file" (lib.strings.hasPrefix dataDir rPath
82− && dataFileMatch rPath)
83− || traceReason "haskell source dir" (lib.any (d: lib.strings.hasPrefix d rPath) hsSourceDirs)
84− || traceReason "include dir" (lib.any (d: lib.strings.hasPrefix d rPath) includeDirs)
85− || traceReason "license file" (licenseMatch rPath)
86− || traceReason "extra source file" (extraSrcMatch rPath)
87− || traceReason "extra doc file" (extraDocMatch rPath)
88− || traceReason "other source file" (lib.any (f: f == rPath) otherSourceFiles);
94+ name = src.name or "source" + "-${componentName}";
95+ subDir = lib.removePrefix "/" (src.origSubDir or "");
96+ includeSiblings = true;
97+ src = cleanSourceWith {
98+ src = src.origSrc or src;
99+ filter = path: type:
100+ (!(src ? filter) || src.filter path type) && (
101+ assert (if !lib.strings.hasPrefix (srcStr + "/") (path + "/")
102+ then throw ("Unexpected path " + path + " (expected something in " + srcStr + "/)")
103+ else true);
104+ let
105+ srcStrLen = lib.strings.stringLength srcStr;
106+ rPath = lib.strings.substring (srcStrLen + 1) (lib.strings.stringLength path - srcStrLen - 1) path;
107+ # This is a handy way to find out why different files are included
108+ # traceReason = reason: v: if v then builtins.trace (rPath + " : " + reason) true else false;
109+ traceReason = reason: v: v;
110+ in
111+ traceReason "directory is needed" (
112+ lib.any (d: lib.strings.hasPrefix (rPath + "/") d) dirsNeeded)
113+ || traceReason "cabal package definition" (lib.strings.hasPrefix subDir rPath
114+ && lib.strings.hasSuffix ".cabal" rPath)
115+ || traceReason "hpack package defintion" (lib.strings.hasPrefix subDir rPath
116+ && rPath == "package.yaml")
117+ || traceReason "data file" (lib.strings.hasPrefix dataDir rPath
118+ && dataFileMatch rPath)
119+ || traceReason "haskell source dir" (lib.any (d: lib.strings.hasPrefix d rPath) hsSourceDirs)
120+ || traceReason "include dir" (lib.any (d: lib.strings.hasPrefix d rPath) includeDirs)
121+ || traceReason "license file" (licenseMatch rPath)
122+ || traceReason "extra source file" (extraSrcMatch rPath)
123+ || traceReason "extra doc file" (extraDocMatch rPath)
124+ || traceReason "other source file" (lib.any (f: f == rPath) otherSourceFiles)
125+ );
126+ };
89127 }
lib/clean-git.nix+28−26
@@ -1,6 +1,6 @@
11# From https://github.com/NixOS/nix/issues/2944
22{ lib, runCommand, git, cleanSourceWith }:
3−{ name ? null, src, subDir ? "" }:
3+{ name ? null, src, subDir ? "", includeSiblings ? false, keepGitDir ? false }:
44
55# The function call
66#
@@ -24,26 +24,6 @@ with builtins;
2424# is shared among multiple invocations of gitSource:
2525
2626let
27− filter_from_list = root: files:
28− let
29− all_paren_dirs = p:
30− if p == "." || p == "/"
31− then []
32− else [ p ] ++ all_paren_dirs (dirOf p);
33−
34− whitelist_set = listToAttrs (
35− concatMap (p:
36− # Using `origSrcSubDir` (if present) makes it possible to cleanGit src that
37− # has already been cleaned with cleanSrcWith.
38− let full_path = root.origSrcSubDir or (toString root) + "/${p}"; in
39− map (p': { name = p'; value = true; }) (all_paren_dirs full_path)
40− ) files
41− );
42− in
43− p: t: hasAttr (toString p) whitelist_set;
44−
45− has_prefix = prefix: s:
46− prefix == builtins.substring 0 (builtins.stringLength prefix) s;
4727 remove_prefix = prefix: s:
4828 builtins.substring
4929 (builtins.stringLength prefix)
@@ -84,7 +64,7 @@ then
8464 git_content = lines (readFile (origSrcSubDir + "/.git"));
8565 first_line = head git_content;
8666 prefix = "gitdir: ";
87− ok = length git_content == 1 && has_prefix prefix first_line;
67+ ok = length git_content == 1 && lib.hasPrefix prefix first_line;
8868 in
8969 if ok
9070 then /. + remove_prefix prefix first_line
@@ -148,17 +128,39 @@ then
148128
149129 whitelist = lines (readFile (whitelist_file.out));
150130
151− filter = filter_from_list src whitelist;
131+ all_paren_dirs = p:
132+ if p == "." || p == "/"
133+ then []
134+ else [ p ] ++ all_paren_dirs (dirOf p);
135+
136+ # All the paths that we need to keep as a set (including parent dirs)
137+ whitelist_set = listToAttrs (
138+ concatMap (p:
139+ # Using `origSrcSubDir` (if present) makes it possible to cleanGit src that
140+ # has already been cleaned with cleanSrcWith.
141+ let full_path = src.origSrcSubDir or (toString src) + "/${p}"; in
142+ map (p': { name = p'; value = true; }) (all_paren_dirs full_path)
143+ ) whitelist
144+ );
145+
146+ # Identify files in the `.git` dir
147+ isGitDirPath = path:
148+ path == origSrcSubDir + "/.git"
149+ || lib.hasPrefix (origSrcSubDir + "/.git/") path;
150+
151+ filter = path: type:
152+ hasAttr (toString path) whitelist_set
153+ || (keepGitDir && isGitDirPath path);
152154 in
153155 cleanSourceWith {
154156 caller = "cleanGit";
155− inherit name src subDir filter;
157+ inherit name src subDir includeSiblings filter;
156158 }
157159
158160else
159− trace "gitSource.nix: ${origSrcSubDir} does not seem to be a git repository,\nassuming it is a clean checkout." (
161+ trace "haskell-nix.haskellLib.cleanGit: ${origSrcSubDir} does not seem to be a git repository,\nassuming it is a clean checkout." (
160162 cleanSourceWith {
161163 caller = "cleanGit";
162− inherit name src subDir;
164+ inherit name src subDir includeSiblings;
163165 }
164166 )
lib/cover-project.nix+1−1
@@ -54,7 +54,7 @@ let
5454 (l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}")
5555 libs;
5656
57− srcDirs = map (l: l.src.outPath) libs;
57+ srcDirs = map (l: l.srcSubDirPath) libs;
5858
5959in pkgs.runCommand "project-coverage-report"
6060 ({ nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
lib/cover.nix+1−1
@@ -21,7 +21,7 @@ let
2121 mixDir = l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}";
2222 mixDirs = map mixDir mixLibraries;
2323
24− srcDirs = map (l: l.src.outPath) mixLibraries;
24+ srcDirs = map (l: l.srcSubDirPath) mixLibraries;
2525
2626in pkgs.runCommand (name + "-coverage-report")
2727 ({nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];
lib/default.nix+50−2
@@ -94,7 +94,7 @@ in {
9494
9595 # if it's a project package it has a src attribute set with an origSubDir attribute.
9696 # project packages are a subset of localPackages
97− isProjectPackage = p: p ? src && p.src ? origSubDir;
97+ isProjectPackage = p: p.isProject or false;
9898 selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
9999
100100 # Format a componentId as it should appear as a target on the
@@ -193,7 +193,7 @@ in {
193193
194194 # Use cleanSourceWith to filter just the files needed for a particular
195195 # component of a package
196− cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
196+ cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith canCleanSource; };
197197
198198 # Clean git directory based on `git ls-files --recurse-submodules`
199199 cleanGit = import ./clean-git.nix {
@@ -273,4 +273,52 @@ in {
273273 inherit (import ./cabal-project-parser.nix {
274274 inherit pkgs;
275275 }) parseIndexState parseBlock;
276+
277+ # This function is like
278+ # `src + (if subDir == "" then "" else "/" + subDir)`
279+ # however when `includeSiblings` is set it maintains
280+ # `src.origSrc` if there is one and instead adds to
281+ # `src.origSubDir`. It uses `cleanSourceWith` when possible
282+ # to keep `cleanSourceWith` support in the result.
283+ appendSubDir = { src, subDir, includeSiblings ? false }:
284+ if subDir == ""
285+ then src
286+ else
287+ if haskellLib.canCleanSource src
288+ then haskellLib.cleanSourceWith {
289+ inherit src subDir includeSiblings;
290+ }
291+ else let name = src.name or "source" + "-" + __replaceStrings ["/"] ["-"] subDir;
292+ in if includeSiblings
293+ then rec {
294+ # Keep `src.origSrc` so it can be used to allow references
295+ # to other parts of that root.
296+ inherit name;
297+ origSrc = src.origSrc or src;
298+ origSubDir = src.origSubDir or "" + "/" + subDir;
299+ outPath = origSrc + origSubDir;
300+ }
301+ else {
302+ # We are not going to need other parts of `origSrc` if there
303+ # was one and we can ignore it
304+ inherit name;
305+ outPath = src + "/" + subDir;
306+ };
307+
308+ # Givin a `src` split it into a `root` path (based on `src.origSrc` if
309+ # present) and `subDir` (based on `src.origSubDir). The
310+ # `root` will still use the `filter` of `src` if there was one.
311+ rootAndSubDir = src: rec {
312+ subDir = src.origSubDir or "";
313+ root =
314+ # Use `cleanSourceWith` to make sure the `filter` is still used
315+ if src ? origSrc && src ? filter && subDir != ""
316+ then haskellLib.cleanSourceWith {
317+ name = src.name or "source" + "-root";
318+ src = src.origSrc;
319+ # Not passing src.origSubDir so that the result points `origSrc`
320+ inherit (src) filter;
321+ }
322+ else src.origSrc or src;
323+ };
276324}
lib/import-and-filter-project.nix+10−3
@@ -37,11 +37,18 @@ in project // {
3737 then
3838 pkgs.lib.lists.elemAt sourceRepos (
3939 pkgs.lib.strings.toInt (pkgs.lib.strings.removePrefix srcRepoPrefix subDir))
40− else if haskellLib.canCleanSource srcRoot
41− then haskellLib.cleanSourceWith { src = srcRoot; inherit subDir; }
42− else srcRoot + (if subDir == "" then "" else "/" + subDir);
40+ else haskellLib.appendSubDir {
41+ src = srcRoot;
42+ inherit subDir;
43+ includeSiblings = true; # Filtering sibling dirs of the package dir is done in the
44+ # component builder so that relative paths can be used to
45+ # reference project directories not in the package subDir.
46+ };
4347 in oldPkg // {
4448 src = (pkgs.lib).mkDefault packageSrc;
49+ package = oldPkg.package // {
50+ isProject = (pkgs.lib).mkDefault true;
51+ };
4552 }) old;
4653 };
4754 }
modules/package.nix+5
@@ -132,6 +132,11 @@ in {
132132 default = false;
133133 };
134134
135+ isProject = mkOption {
136+ type = bool;
137+ default = false;
138+ };
139+
135140 ghcOptions = mkOption {
136141 type = nullOr str;
137142 default = null;
overlays/nix-prefetch-git-minimal.nix+15
@@ -1,4 +1,19 @@
11final: prev: {
2+ # gitMinimal still ships with perl (breaks for windows cross compilation)
3+ gitReallyMinimal = (
4+ final.git.override {
5+ perlSupport = false;
6+ pythonSupport = false;
7+ withManual = false;
8+ withpcre2 = false;
9+ }
10+ ).overrideAttrs (
11+ _: {
12+ # installCheck is broken when perl is disabled
13+ doInstallCheck = false;
14+ }
15+ );
16+
217 # This can reduce closure size of nix-tools:
318 # * Eliminates dependency on python3 (70MB)
419 # * Allows sharing with `fetchgit` as it also uses `gitMinimal` (50MB)
test/default.nix+9
@@ -136,6 +136,12 @@ let
136136
137137 testSrcRoot = haskell-nix.haskellLib.cleanGit { src = ../.; subDir = "test"; };
138138 testSrc = subDir: haskell-nix.haskellLib.cleanSourceWith { src = testSrcRoot; inherit subDir; };
139+ # Use the following reproduce issues that may arise on hydra as a
140+ # result of building a snapshot not a git repo.
141+ # testSrcRoot = pkgs.copyPathToStore ./.;
142+ # testSrc = subDir: testSrcRoot + "/${subDir}";
143+ testSrcRootWithGitDir = haskell-nix.haskellLib.cleanGit { src = ../.; subDir = "test"; includeSiblings = true; keepGitDir = true; };
144+ testSrcWithGitDir = subDir: haskell-nix.haskellLib.cleanSourceWith { src = testSrcRootWithGitDir; inherit subDir; includeSiblings = true; };
139145 callTest = x: args: haskell-nix.callPackage x (args // { inherit testSrc; });
140146
141147 # Run unit tests with: nix-instantiate --eval --strict -A unit.tests
@@ -203,6 +209,9 @@ let
203209 } // lib.optionalAttrs (!pkgs.haskell-nix.haskellLib.isCrossHost) {
204210 # Haddock is not included with cross compilers currently
205211 sublib-docs = callTest ./sublib-docs { inherit util compiler-nix-name; };
212+ # githash runs git from TH code and this needs a cross compiled git exe
213+ # to work correctly. Cross compiling git is currently brocken.
214+ githash = haskell-nix.callPackage ./githash { inherit compiler-nix-name; testSrc = testSrcWithGitDir; };
206215 };
207216
208217 # This is the same as allTests, but filter out all the key/vaules from the
test/githash/default.nixnew file+67
@@ -0,0 +1,67 @@
1+{ stdenv, haskell-nix, recurseIntoAttrs, testSrc, compiler-nix-name, runCommand, gitMinimal, buildPackages }:
2+
3+with stdenv.lib;
4+
5+let
6+ src = testSrc "githash";
7+ git =
8+ # Using the cross compiled version here, but currently git does not
9+ # seem to cross compile (so this test is disabled for cross compilation in
10+ # the test/default.nix file).
11+ # Using buildPackages here is not right, but at least gets musl64 test to pass.
12+ if stdenv.hostPlatform != stdenv.buildPlatform
13+ then buildPackages.buildPackages.gitReallyMinimal
14+ else gitMinimal;
15+ project = haskell-nix.cabalProject' {
16+ inherit src;
17+ # When haskell.nix has come from the store (e.g. on hydra) we need to provide
18+ # a suitable mock of the cleaned source with a .git dir.
19+ modules = (optional (!(src ? origSrc && __pathExists (src.origSrc + "/.git"))) {
20+ packages.githash-test.src =
21+ rec {
22+ origSrc = runCommand "githash-test-src" { nativeBuildInputs = [ git ]; } ''
23+ mkdir -p $out/test/githash
24+ cd $out
25+ git init
26+ cp -r ${src}/* test/githash
27+ git add test/githash
28+ git -c "user.name=unknown" -c "user.email=unknown" commit -m 'Initial Commit'
29+ '';
30+ origSubDir = "/test/githash";
31+ origSrcSubDir = origSrc + origSubDir;
32+ outPath = origSrcSubDir;
33+ };
34+ }) ++ [{
35+ packages.githash-test.components.exes.githash-test.build-tools = mkForce [ git ];
36+ }];
37+ inherit compiler-nix-name;
38+ };
39+
40+ packages = project.hsPkgs;
41+ githash-test =
42+ packages.githash-test.components.exes.githash-test;
43+
44+in recurseIntoAttrs {
45+ ifdInputs = {
46+ inherit (project) plan-nix;
47+ };
48+
49+ run = stdenv.mkDerivation {
50+ name = "run-githash-test";
51+
52+ buildCommand = ''
53+ exe="${githash-test}/bin/githash-test${stdenv.hostPlatform.extensions.executable}"
54+ echo Checking that the error message is generated and that it came from the right place:
55+ (${toString githash-test.config.testWrapper} $exe || true) 2>&1 \
56+ | grep "error, called at src/Main.hs:5:13 in main:Main"
57+ touch $out
58+ '';
59+
60+ meta.platforms = platforms.all;
61+
62+ passthru = {
63+ # Used for debugging with nix repl
64+ inherit project packages;
65+ };
66+ };
67+}
test/githash/githash-test.cabalnew file+16
@@ -0,0 +1,16 @@
1+cabal-version: 2.4
2+name: githash-test
3+version: 0.1.0.0
4+license: MIT
5+author: Hamish Mackenzie
6+build-type: Simple
7+extra-source-files: ../../.git/**/*
8+
9+executable githash-test
10+ hs-source-dirs: src
11+ main-is: Main.hs
12+ build-depends: base >=4.12 && <5,
13+ githash
14+ default-language: Haskell2010
15+ build-tools: git
16+
test/githash/src/Main.hsnew file+15
@@ -0,0 +1,15 @@
1+{-# LANGUAGE TemplateHaskell #-}
2+import GitHash
3+
4+panic :: String -> a
5+panic msg = error panicMsg
6+ where panicMsg =
7+ concat [ "[panic ", giBranch gi, "@", giHash gi
8+ , " (", giCommitDate gi, ")"
9+ , " (", show (giCommitCount gi), " commits in HEAD)"
10+ , dirty, "] ", msg ]
11+ dirty | giDirty gi = " (uncommitted files present)"
12+ | otherwise = ""
13+ gi = $$tGitInfoCwd
14+
15+main = panic "oh no!"