on iko/ghcjs-patch-master help sign in
Fix use of multiple subdirs in cabal.project (#790)

It is now possible to specify multiple subdirs in a `source-repository-package` block. This change fixes the parser code in haskell.nix so that it understands.

commit251c8998047e775ac84b3b8f884be707f724b301 authorHamish Mackenzie <Hamish.Mackenzie@iohk.io> dateTue 28 Jul 2020, 14:19 (6 years ago) parentee1be41
4 files changed+169−70
lib/cabal-project-parser.nix+93 lib/call-cabal-project-to-nix.nix+3−65 lib/default.nix+5−1 test/unit.nix+68−4
lib/cabal-project-parser.nixnew file+93
@@ -0,0 +1,93 @@
1+{ pkgs }:
2+let
3+ span = pred: list:
4+ let n = pkgs.lib.lists.foldr (x: acc: if pred x then acc + 1 else 0) 0 list;
5+ in { fst = pkgs.lib.lists.take n list; snd = pkgs.lib.lists.drop n list; };
6+
7+ # Look for a index-state: field in the cabal.project file
8+ parseIndexState = rawCabalProject:
9+ let
10+ indexState = pkgs.lib.lists.concatLists (
11+ pkgs.lib.lists.filter (l: l != null)
12+ (builtins.map (l: builtins.match "^index-state: *(.*)" l)
13+ (pkgs.lib.splitString "\n" rawCabalProject)));
14+ in
15+ pkgs.lib.lists.head (indexState ++ [ null ]);
16+
17+ # Parse lines of a source-repository-package block
18+ parseBlockLines = blockLines: (pkgs.lib.foldl' ({name, attrs}: s:
19+ let
20+ # Look for a new attribute name
21+ pair = builtins.match " *([^:]*): *(.*)" s;
22+
23+ # Function to build the next parse state when the attribute name is known
24+ nextState = name: value: {
25+ inherit name;
26+ # Support multi line attributes by appending the value to the named attribute
27+ attrs = attrs // {
28+ ${name} =
29+ if attrs ? ${name}
30+ then attrs.${name} + " " + value
31+ else value;
32+ };
33+ };
34+ in
35+ if pair != null
36+ then
37+ # First line of a new attribute
38+ nextState (builtins.head pair) (builtins.elemAt pair 1)
39+ else
40+ if name != null
41+ then nextState name s # Append another line to the current attirbute
42+ else __trace "Expected attribute but found `${s}`" { inherit name attrs; }
43+ ) { name = null; attrs = {}; } blockLines).attrs;
44+
45+ hashPath = path:
46+ builtins.readFile (pkgs.runCommand "hash-path" { preferLocalBuild = true; }
47+ "echo -n $(${pkgs.nix}/bin/nix-hash --type sha256 --base32 ${path}) > $out");
48+
49+ # Use pkgs.fetchgit if we have a sha256. Add comment like this
50+ # --shar256: 003lm3pm0000hbfmii7xcdd9v20000flxf7gdl2pyxia7p014i8z
51+ # otherwise use __fetchGit.
52+ fetchRepo = cabalProjectFileName: lookupSha256: repo:
53+ builtins.map (subdir:
54+ let sha256 = repo."--sha256" or (lookupSha256 repo);
55+ in (if sha256 != null
56+ then pkgs.evalPackages.fetchgit {
57+ url = repo.location;
58+ rev = repo.tag;
59+ inherit sha256;
60+ }
61+ else
62+ let drv = builtins.fetchGit {
63+ url = repo.location;
64+ ref = repo.tag;
65+ };
66+ in __trace "WARNING: No sha256 found for source-repository-package ${repo.location} ${repo.tag} download may fail in restricted mode (hydra)"
67+ (__trace "Consider adding `--sha256: ${hashPath drv}` to the ${cabalProjectFileName} file or passing in a lookupSha256 argument"
68+ drv)
69+ ) + subdir)
70+ (if repo ? subdir
71+ then builtins.map (x: "/" + x) (
72+ pkgs.lib.filter (x: x != "") (pkgs.lib.splitString " " repo.subdir))
73+ else [""]);
74+
75+ # Parse a source-repository-package and fetch it if has `type: git`
76+ parseBlock = cabalProjectFileName: lookupSha256: block:
77+ let
78+ x = span (pkgs.lib.strings.hasPrefix " ") (pkgs.lib.splitString "\n" block);
79+ attrs = parseBlockLines x.fst;
80+ in
81+ if attrs."type" or "" != "git"
82+ then {
83+ sourceRepo = [];
84+ otherText = "\nsource-repository-package\n" + block;
85+ }
86+ else {
87+ sourceRepo = fetchRepo cabalProjectFileName lookupSha256 attrs;
88+ otherText = pkgs.lib.strings.concatStringsSep "\n" x.snd;
89+ };
90+
91+in {
92+ inherit parseIndexState parseBlockLines parseBlock;
93+}
lib/call-cabal-project-to-nix.nix+3−65
@@ -101,21 +101,11 @@ let
101101 )
102102 else null;
103103
104− # Look for a index-state: field in the cabal.project file
105− parseIndexState = rawCabalProject:
106− let
107− indexState = pkgs.lib.lists.concatLists (
108− pkgs.lib.lists.filter (l: l != null)
109− (builtins.map (l: builtins.match "^index-state: *(.*)" l)
110− (pkgs.lib.splitString "\n" rawCabalProject)));
111− in
112− pkgs.lib.lists.head (indexState ++ [ null ]);
113−
114104 index-state-found = if index-state != null
115105 then index-state
116106 else
117107 let cabalProjectIndexState = if rawCabalProject != null
118− then parseIndexState rawCabalProject
108+ then pkgs.haskell-nix.haskellLib.parseIndexState rawCabalProject
119109 else null;
120110 in
121111 if cabalProjectIndexState != null
@@ -160,67 +150,15 @@ in
160150 then throw "Unknown index-state ${index-state-found}, the latest index-state I know about is ${pkgs.lib.last (builtins.attrNames index-state-hashes)}. You may need to update to a newer hackage.nix." else true);
161151
162152let
163− span = pred: list:
164− let n = pkgs.lib.lists.foldr (x: acc: if pred x then acc + 1 else 0) 0 list;
165− in { fst = pkgs.lib.lists.take n list; snd = pkgs.lib.lists.drop n list; };
166−
167− # Parse lines of a source-repository-package block
168− parseBlockLines = blockLines: builtins.listToAttrs (builtins.concatMap (s:
169− let pair = builtins.match " *([^:]*): *(.*)" s;
170− in pkgs.lib.optional (pair != null) (pkgs.lib.attrsets.nameValuePair
171− (builtins.head pair)
172− (builtins.elemAt pair 1))) blockLines);
173−
174− hashPath = path:
175− builtins.readFile (pkgs.runCommand "hash-path" { preferLocalBuild = true; }
176− "echo -n $(${pkgs.nix}/bin/nix-hash --type sha256 --base32 ${path}) > $out");
177−
178− # Use pkgs.fetchgit if we have a sha256. Add comment like this
179− # --shar256: 003lm3pm0000hbfmii7xcdd9v20000flxf7gdl2pyxia7p014i8z
180− # otherwise use __fetchGit.
181− fetchRepo = repo:
182− let sha256 = repo."--sha256" or (lookupSha256 repo);
183− in (if sha256 != null
184− then pkgs.evalPackages.fetchgit {
185− url = repo.location;
186− rev = repo.tag;
187− inherit sha256;
188− }
189− else
190− let drv = builtins.fetchGit {
191− url = repo.location;
192− ref = repo.tag;
193− };
194− in __trace "WARNING: No sha256 found for source-repository-package ${repo.location} ${repo.tag} download may fail in restricted mode (hydra)"
195− (__trace "Consider adding `--sha256: ${hashPath drv}` to the ${cabalProjectFileName} file or passing in a lookupSha256 argument"
196− drv)
197− ) + (if repo.subdir or "" == "" then "" else "/" + repo.subdir);
198−
199− # Parse a source-repository-package and fetch it if has `type: git`
200− parseBlock = block:
201− let
202− x = span (pkgs.lib.strings.hasPrefix " ") (pkgs.lib.splitString "\n" block);
203− attrs = parseBlockLines x.fst;
204− in
205− if attrs."type" or "" != "git"
206− then {
207− sourceRepo = [];
208− otherText = "\nsource-repository-package\n" + block;
209− }
210− else {
211− sourceRepo = [ (fetchRepo attrs) ];
212− otherText = pkgs.lib.strings.concatStringsSep "\n" x.snd;
213− };
214−
215153 # Deal with source-repository-packages in a way that will work in
216154 # restricted-eval mode (as long as a sha256 is included).
217155 # Replace source-repository-package blocks that have a sha256 with
218156 # packages: block containing nix sotre paths of the fetched repos.
219157 replaceSoureRepos = projectFile:
220158 let
221− blocks = pkgs.lib.splitString "\nsource-repository-package\n" projectFile;
159+ blocks = pkgs.lib.splitString "\nsource-repository-package\n" ("\n" + projectFile);
222160 initialText = pkgs.lib.lists.take 1 blocks;
223− repoBlocks = builtins.map parseBlock (pkgs.lib.lists.drop 1 blocks);
161+ repoBlocks = builtins.map (pkgs.haskell-nix.haskellLib.parseBlock cabalProjectFileName lookupSha256) (pkgs.lib.lists.drop 1 blocks);
224162 sourceRepos = pkgs.lib.lists.concatMap (x: x.sourceRepo) repoBlocks;
225163 otherText = pkgs.evalPackages.writeText "cabal.project" (pkgs.lib.strings.concatStringsSep "\n" (
226164 initialText
lib/default.nix+5−1
@@ -258,5 +258,9 @@ in {
258258 # Find the resolver in the stack.yaml file and fetch it if a sha256 value is provided
259259 fetchResolver = import ./fetch-resolver.nix {
260260 inherit (pkgs.evalPackages) pkgs;
261− };
261+ };
262+
263+ inherit (import ./cabal-project-parser.nix {
264+ inherit pkgs;
265+ }) parseIndexState parseBlock;
262266}
test/unit.nix+68−4
@@ -1,4 +1,4 @@
1−{ lib, haskellLib }:
1+{ pkgs, lib, haskellLib }:
22
33let
44 emptyConfig = {
@@ -9,7 +9,6 @@ let
99 library = "library";
1010 sublibs = { };
1111 tests = { };
12− all = "all";
1312 };
1413 package.identifier.name = "empty";
1514 };
@@ -22,11 +21,15 @@ let
2221 library = "library";
2322 sublibs = { };
2423 tests = { ttt = "ttt"; };
25− all = "all";
2624 };
2725 package.identifier.name = "nnn";
2826 };
2927
28+ testRepo = pkgs.fetchgit {
29+ url = "https://github.com/input-output-hk/haskell.nix.git";
30+ rev = "487eea1c249537d34c27f6143dff2b9d5586c657";
31+ sha256 = "077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123";
32+ };
3033in
3134lib.runTests {
3235 # identity function for applyComponents
@@ -38,7 +41,7 @@ lib.runTests {
3841 # map a component to its component name and check these are correct
3942 test-applyComponents-library = {
4043 expr = haskellLib.applyComponents (componentId: component: componentId.cname) emptyConfig;
41− expected = emptyConfig.components // { library = "empty"; all = "empty"; };
44+ expected = emptyConfig.components // { library = "empty"; };
4245 };
4346
4447 test-applyComponents-components = {
@@ -51,4 +54,65 @@ lib.runTests {
5154 expr = lib.id 1;
5255 expected = 1;
5356 };
57+
58+ testParseBlock1 = {
59+ expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
60+ type: git
61+ location: https://github.com/input-output-hk/haskell.nix.git
62+ tag: 487eea1c249537d34c27f6143dff2b9d5586c657
63+ --sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
64+ -- end of block
65+ '');
66+ expected = __toJSON {
67+ otherText = "-- end of block\n";
68+ sourceRepo = [testRepo];
69+ };
70+ };
71+
72+ testParseBlock2 = {
73+ expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
74+ type: git
75+ location: https://github.com/input-output-hk/haskell.nix.git
76+ tag: 487eea1c249537d34c27f6143dff2b9d5586c657
77+ --sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
78+ subdir: dir
79+ -- end of block
80+ '');
81+ expected = __toJSON {
82+ otherText = "-- end of block\n";
83+ sourceRepo = [(testRepo + "/dir")];
84+ };
85+ };
86+
87+ testParseBlock3 = {
88+ expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
89+ type: git
90+ location: https://github.com/input-output-hk/haskell.nix.git
91+ tag: 487eea1c249537d34c27f6143dff2b9d5586c657
92+ --sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
93+ subdir: dir1 dir2
94+ -- end of block
95+ '');
96+ expected = __toJSON {
97+ otherText = "-- end of block\n";
98+ sourceRepo = [(testRepo + "/dir1") (testRepo + "/dir2")];
99+ };
100+ };
101+
102+ testParseBlock4 = {
103+ expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
104+ type: git
105+ location: https://github.com/input-output-hk/haskell.nix.git
106+ tag: 487eea1c249537d34c27f6143dff2b9d5586c657
107+ --sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
108+ subdir:
109+ dir1
110+ dir2
111+ -- end of block
112+ '');
113+ expected = __toJSON {
114+ otherText = "-- end of block\n";
115+ sourceRepo = [(testRepo + "/dir1") (testRepo + "/dir2")];
116+ };
117+ };
54118}