diff --git a/builder/comp-builder.nix b/builder/comp-builder.nix index 8e2eec54..15b7c13a 100644 --- a/builder/comp-builder.nix +++ b/builder/comp-builder.nix @@ -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; }); diff --git a/builder/hspkg-builder.nix b/builder/hspkg-builder.nix index 799664b2..ac9c6c72 100644 --- a/builder/hspkg-builder.nix +++ b/builder/hspkg-builder.nix @@ -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; diff --git a/builder/setup-builder.nix b/builder/setup-builder.nix index 6634fe4b..47d6b16a 100644 --- a/builder/setup-builder.nix +++ b/builder/setup-builder.nix @@ -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 ); diff --git a/changelog.md b/changelog.md index 02acf5e1..55fa8327 100644 --- a/changelog.md +++ b/changelog.md @@ -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 diff --git a/lib/clean-cabal-component.nix b/lib/clean-cabal-component.nix index 5dc6488a..3f721d45 100644 --- a/lib/clean-cabal-component.nix +++ b/lib/clean-cabal-component.nix @@ -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) + ); + }; } diff --git a/lib/clean-git.nix b/lib/clean-git.nix index 6576ff6e..9adcf753 100644 --- a/lib/clean-git.nix +++ b/lib/clean-git.nix @@ -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; } ) diff --git a/lib/cover-project.nix b/lib/cover-project.nix index 18e6ef4d..287a6109 100644 --- a/lib/cover-project.nix +++ b/lib/cover-project.nix @@ -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 ]; diff --git a/lib/cover.nix b/lib/cover.nix index 62243d07..4d17eae0 100644 --- a/lib/cover.nix +++ b/lib/cover.nix @@ -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 ]; diff --git a/lib/default.nix b/lib/default.nix index fea665d8..074f73fd 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -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; + }; } diff --git a/lib/import-and-filter-project.nix b/lib/import-and-filter-project.nix index 7bd8e7e9..61fde47f 100644 --- a/lib/import-and-filter-project.nix +++ b/lib/import-and-filter-project.nix @@ -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; }; } diff --git a/modules/package.nix b/modules/package.nix index e85e5410..dc72329e 100644 --- a/modules/package.nix +++ b/modules/package.nix @@ -132,6 +132,11 @@ in { default = false; }; + isProject = mkOption { + type = bool; + default = false; + }; + ghcOptions = mkOption { type = nullOr str; default = null; diff --git a/overlays/nix-prefetch-git-minimal.nix b/overlays/nix-prefetch-git-minimal.nix index 62d3dcbe..39642930 100644 --- a/overlays/nix-prefetch-git-minimal.nix +++ b/overlays/nix-prefetch-git-minimal.nix @@ -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) diff --git a/test/default.nix b/test/default.nix index 4f1df4af..039200d1 100644 --- a/test/default.nix +++ b/test/default.nix @@ -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 diff --git a/test/githash/default.nix b/test/githash/default.nix new file mode 100644 index 00000000..90dcfa74 --- /dev/null +++ b/test/githash/default.nix @@ -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; + }; + }; +} diff --git a/test/githash/githash-test.cabal b/test/githash/githash-test.cabal new file mode 100644 index 00000000..e9010d78 --- /dev/null +++ b/test/githash/githash-test.cabal @@ -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 + diff --git a/test/githash/src/Main.hs b/test/githash/src/Main.hs new file mode 100644 index 00000000..83f38318 --- /dev/null +++ b/test/githash/src/Main.hs @@ -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!"