Skip to content

Commit

Permalink
Fix use of multiple subdirs in cabal.project (input-output-hk#790)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
hamishmack authored and booniepepper committed Feb 4, 2022
1 parent 3dfdf7a commit 284b1eb
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 70 deletions.
93 changes: 93 additions & 0 deletions lib/cabal-project-parser.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
{ pkgs }:
let
span = pred: list:
let n = pkgs.lib.lists.foldr (x: acc: if pred x then acc + 1 else 0) 0 list;
in { fst = pkgs.lib.lists.take n list; snd = pkgs.lib.lists.drop n list; };

# Look for a index-state: field in the cabal.project file
parseIndexState = rawCabalProject:
let
indexState = pkgs.lib.lists.concatLists (
pkgs.lib.lists.filter (l: l != null)
(builtins.map (l: builtins.match "^index-state: *(.*)" l)
(pkgs.lib.splitString "\n" rawCabalProject)));
in
pkgs.lib.lists.head (indexState ++ [ null ]);

# Parse lines of a source-repository-package block
parseBlockLines = blockLines: (pkgs.lib.foldl' ({name, attrs}: s:
let
# Look for a new attribute name
pair = builtins.match " *([^:]*): *(.*)" s;

# Function to build the next parse state when the attribute name is known
nextState = name: value: {
inherit name;
# Support multi line attributes by appending the value to the named attribute
attrs = attrs // {
${name} =
if attrs ? ${name}
then attrs.${name} + " " + value
else value;
};
};
in
if pair != null
then
# First line of a new attribute
nextState (builtins.head pair) (builtins.elemAt pair 1)
else
if name != null
then nextState name s # Append another line to the current attirbute
else __trace "Expected attribute but found `${s}`" { inherit name attrs; }
) { name = null; attrs = {}; } blockLines).attrs;

hashPath = path:
builtins.readFile (pkgs.runCommand "hash-path" { preferLocalBuild = true; }
"echo -n $(${pkgs.nix}/bin/nix-hash --type sha256 --base32 ${path}) > $out");

# Use pkgs.fetchgit if we have a sha256. Add comment like this
# --shar256: 003lm3pm0000hbfmii7xcdd9v20000flxf7gdl2pyxia7p014i8z
# otherwise use __fetchGit.
fetchRepo = cabalProjectFileName: lookupSha256: repo:
builtins.map (subdir:
let sha256 = repo."--sha256" or (lookupSha256 repo);
in (if sha256 != null
then pkgs.evalPackages.fetchgit {
url = repo.location;
rev = repo.tag;
inherit sha256;
}
else
let drv = builtins.fetchGit {
url = repo.location;
ref = repo.tag;
};
in __trace "WARNING: No sha256 found for source-repository-package ${repo.location} ${repo.tag} download may fail in restricted mode (hydra)"
(__trace "Consider adding `--sha256: ${hashPath drv}` to the ${cabalProjectFileName} file or passing in a lookupSha256 argument"
drv)
) + subdir)
(if repo ? subdir
then builtins.map (x: "/" + x) (
pkgs.lib.filter (x: x != "") (pkgs.lib.splitString " " repo.subdir))
else [""]);

# Parse a source-repository-package and fetch it if has `type: git`
parseBlock = cabalProjectFileName: lookupSha256: block:
let
x = span (pkgs.lib.strings.hasPrefix " ") (pkgs.lib.splitString "\n" block);
attrs = parseBlockLines x.fst;
in
if attrs."type" or "" != "git"
then {
sourceRepo = [];
otherText = "\nsource-repository-package\n" + block;
}
else {
sourceRepo = fetchRepo cabalProjectFileName lookupSha256 attrs;
otherText = pkgs.lib.strings.concatStringsSep "\n" x.snd;
};

in {
inherit parseIndexState parseBlockLines parseBlock;
}
68 changes: 3 additions & 65 deletions lib/call-cabal-project-to-nix.nix
Original file line number Diff line number Diff line change
Expand Up @@ -101,21 +101,11 @@ let
)
else null;

# Look for a index-state: field in the cabal.project file
parseIndexState = rawCabalProject:
let
indexState = pkgs.lib.lists.concatLists (
pkgs.lib.lists.filter (l: l != null)
(builtins.map (l: builtins.match "^index-state: *(.*)" l)
(pkgs.lib.splitString "\n" rawCabalProject)));
in
pkgs.lib.lists.head (indexState ++ [ null ]);

index-state-found = if index-state != null
then index-state
else
let cabalProjectIndexState = if rawCabalProject != null
then parseIndexState rawCabalProject
then pkgs.haskell-nix.haskellLib.parseIndexState rawCabalProject
else null;
in
if cabalProjectIndexState != null
Expand Down Expand Up @@ -160,67 +150,15 @@ in
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);

let
span = pred: list:
let n = pkgs.lib.lists.foldr (x: acc: if pred x then acc + 1 else 0) 0 list;
in { fst = pkgs.lib.lists.take n list; snd = pkgs.lib.lists.drop n list; };

# Parse lines of a source-repository-package block
parseBlockLines = blockLines: builtins.listToAttrs (builtins.concatMap (s:
let pair = builtins.match " *([^:]*): *(.*)" s;
in pkgs.lib.optional (pair != null) (pkgs.lib.attrsets.nameValuePair
(builtins.head pair)
(builtins.elemAt pair 1))) blockLines);

hashPath = path:
builtins.readFile (pkgs.runCommand "hash-path" { preferLocalBuild = true; }
"echo -n $(${pkgs.nix}/bin/nix-hash --type sha256 --base32 ${path}) > $out");

