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).
This commit is contained in:
Hamish Mackenzie 2021-01-14 12:34:06 +13:00 committed by GitHub
parent faaca67355
commit 64efc98852
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 383 additions and 105 deletions

View File

@ -77,7 +77,27 @@ let self =
let
# TODO fix cabal wildcard support so hpack wildcards can be mapped to cabal wildcards
canCleanSource = !(cabal-generator == "hpack" && !(package.cleanHpack or false));
cleanSrc = if canCleanSource then haskellLib.cleanCabalComponent package component src else src;
# In order to support relative references to other packages we need to use
# the `origSrc` diretory as the root `src` for the derivation.
# We use `rootAndSubDir` here to split the cleaned source into a `cleanSrc.root`
# path (that respects the filtering) and a `cleanSrc.subDir` that
# is the sub directory in that root path that contains the package.
# `cleanSrc.subDir` is used in `prePatch` and `lib/cover.nix`.
cleanSrc = haskellLib.rootAndSubDir (if canCleanSource
then haskellLib.cleanCabalComponent package component "${componentId.ctype}-${componentId.cname}" src
else
# We can clean out the siblings though to at least avoid changes to other packages
# from triggering a rebuild of this one.
# Passing `subDir` but not `includeSiblings = true;` will exclude anything not
# in the `subDir`.
if src ? origSrc && src ? filter && src.origSubDir or "" != ""
then haskellLib.cleanSourceWith {
name = src.name or "source";
src = src.origSrc;
subDir = lib.removePrefix "/" src.origSubDir;
inherit (src) filter;
}
else src);
nameOnly = "${package.identifier.name}-${componentId.ctype}-${componentId.cname}";
@ -189,7 +209,7 @@ let
# Attributes that are common to both the build and haddock derivations
commonAttrs = {
src = cleanSrc;
src = cleanSrc.root;
LANG = "en_US.UTF-8"; # GHC needs the locale configured during the Haddock phase.
LC_ALL = "en_US.UTF-8";
@ -198,14 +218,27 @@ let
SETUP_HS = setup + /bin/Setup;
prePatch = if (cabalFile != null)
then ''cat ${cabalFile} > ${package.identifier.name}.cabal''
else
# When building hpack package we use the internal nix-tools
# (compiled with a fixed GHC version)
lib.optionalString (cabal-generator == "hpack") ''
${buildPackages.haskell-nix.internal-nix-tools}/bin/hpack
'';
prePatch =
# If the package is in a sub directory `cd` there first.
# In some cases the `cleanSrc.subDir` will be empty and the `.cabal`
# file will be in the root of `src` (`cleanSrc.root`). This
# will happen when:
# * the .cabal file is in the projects `src.origSrc or src`
# * the package src was overridden with a value that does not
# include an `origSubDir`
(lib.optionalString (cleanSrc.subDir != "") ''
cd ${lib.removePrefix "/" cleanSrc.subDir}
''
) +
(if cabalFile != null
then ''cat ${cabalFile} > ${package.identifier.name}.cabal''
else
# When building hpack package we use the internal nix-tools
# (compiled with a fixed GHC version)
lib.optionalString (cabal-generator == "hpack") ''
${buildPackages.haskell-nix.internal-nix-tools}/bin/hpack
''
);
}
# patches can (if they like) depend on the version and revision of the package.
// lib.optionalAttrs (patches != []) {
@ -244,7 +277,9 @@ let
passthru = {
inherit (package) identifier;
config = component;
inherit configFiles executableToolDepends cleanSrc exeName;
srcSubDir = cleanSrc.subDir;
srcSubDirPath = cleanSrc.root + cleanSrc.subDir;
inherit configFiles executableToolDepends exeName;
exePath = drv + "/bin/${exeName}";
env = shellWrappers;
profiled = self (drvArgs // { enableLibraryProfiling = true; });

View File

@ -118,7 +118,7 @@ in rec {
checks = pkgs.recurseIntoAttrs (builtins.mapAttrs
(_: d: haskellLib.check d)
(lib.filterAttrs (_: d: d.config.doCheck) components.tests));
inherit (package) identifier detailLevel isLocal;
inherit (package) identifier detailLevel isLocal isProject;
inherit setup cabalFile;
isHaskell = true;
inherit src;

View File

@ -5,10 +5,12 @@
, prePatch ? null, postPatch ? null
, preBuild ? component.preBuild , postBuild ? component.postBuild
, preInstall ? component.preInstall , postInstall ? component.postInstall
, cleanSrc ? haskellLib.cleanCabalComponent package component src
, cleanSrc ? haskellLib.cleanCabalComponent package component "setup" src
}:
let
cleanSrc' = haskellLib.rootAndSubDir cleanSrc;
fullName = "${name}-setup";
includeGhcPackage = lib.any (p: p.identifier.name == "ghc") component.depends;
@ -35,7 +37,7 @@ let
drv =
stdenv.mkDerivation ({
name = "${ghc.targetPrefix}${fullName}";
src = cleanSrc;
src = cleanSrc'.root;
buildInputs = component.libs
++ component.frameworks
++ builtins.concatLists component.pkgconfig;
@ -44,7 +46,10 @@ let
passthru = {
inherit (package) identifier;
config = component;
inherit configFiles cleanSrc;
srcSubDir = cleanSrc'.subDir;
srcSubDirPath = cleanSrc'.root + cleanSrc'.subDir;
cleanSrc = cleanSrc';
inherit configFiles;
};
meta = {
@ -83,6 +88,13 @@ let
runHook postInstall
'';
}
// (lib.optionalAttrs (cleanSrc'.subDir != "") {
prePatch =
# If the package is in a sub directory `cd` there first
''
cd ${lib.removePrefix "/" cleanSrc'.subDir}
'';
})
// (lib.optionalAttrs (patches != []) { patches = map (p: if builtins.isFunction p then p { inherit (package.identifier) version; inherit revision; } else p) patches; })
// hooks
);

View File

@ -1,6 +1,15 @@
This file contains a summary of changes to Haskell.nix and `nix-tools`
that will impact users.
## Jan 14, 2021
* Added support for cross package refs (with a project). Relative
directory references between packages within a project should now
work.
* Added `includeSiblings` to `cleanSourceWith`. When `true` it
prevents the `subDir` arg from causing filtering of other directories.
* Added `keepGitDir` to `cleanGit` to allow `.git` directory to be kept
(useful for components that use the `githash` package).
## Nov 26, 2020
* Renamed `otherShells` arg for `shellFor` to `inputsFrom

View File

@ -1,89 +1,127 @@
# Use cleanSourceWith to filter just the files needed for a particular
# component of the package
{ lib, cleanSourceWith }: package: component: src:
{ lib, cleanSourceWith, canCleanSource }: package: component: componentName: src:
let
srcStr' = src.origSrcSubDir or src.origSrc or null;
srcStr' = src.origSrc or null;
subDir = if src.origSubDir or "" == ""
then ""
else lib.removePrefix "/" src.origSubDir + "/";
# Remove a directory for each .. part of a path.
removeDotDots = parts: lib.reverseList (
builtins.foldl' (a: b:
if b == ".."
then builtins.tail a
else builtins.concatLists [ [b] a ]) [] parts);
# Transform
# "." -> ""
# "./." -> ""
# "./xyz" -> "xyz"
normalizeRelativePath = rel:
if rel == "." || rel == "./."
then ""
else lib.strings.removePrefix "./" rel;
# Like normalizeRelativePath but with a trailing / when needed
normalizeRelativeDir = dir:
let p = normalizeRelativePath dir;
# "." -> ""
# "./." -> ""
# "./xyz" -> "xyz"
# "../abc" -> ERROR
# "abc/.." -> ""
# "abc/../xyz" -> "xyz"
# "abc/./xyz" -> "abc/xyz"
# "abc/./../xyz" -> "xyz"
# "abc/.././xyz" -> "xyz"
# "abc/" -> "abc/"
normalizeRelativePath = path:
let
# Split the path into component parts and remove the empty ones and single dots.
nonEmptyParts = lib.filter (x: x != "" && x != ".") (lib.splitString "/" path);
in lib.concatStringsSep "/" (removeDotDots nonEmptyParts)
# Keep the trailing slash if there was one.
+ (if lib.hasSuffix "/" path then "/" else "");
isAbsolutePath = path: lib.hasPrefix "/" path;
isRelativePath = path: !(isAbsolutePath path);
normalizePath = path:
(if isAbsolutePath path
then "/"
else ""
) + normalizeRelativePath path;
combinePaths = a: b: if isAbsolutePath b
then b
else normalizePath (a + "/" + b);
# Like normalizePath but with a trailing / when needed
normalizeDir = dir:
let p = normalizePath dir;
in if p == "" || p == "/"
then ""
else if lib.hasSuffix "/" p
then p
else p + "/";
in
if srcStr' == null || package.detailLevel != "FullDetails"
if srcStr' == null || package.detailLevel != "FullDetails" || !canCleanSource src
then src
else
let
srcStr = toString srcStr';
dataDir = normalizeRelativeDir package.dataDir;
hsSourceDirs = builtins.map normalizeRelativeDir component.hsSourceDirs
++ (if component.hsSourceDirs == [] then [""] else []);
includeDirs = builtins.map normalizeRelativeDir component.includeDirs;
dirsNeeded = [dataDir]
dataDir = combinePaths subDir package.dataDir;
hsSourceDirs = builtins.map (d: combinePaths subDir d) component.hsSourceDirs
++ (if component.hsSourceDirs == [] then [subDir] else []);
includeDirs = builtins.map (d: combinePaths subDir d) component.includeDirs;
dirsNeeded = builtins.map (d: combinePaths subDir d) (
[dataDir]
++ hsSourceDirs
++ includeDirs;
++ includeDirs
++ package.licenseFiles
++ package.extraSrcFiles
++ component.extraSrcFiles
++ package.extraDocFiles
++ builtins.map (f: dataDir + f) package.dataFiles
++ otherSourceFiles);
fileMatch = dir: list:
let
prefixes = builtins.map (f: dir + f) (
prefixes = builtins.map (f: combinePaths dir f) (
lib.lists.remove null (lib.lists.flatten (
builtins.map (f: builtins.match "([^*]*)[*].*" f) list)));
exactMatches = builtins.map (f: dataDir + f) (
exactMatches = builtins.map (f: combinePaths dir f) (
lib.lists.remove null (lib.lists.flatten (
builtins.map (f: builtins.match "([^*]*)" f) list)));
in rPath: lib.any (d: lib.strings.hasPrefix d rPath) prefixes
|| lib.any (d: d == rPath) exactMatches;
dataFileMatch = fileMatch dataDir package.dataFiles;
licenseMatch = fileMatch "" package.licenseFiles;
extraSrcMatch = fileMatch "" (
licenseMatch = fileMatch subDir package.licenseFiles;
extraSrcMatch = fileMatch subDir (
package.extraSrcFiles
++ component.extraSrcFiles);
extraDocMatch = fileMatch "" package.extraDocFiles;
otherSourceFiles =
extraDocMatch = fileMatch subDir package.extraDocFiles;
otherSourceFiles = builtins.map (f: combinePaths subDir f) (
component.asmSources
++ component.cmmSources
++ component.cSources
++ component.cxxSources
++ component.jsSources;
++ component.jsSources);
in cleanSourceWith {
inherit src;
filter = path: type:
assert (if !lib.strings.hasPrefix (srcStr + "/") (path + "/")
then throw ("Unexpected path " + path + " (expected something in " + srcStr + "/)")
else true);
let
srcStrLen = lib.strings.stringLength srcStr;
rPath = lib.strings.substring (srcStrLen + 1) (lib.strings.stringLength path - srcStrLen - 1) path;
# This is a handy way to find out why different files are included
# traceReason = reason: v: if v then builtins.trace (rPath + " : " + reason) true else false;
traceReason = reason: v: v;
in
traceReason "directory is needed" (
lib.any (d: lib.strings.hasPrefix (rPath + "/") d) (
dirsNeeded
++ package.licenseFiles
++ package.extraSrcFiles
++ component.extraSrcFiles
++ package.extraDocFiles
++ builtins.map (f: dataDir + f) package.dataFiles
++ otherSourceFiles))
|| traceReason "cabal package definition" (lib.strings.hasSuffix ".cabal" rPath)
|| traceReason "hpack package defintion" (rPath == "package.yaml")
|| traceReason "data file" (lib.strings.hasPrefix dataDir rPath
&& dataFileMatch rPath)
|| traceReason "haskell source dir" (lib.any (d: lib.strings.hasPrefix d rPath) hsSourceDirs)
|| traceReason "include dir" (lib.any (d: lib.strings.hasPrefix d rPath) includeDirs)
|| traceReason "license file" (licenseMatch rPath)
|| traceReason "extra source file" (extraSrcMatch rPath)
|| traceReason "extra doc file" (extraDocMatch rPath)
|| traceReason "other source file" (lib.any (f: f == rPath) otherSourceFiles);
name = src.name or "source" + "-${componentName}";
subDir = lib.removePrefix "/" (src.origSubDir or "");
includeSiblings = true;
src = cleanSourceWith {
src = src.origSrc or src;
filter = path: type:
(!(src ? filter) || src.filter path type) && (
assert (if !lib.strings.hasPrefix (srcStr + "/") (path + "/")
then throw ("Unexpected path " + path + " (expected something in " + srcStr + "/)")
else true);
let
srcStrLen = lib.strings.stringLength srcStr;
rPath = lib.strings.substring (srcStrLen + 1) (lib.strings.stringLength path - srcStrLen - 1) path;
# This is a handy way to find out why different files are included
# traceReason = reason: v: if v then builtins.trace (rPath + " : " + reason) true else false;
traceReason = reason: v: v;
in
traceReason "directory is needed" (
lib.any (d: lib.strings.hasPrefix (rPath + "/") d) dirsNeeded)
|| traceReason "cabal package definition" (lib.strings.hasPrefix subDir rPath
&& lib.strings.hasSuffix ".cabal" rPath)
|| traceReason "hpack package defintion" (lib.strings.hasPrefix subDir rPath
&& rPath == "package.yaml")
|| traceReason "data file" (lib.strings.hasPrefix dataDir rPath
&& dataFileMatch rPath)
|| traceReason "haskell source dir" (lib.any (d: lib.strings.hasPrefix d rPath) hsSourceDirs)
|| traceReason "include dir" (lib.any (d: lib.strings.hasPrefix d rPath) includeDirs)
|| traceReason "license file" (licenseMatch rPath)
|| traceReason "extra source file" (extraSrcMatch rPath)
|| traceReason "extra doc file" (extraDocMatch rPath)
|| traceReason "other source file" (lib.any (f: f == rPath) otherSourceFiles)
);
};
}

View File

@ -1,6 +1,6 @@
# From https://github.com/NixOS/nix/issues/2944
{ lib, runCommand, git, cleanSourceWith }:
{ name ? null, src, subDir ? "" }:
{ name ? null, src, subDir ? "", includeSiblings ? false, keepGitDir ? false }:
# The function call
#
@ -24,26 +24,6 @@ with builtins;
# is shared among multiple invocations of gitSource:
let
filter_from_list = root: files:
let
all_paren_dirs = p:
if p == "." || p == "/"
then []
else [ p ] ++ all_paren_dirs (dirOf p);
whitelist_set = listToAttrs (
concatMap (p:
# Using `origSrcSubDir` (if present) makes it possible to cleanGit src that
# has already been cleaned with cleanSrcWith.
let full_path = root.origSrcSubDir or (toString root) + "/${p}"; in
map (p': { name = p'; value = true; }) (all_paren_dirs full_path)
) files
);
in
p: t: hasAttr (toString p) whitelist_set;
has_prefix = prefix: s:
prefix == builtins.substring 0 (builtins.stringLength prefix) s;
remove_prefix = prefix: s:
builtins.substring
(builtins.stringLength prefix)
@ -84,7 +64,7 @@ then
git_content = lines (readFile (origSrcSubDir + "/.git"));
first_line = head git_content;
prefix = "gitdir: ";
ok = length git_content == 1 && has_prefix prefix first_line;
ok = length git_content == 1 && lib.hasPrefix prefix first_line;
in
if ok
then /. + remove_prefix prefix first_line
@ -148,17 +128,39 @@ then
whitelist = lines (readFile (whitelist_file.out));
filter = filter_from_list src whitelist;
all_paren_dirs = p:
if p == "." || p == "/"
then []
else [ p ] ++ all_paren_dirs (dirOf p);
# All the paths that we need to keep as a set (including parent dirs)
whitelist_set = listToAttrs (
concatMap (p:
# Using `origSrcSubDir` (if present) makes it possible to cleanGit src that
# has already been cleaned with cleanSrcWith.
let full_path = src.origSrcSubDir or (toString src) + "/${p}"; in
map (p': { name = p'; value = true; }) (all_paren_dirs full_path)
) whitelist
);
# Identify files in the `.git` dir
isGitDirPath = path:
path == origSrcSubDir + "/.git"
|| lib.hasPrefix (origSrcSubDir + "/.git/") path;
filter = path: type:
hasAttr (toString path) whitelist_set
|| (keepGitDir && isGitDirPath path);
in
cleanSourceWith {
caller = "cleanGit";
inherit name src subDir filter;
inherit name src subDir includeSiblings filter;
}
else
trace "gitSource.nix: ${origSrcSubDir} does not seem to be a git repository,\nassuming it is a clean checkout." (
trace "haskell-nix.haskellLib.cleanGit: ${origSrcSubDir} does not seem to be a git repository,\nassuming it is a clean checkout." (
cleanSourceWith {
caller = "cleanGit";
inherit name src subDir;
inherit name src subDir includeSiblings;
}
)

View File

@ -54,7 +54,7 @@ let
(l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}")
libs;
srcDirs = map (l: l.src.outPath) libs;
srcDirs = map (l: l.srcSubDirPath) libs;
in pkgs.runCommand "project-coverage-report"
({ nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];

View File

@ -21,7 +21,7 @@ let
mixDir = l: "${l}/share/hpc/vanilla/mix/${l.identifier.name}-${l.identifier.version}";
mixDirs = map mixDir mixLibraries;
srcDirs = map (l: l.src.outPath) mixLibraries;
srcDirs = map (l: l.srcSubDirPath) mixLibraries;
in pkgs.runCommand (name + "-coverage-report")
({nativeBuildInputs = [ (ghc.buildGHC or ghc) pkgs.buildPackages.zip ];

View File

@ -94,7 +94,7 @@ in {
# if it's a project package it has a src attribute set with an origSubDir attribute.
# project packages are a subset of localPackages
isProjectPackage = p: p ? src && p.src ? origSubDir;
isProjectPackage = p: p.isProject or false;
selectProjectPackages = ps: lib.filterAttrs (n: p: p != null && isLocalPackage p && isProjectPackage p) ps;
# Format a componentId as it should appear as a target on the
@ -193,7 +193,7 @@ in {
# Use cleanSourceWith to filter just the files needed for a particular
# component of a package
cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith; };
cleanCabalComponent = import ./clean-cabal-component.nix { inherit lib cleanSourceWith canCleanSource; };
# Clean git directory based on `git ls-files --recurse-submodules`
cleanGit = import ./clean-git.nix {
@ -273,4 +273,52 @@ in {
inherit (import ./cabal-project-parser.nix {
inherit pkgs;
}) parseIndexState parseBlock;
# This function is like
# `src + (if subDir == "" then "" else "/" + subDir)`
# however when `includeSiblings` is set it maintains
# `src.origSrc` if there is one and instead adds to
# `src.origSubDir`. It uses `cleanSourceWith` when possible
# to keep `cleanSourceWith` support in the result.
appendSubDir = { src, subDir, includeSiblings ? false }:
if subDir == ""
then src
else
if haskellLib.canCleanSource src
then haskellLib.cleanSourceWith {
inherit src subDir includeSiblings;
}
else let name = src.name or "source" + "-" + __replaceStrings ["/"] ["-"] subDir;
in if includeSiblings
then rec {
# Keep `src.origSrc` so it can be used to allow references
# to other parts of that root.
inherit name;
origSrc = src.origSrc or src;
origSubDir = src.origSubDir or "" + "/" + subDir;
outPath = origSrc + origSubDir;
}
else {
# We are not going to need other parts of `origSrc` if there
# was one and we can ignore it
inherit name;
outPath = src + "/" + subDir;
};
# Givin a `src` split it into a `root` path (based on `src.origSrc` if
# present) and `subDir` (based on `src.origSubDir). The
# `root` will still use the `filter` of `src` if there was one.
rootAndSubDir = src: rec {
subDir = src.origSubDir or "";
root =
# Use `cleanSourceWith` to make sure the `filter` is still used
if src ? origSrc && src ? filter && subDir != ""
then haskellLib.cleanSourceWith {
name = src.name or "source" + "-root";
src = src.origSrc;
# Not passing src.origSubDir so that the result points `origSrc`
inherit (src) filter;
}
else src.origSrc or src;
};
}

