Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions lib/stack-cache-generator.nix
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
# sha256map =
# { "https://github.com/jgm/pandoc-citeproc"."0.17"
# = "0dxx8cp2xndpw3jwiawch2dkrkp15mil7pyx7dvd810pwc22pm2q"; };
, inputMap ? {}
# Maps a dependency git url to a local path / flake
# input, mirroring the `cabalProject` `inputMap`
# argument. When a url is present here it is used
# instead of fetching, so private repositories and
# offline builds work:
# inputMap =
# { "https://github.com/jgm/pandoc-citeproc"
# = inputs.pandoc-citeproc; };
, branchMap ? null
# A way to specify in which branch a git commit can
# be found
Expand Down Expand Up @@ -77,6 +86,9 @@ let
hashPath = path:
builtins.readFile (evalPackages.runCommand "hash-path" { preferLocalBuild = true; }
"echo -n $(${evalPackages.nix}/bin/nix-hash --type sha256 --base32 ${path}) > $out");
# `inputMap` may legitimately be `null` (the option type is
# `nullOr attrs`); treat that the same as an empty map.
inputMap' = if inputMap == null then {} else inputMap;
in with evalPackages.lib;
concatMap (dep:
let
Expand All @@ -91,8 +103,23 @@ concatMap (dep:
location = dep.url;
tag = dep.rev;
};
# When the dependency url is present in `inputMap` we use the
# mapped local path / flake input directly instead of fetching
# it. This mirrors the `cabalProject` `inputMap` resolution in
# `lib/call-cabal-project-to-nix.nix`.
input =
if inputMap' ? "${dep.url}/${dep.rev}"
then inputMap'."${dep.url}/${dep.rev}"
else if inputMap' ? ${dep.url}
then
(if (inputMap'.${dep.url}.rev or null) != dep.rev
then throw "${inputMap'.${dep.url}.rev or "<no rev>"} may not match ${dep.rev} for ${dep.url} use \"${dep.url}/${dep.rev}\" as the inputMap key if ${dep.rev} is a branch or tag that points to ${inputMap'.${dep.url}.rev or "<no rev>"}."
else inputMap'.${dep.url})
else null;
pkgsrc =
if !is-private && sha256 != null
if input != null
then input
else if !is-private && sha256 != null
then evalPackages.fetchgit {
inherit (dep) url rev;
inherit sha256;
Expand All @@ -106,6 +133,10 @@ concatMap (dep:
name = cabalName "${pkgsrc}/${subdir}";
inherit (dep) url rev;
inherit is-private;
sha256 = if !is-private then hashPath pkgsrc else null;
} // (optionalAttrs (subdir != "") { inherit subdir; }))
# A mapped input is used directly as the package `src` by
# `mkCacheModule` (see `overlays/haskell.nix`), so there is
# no need to compute a sha256 for it.
sha256 = if input == null && !is-private then hashPath pkgsrc else null;
} // (optionalAttrs (subdir != "") { inherit subdir; })
// (optionalAttrs (input != null) { inherit input; }))
(dep.subdirs or [ "" ])) repos
21 changes: 21 additions & 0 deletions modules/stack-project.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,27 @@ with types;
= "0dxx8cp2xndpw3jwiawch2dkrkp15mil7pyx7dvd810pwc22pm2q"; };
'';
};
inputMap = mkOption {
type = nullOr attrs;
default = {};
description = ''
Specifies the contents of the git urls referenced by the
`extra-deps` in the stack.yaml file. This is the stack
equivalent of the `cabalProject` `inputMap` argument and is
the recommended way to make private git repositories (served
over ssh/git) and fully offline builds work.

Keys are the repository url (optionally suffixed with
`/<rev>`) and values are the corresponding local path or flake
input. When a key of the form `<url>/<rev>` is present the
value is used directly. When a bare `<url>` key is used the
value's `.rev` attribute is checked against the `commit` in the
stack.yaml file.

For example:
inputMap = { "https://github.com/jgm/pandoc-citeproc" = inputs.pandoc-citeproc; };
'';
};
branchMap = mkOption {
type = nullOr unspecified;
default = null;
Expand Down
19 changes: 15 additions & 4 deletions overlays/haskell.nix
Original file line number Diff line number Diff line change
Expand Up @@ -482,12 +482,17 @@ final: prev: {
# This handles private repositories with the `is-private` argument
# (with `builtins.fetchGit`), as well as handling stack-based projects
# with the `type` argument.
mkCacheLine = { name, url, rev, ref ? null, subdir ? ".", sha256 ? null, cabal-file ? "${name}.cabal", type ? "cabal", is-private ? false }:
mkCacheLine = { name, url, rev, ref ? null, subdir ? ".", sha256 ? null, cabal-file ? "${name}.cabal", type ? "cabal", is-private ? false, input ? null }:
let
# Fetch the entire repo, using either pkgs.fetchgit or
# builtins.fetchGit depending on whether the repo is private.
# When the repo was resolved via `inputMap`
# (see `lib/stack-cache-generator.nix`) use the mapped local
# path / flake input directly instead of fetching it.
entireRepo =
if is-private
if input != null
then input
else if is-private
then
# It doesn't make sense to specify sha256 on a private repo
# because it is not used by buitins.fetchGit.
Expand Down Expand Up @@ -578,10 +583,16 @@ final: prev: {
# TODO: this should be moved into `call-stack-to-nix`
{ packages =
let
repoToAttr = { name, url, rev, ref ? null, sha256 ? null, subdir ? null, is-private ? false, ... }: {
repoToAttr = { name, url, rev, ref ? null, sha256 ? null, subdir ? null, is-private ? false, input ? null, ... }: {
${name} = {
src =
if is-private
# When a dependency url was resolved via `inputMap`
# (see `lib/stack-cache-generator.nix`) use the mapped
# local path / flake input directly instead of
# fetching it again.
if input != null
then input
else if is-private
then
builtins.fetchGit
({ inherit url rev; } //
Expand Down
1 change: 1 addition & 0 deletions test/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ let
ghc-options-stack = callTest ./ghc-options/stack.nix {};
exe-only = callTest ./exe-only { inherit util; };
stack-source-repo = callTest ./stack-source-repo {};
stack-inputMap = callTest ./stack-inputMap { inherit testSrcRoot; };
ghc-lib-reinstallable-cabal = callTest ./ghc-lib-reinstallable/cabal.nix {};
ghc-lib-reinstallable-stack = callTest ./ghc-lib-reinstallable/stack.nix {};
cabal-doctests = callTest ./cabal-doctests { inherit util; };
Expand Down
24 changes: 24 additions & 0 deletions test/stack-inputMap/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{ lib, stdenv, stackProject', testSrc, testSrcRoot, compiler-nix-name, evalPackages }:

let
# The `extra-deps` git url is never fetched: `inputMap` maps it (keyed
# by `<url>/<commit>`) to the local test source tree, so the
# `cabal-simple` package used as the dependency comes from
# `${testSrcRoot}/cabal-simple`. This exercises the `inputMap`
# support added for stack projects and keeps the test offline.
project = stackProject' {
src = testSrc "stack-inputMap";
inputMap = {
"https://github.com/input-output-hk/haskell.nix.git/bc01ebc05a8105035c9449943046b46c8364b932" = testSrcRoot;
};
inherit evalPackages;
};
packages = project.hsPkgs;

in lib.recurseIntoAttrs {
meta.disabled = compiler-nix-name != "ghc984" || stdenv.hostPlatform.isGhcjs;
ifdInputs = {
inherit (project) stack-nix;
};
inherit (packages.stack-inputMap.components) library;
}
8 changes: 8 additions & 0 deletions test/stack-inputMap/package.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: stack-inputMap

dependencies:
- base
- cabal-simple

library:
source-dirs: src
6 changes: 6 additions & 0 deletions test/stack-inputMap/src/Lib.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Lib
( someFunc
) where

someFunc :: IO ()
someFunc = putStrLn "someFunc"
10 changes: 10 additions & 0 deletions test/stack-inputMap/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resolver: lts-23.7

packages:
- .

extra-deps:
- git: https://github.com/input-output-hk/haskell.nix.git
commit: bc01ebc05a8105035c9449943046b46c8364b932
subdirs:
- cabal-simple
Loading