diff --git a/flake.nix b/flake.nix index e9d731f0..e9cd04b9 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,13 @@ }; }; + test-cabal2nix = { + dir = "test/cabal2nix"; + overrideInputs = { + inherit nixpkgs flake-parts haskell-flake; + }; + }; + test-with-subdir = { dir = "test/with-subdir"; overrideInputs = { diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index 0859b9a8..10ea3255 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -12,7 +12,7 @@ }: let - mkNewStorePath = name: src: + mkNewStorePath' = name: src: # Since 'src' may be a subdirectory of a store path # (in string form, which means that it isn't automatically # copied), the purpose of cleanSourceWith here is to create a @@ -22,15 +22,39 @@ let name = "${name}"; inherit src; }; -in -name: root: -lib.pipe root - [ - # Avoid rebuilding because of changes in parent directories - (mkNewStorePath "source-${name}") - (x: log.traceDebug "${name}.mkNewStorePath ${x.outPath}" x) + # Avoid rebuilding because of changes in parent directories + mkNewStorePath = name: src: + let newSrc = mkNewStorePath' name src; + in log.traceDebug "${name}.mkNewStorePath ${newSrc}" newSrc; + + callCabal2nix = name: src: + let pkg = self.callCabal2nix name src { }; + in log.traceDebug "${name}.callCabal2nix src=${src} deriver=${pkg.cabal2nixDeriver.outPath}" pkg; + + # Use cached cabal2nix generated nix expression if present, otherwise use IFD (callCabal2nix) + callCabal2NixUnlessCached = name: src: cabal2nixFile: + let path = "${src}/${cabal2nixFile}"; + in + if builtins.pathExists path + then + callPackage name path + else + callCabal2nix name src; + + callPackage = name: nixFilePath: + let pkg = self.callPackage nixFilePath { }; + in log.traceDebug "${name}.callPackage[cabal2nix] ${nixFilePath}" pkg; + + callHackage = name: version: + let pkg = self.callHackage name version { }; + in log.traceDebug "${name}.callHackage ver=${version}" pkg; +in - (root: self.callCabal2nix name root { }) - (x: log.traceDebug "${name}.cabal2nixDeriver ${x.cabal2nixDeriver.outPath}" x) - ] +name: cfg: +# If 'source' is a path, we treat it as such. Otherwise, we assume it's a version (from hackage). +if lib.types.path.check cfg.source +then + callCabal2NixUnlessCached name (mkNewStorePath name cfg.source) cfg.cabal2NixFile +else + callHackage name cfg.source diff --git a/nix/modules/project/packages/default.nix b/nix/modules/project/packages/default.nix index 5a39934c..6bcd6e6e 100644 --- a/nix/modules/project/packages/default.nix +++ b/nix/modules/project/packages/default.nix @@ -58,16 +58,8 @@ in build-haskell-package = import ../../../build-haskell-package.nix { inherit pkgs lib self log; }; - getOrMkPackage = name: cfg: - if lib.types.path.check cfg.source - then - log.traceDebug "${name}.callCabal2nix ${cfg.source}" - (build-haskell-package name cfg.source) - else - log.traceDebug "${name}.callHackage ${cfg.source}" - (self.callHackage name cfg.source { }); in - lib.mapAttrs getOrMkPackage project.config.packages; + lib.mapAttrs build-haskell-package project.config.packages; }; }; } diff --git a/nix/modules/project/packages/package.nix b/nix/modules/project/packages/package.nix index e8921c34..21a5c960 100644 --- a/nix/modules/project/packages/package.nix +++ b/nix/modules/project/packages/package.nix @@ -23,6 +23,16 @@ in ''; }; + cabal2NixFile = lib.mkOption { + type = lib.types.str; + description = '' + Filename of the cabal2nix generated nix expression. + + This gets used if it exists instead of using IFD (callCabal2nix). + ''; + default = "cabal.nix"; + }; + cabal.executables = mkOption { type = types.nullOr (types.listOf types.str); description = '' diff --git a/test/cabal2nix/default.nix b/test/cabal2nix/default.nix new file mode 100644 index 00000000..bbfbbbcc --- /dev/null +++ b/test/cabal2nix/default.nix @@ -0,0 +1,11 @@ +{ mkDerivation, base, lib, random }: +mkDerivation { + pname = "haskell-flake-test"; + version = "0.1.0.0"; + src = ./.; + isLibrary = false; + isExecutable = true; + executableHaskellDepends = [ base random ]; + license = "unknown"; + mainProgram = "haskell-flake-test"; +} diff --git a/test/cabal2nix/flake.nix b/test/cabal2nix/flake.nix new file mode 100644 index 00000000..3bd27951 --- /dev/null +++ b/test/cabal2nix/flake.nix @@ -0,0 +1,35 @@ +{ + # Disable IFD for this test. + nixConfig = { + allow-import-from-derivation = false; + }; + + # Since there is no flake.lock file (to avoid incongruent haskell-flake + # pinning), we must specify revisions for *all* inputs to ensure + # reproducibility. + inputs = { + nixpkgs = { }; + flake-parts = { }; + haskell-flake = { }; + }; + + outputs = inputs@{ self, nixpkgs, flake-parts, ... }: + flake-parts.lib.mkFlake { inherit inputs; } { + systems = nixpkgs.lib.systems.flakeExposed; + imports = [ + inputs.haskell-flake.flakeModule + ]; + debug = true; + perSystem = { config, self', pkgs, lib, ... }: { + haskellProjects.default = { + # If IFD is disabled, + # we need to specify the pre-generated `cabal2nix` expressions + # file to haskell-flake for the package, + # otherwise build would fail as it would use `callCabal2nix` function + # which uses IFD. + packages.haskell-flake-test.cabal2NixFile = "default.nix"; + settings = { }; + }; + }; + }; +} diff --git a/test/cabal2nix/haskell-flake-test.cabal b/test/cabal2nix/haskell-flake-test.cabal new file mode 100644 index 00000000..29661a1a --- /dev/null +++ b/test/cabal2nix/haskell-flake-test.cabal @@ -0,0 +1,20 @@ +cabal-version: 3.0 +name: haskell-flake-test +version: 0.1.0.0 +license: NONE +author: Joe +maintainer: joe@example.com +build-type: Simple + +common warnings + ghc-options: -Wall + +executable haskell-flake-test + import: warnings + main-is: Main.hs + build-depends: + base, + random + hs-source-dirs: src + default-language: Haskell2010 + diff --git a/test/cabal2nix/src/Main.hs b/test/cabal2nix/src/Main.hs new file mode 100644 index 00000000..21d21d0c --- /dev/null +++ b/test/cabal2nix/src/Main.hs @@ -0,0 +1,6 @@ +module Main where + +import System.Random + +main :: IO () +main = putStrLn "Hello, Haskell!"