View File

@ -37,11 +37,18 @@ in project // {
then
pkgs.lib.lists.elemAt sourceRepos (
pkgs.lib.strings.toInt (pkgs.lib.strings.removePrefix srcRepoPrefix subDir))
else if haskellLib.canCleanSource srcRoot
then haskellLib.cleanSourceWith { src = srcRoot; inherit subDir; }
else srcRoot + (if subDir == "" then "" else "/" + subDir);
else haskellLib.appendSubDir {
src = srcRoot;
inherit subDir;
includeSiblings = true; # Filtering sibling dirs of the package dir is done in the
# component builder so that relative paths can be used to
# reference project directories not in the package subDir.
};
in oldPkg // {
src = (pkgs.lib).mkDefault packageSrc;
package = oldPkg.package // {
isProject = (pkgs.lib).mkDefault true;
};
}) old;
};
}

View File

@ -132,6 +132,11 @@ in {
default = false;
};
isProject = mkOption {
type = bool;
default = false;
};
ghcOptions = mkOption {
type = nullOr str;
default = null;

View File

@ -1,4 +1,19 @@
final: prev: {
# gitMinimal still ships with perl (breaks for windows cross compilation)
gitReallyMinimal = (
final.git.override {
perlSupport = false;
pythonSupport = false;
withManual = false;
withpcre2 = false;
}
).overrideAttrs (
_: {
# installCheck is broken when perl is disabled
doInstallCheck = false;
}
);
# This can reduce closure size of nix-tools:
# * Eliminates dependency on python3 (70MB)
# * Allows sharing with `fetchgit` as it also uses `gitMinimal` (50MB)

View File

@ -136,6 +136,12 @@ let
testSrcRoot = haskell-nix.haskellLib.cleanGit { src = ../.; subDir = "test"; };
testSrc = subDir: haskell-nix.haskellLib.cleanSourceWith { src = testSrcRoot; inherit subDir; };
# Use the following reproduce issues that may arise on hydra as a
# result of building a snapshot not a git repo.
# testSrcRoot = pkgs.copyPathToStore ./.;
# testSrc = subDir: testSrcRoot + "/${subDir}";
testSrcRootWithGitDir = haskell-nix.haskellLib.cleanGit { src = ../.; subDir = "test"; includeSiblings = true; keepGitDir = true; };
testSrcWithGitDir = subDir: haskell-nix.haskellLib.cleanSourceWith { src = testSrcRootWithGitDir; inherit subDir; includeSiblings = true; };
callTest = x: args: haskell-nix.callPackage x (args // { inherit testSrc; });
# Run unit tests with: nix-instantiate --eval --strict -A unit.tests
@ -203,6 +209,9 @@ let
} // lib.optionalAttrs (!pkgs.haskell-nix.haskellLib.isCrossHost) {
# Haddock is not included with cross compilers currently
sublib-docs = callTest ./sublib-docs { inherit util compiler-nix-name; };
# githash runs git from TH code and this needs a cross compiled git exe
# to work correctly. Cross compiling git is currently brocken.
githash = haskell-nix.callPackage ./githash { inherit compiler-nix-name; testSrc = testSrcWithGitDir; };
};
# This is the same as allTests, but filter out all the key/vaules from the

67
test/githash/default.nix Normal file
View File

@ -0,0 +1,67 @@
{ stdenv, haskell-nix, recurseIntoAttrs, testSrc, compiler-nix-name, runCommand, gitMinimal, buildPackages }:
with stdenv.lib;
let
src = testSrc "githash";
git =
# Using the cross compiled version here, but currently git does not
# seem to cross compile (so this test is disabled for cross compilation in
# the test/default.nix file).
# Using buildPackages here is not right, but at least gets musl64 test to pass.
if stdenv.hostPlatform != stdenv.buildPlatform
then buildPackages.buildPackages.gitReallyMinimal
else gitMinimal;
project = haskell-nix.cabalProject' {
inherit src;
# When haskell.nix has come from the store (e.g. on hydra) we need to provide
# a suitable mock of the cleaned source with a .git dir.
modules = (optional (!(src ? origSrc && __pathExists (src.origSrc + "/.git"))) {
packages.githash-test.src =
rec {
origSrc = runCommand "githash-test-src" { nativeBuildInputs = [ git ]; } ''
mkdir -p $out/test/githash
cd $out
git init
cp -r ${src}/* test/githash
git add test/githash
git -c "user.name=unknown" -c "user.email=unknown" commit -m 'Initial Commit'
'';
origSubDir = "/test/githash";
origSrcSubDir = origSrc + origSubDir;
outPath = origSrcSubDir;
};
}) ++ [{
packages.githash-test.components.exes.githash-test.build-tools = mkForce [ git ];
}];
inherit compiler-nix-name;
};
packages = project.hsPkgs;
githash-test =
packages.githash-test.components.exes.githash-test;
in recurseIntoAttrs {
ifdInputs = {
inherit (project) plan-nix;
};
run = stdenv.mkDerivation {
name = "run-githash-test";
buildCommand = ''
exe="${githash-test}/bin/githash-test${stdenv.hostPlatform.extensions.executable}"
echo Checking that the error message is generated and that it came from the right place:
(${toString githash-test.config.testWrapper} $exe || true) 2>&1 \
| grep "error, called at src/Main.hs:5:13 in main:Main"
touch $out
'';
meta.platforms = platforms.all;
passthru = {
# Used for debugging with nix repl
inherit project packages;
};
};
}

View File

@ -0,0 +1,16 @@
cabal-version: 2.4
name: githash-test
version: 0.1.0.0
license: MIT
author: Hamish Mackenzie
build-type: Simple
extra-source-files: ../../.git/**/*
executable githash-test
hs-source-dirs: src
main-is: Main.hs
build-depends: base >=4.12 && <5,
githash
default-language: Haskell2010
build-tools: git

15
test/githash/src/Main.hs Normal file
View File

@ -0,0 +1,15 @@
{-# LANGUAGE TemplateHaskell #-}
import GitHash
panic :: String -> a
panic msg = error panicMsg
where panicMsg =
concat [ "[panic ", giBranch gi, "@", giHash gi
, " (", giCommitDate gi, ")"
, " (", show (giCommitCount gi), " commits in HEAD)"
, dirty, "] ", msg ]
dirty | giDirty gi = " (uncommitted files present)"
| otherwise = ""
gi = $$tGitInfoCwd
main = panic "oh no!"