# Use pkgs.fetchgit if we have a sha256. Add comment like this
# --shar256: 003lm3pm0000hbfmii7xcdd9v20000flxf7gdl2pyxia7p014i8z
# otherwise use __fetchGit.
fetchRepo = repo:
let sha256 = repo."--sha256" or (lookupSha256 repo);
in (if sha256 != null
then pkgs.evalPackages.fetchgit {
url = repo.location;
rev = repo.tag;
inherit sha256;
}
else
let drv = builtins.fetchGit {
url = repo.location;
ref = repo.tag;
};
in __trace "WARNING: No sha256 found for source-repository-package ${repo.location} ${repo.tag} download may fail in restricted mode (hydra)"
(__trace "Consider adding `--sha256: ${hashPath drv}` to the ${cabalProjectFileName} file or passing in a lookupSha256 argument"
drv)
) + (if repo.subdir or "" == "" then "" else "/" + repo.subdir);

# Parse a source-repository-package and fetch it if has `type: git`
parseBlock = block:
let
x = span (pkgs.lib.strings.hasPrefix " ") (pkgs.lib.splitString "\n" block);
attrs = parseBlockLines x.fst;
in
if attrs."type" or "" != "git"
then {
sourceRepo = [];
otherText = "\nsource-repository-package\n" + block;
}
else {
sourceRepo = [ (fetchRepo attrs) ];
otherText = pkgs.lib.strings.concatStringsSep "\n" x.snd;
};

# Deal with source-repository-packages in a way that will work in
# restricted-eval mode (as long as a sha256 is included).
# Replace source-repository-package blocks that have a sha256 with
# packages: block containing nix sotre paths of the fetched repos.
replaceSoureRepos = projectFile:
let
blocks = pkgs.lib.splitString "\nsource-repository-package\n" projectFile;
blocks = pkgs.lib.splitString "\nsource-repository-package\n" ("\n" + projectFile);
initialText = pkgs.lib.lists.take 1 blocks;
repoBlocks = builtins.map parseBlock (pkgs.lib.lists.drop 1 blocks);
repoBlocks = builtins.map (pkgs.haskell-nix.haskellLib.parseBlock cabalProjectFileName lookupSha256) (pkgs.lib.lists.drop 1 blocks);
sourceRepos = pkgs.lib.lists.concatMap (x: x.sourceRepo) repoBlocks;
otherText = pkgs.evalPackages.writeText "cabal.project" (pkgs.lib.strings.concatStringsSep "\n" (
initialText
Expand Down
6 changes: 5 additions & 1 deletion lib/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -258,5 +258,9 @@ in {
# Find the resolver in the stack.yaml file and fetch it if a sha256 value is provided
fetchResolver = import ./fetch-resolver.nix {
inherit (pkgs.evalPackages) pkgs;
};
};

inherit (import ./cabal-project-parser.nix {
inherit pkgs;
}) parseIndexState parseBlock;
}
72 changes: 68 additions & 4 deletions test/unit.nix
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{ lib, haskellLib }:
{ pkgs, lib, haskellLib }:

let
emptyConfig = {
Expand All @@ -9,7 +9,6 @@ let
library = "library";
sublibs = { };
tests = { };
all = "all";
};
package.identifier.name = "empty";
};
Expand All @@ -22,11 +21,15 @@ let
library = "library";
sublibs = { };
tests = { ttt = "ttt"; };
all = "all";
};
package.identifier.name = "nnn";
};

testRepo = pkgs.fetchgit {
url = "https://github.com/input-output-hk/haskell.nix.git";
rev = "487eea1c249537d34c27f6143dff2b9d5586c657";
sha256 = "077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123";
};
in
lib.runTests {
# identity function for applyComponents
Expand All @@ -38,7 +41,7 @@ lib.runTests {
# map a component to its component name and check these are correct
test-applyComponents-library = {
expr = haskellLib.applyComponents (componentId: component: componentId.cname) emptyConfig;
expected = emptyConfig.components // { library = "empty"; all = "empty"; };
expected = emptyConfig.components // { library = "empty"; };
};

test-applyComponents-components = {
Expand All @@ -51,4 +54,65 @@ lib.runTests {
expr = lib.id 1;
expected = 1;
};

testParseBlock1 = {
expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
type: git
location: https://github.com/input-output-hk/haskell.nix.git
tag: 487eea1c249537d34c27f6143dff2b9d5586c657
--sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
-- end of block
'');
expected = __toJSON {
otherText = "-- end of block\n";
sourceRepo = [testRepo];
};
};

testParseBlock2 = {
expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
type: git
location: https://github.com/input-output-hk/haskell.nix.git
tag: 487eea1c249537d34c27f6143dff2b9d5586c657
--sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
subdir: dir
-- end of block
'');
expected = __toJSON {
otherText = "-- end of block\n";
sourceRepo = [(testRepo + "/dir")];
};
};

testParseBlock3 = {
expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
type: git
location: https://github.com/input-output-hk/haskell.nix.git
tag: 487eea1c249537d34c27f6143dff2b9d5586c657
--sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
subdir: dir1 dir2
-- end of block
'');
expected = __toJSON {
otherText = "-- end of block\n";
sourceRepo = [(testRepo + "/dir1") (testRepo + "/dir2")];
};
};

testParseBlock4 = {
expr = __toJSON (haskellLib.parseBlock "cabal.project" (_: null) ''
type: git
location: https://github.com/input-output-hk/haskell.nix.git
tag: 487eea1c249537d34c27f6143dff2b9d5586c657
--sha256: 077j5j3j86qy1wnabjlrg4dmqy1fv037dyq3xb8ch4ickpxxs123
subdir:
dir1
dir2
-- end of block
'');
expected = __toJSON {
otherText = "-- end of block\n";
sourceRepo = [(testRepo + "/dir1") (testRepo + "/dir2")];
};
};
}

0 comments on commit 284b1eb

Please sign in to comment.