From aa28ca6a0f6f5e13e9c7ea845a76028c8776bc9b Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Sun, 8 Dec 2024 18:52:58 +0530 Subject: [PATCH 1/7] feat: use cached cabal2nix expression --- nix/build-haskell-package.nix | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index 0859b9a8..bb9e5e85 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -22,6 +22,20 @@ let name = "${name}"; inherit src; }; + + callPackageKeepDeriver = src: args: + pkgs.haskell.lib.compose.overrideCabal + (orig: { + inherit src; # Override original source path to use new + passthru = orig.passthru or { } // { + # When using cabal2nix, it is often useful + # to debug a failure by inspecting the Nix expression + # generated by cabal2nix. This can be accessed via this + # cabal2nixDeriver field. + cabal2nixDeriver = src; + }; + }) + (self.callPackage src args); in name: root: @@ -31,6 +45,17 @@ lib.pipe root (mkNewStorePath "source-${name}") (x: log.traceDebug "${name}.mkNewStorePath ${x.outPath}" x) - (root: self.callCabal2nix name root { }) - (x: log.traceDebug "${name}.cabal2nixDeriver ${x.cabal2nixDeriver.outPath}" x) + (root: + # Check if cached cabal2nix generated nix expression is present, + # if present use it with callPackage + # to avoid IFD + let pkg = + if lib.pathExists (lib.concatStringsSep "/" [ root "default.nix" ]) + then + callPackageKeepDeriver root { } + else + self.callCabal2nix name root { }; + in + (log.traceDebug "${name}.cabal2nixDeriver ${pkg.cabal2nixDeriver.outPath}" pkg) + ) ] From 762a24ba5a3d85aaff25de6cc1ec16f8e587e13a Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Sun, 8 Dec 2024 19:07:21 +0530 Subject: [PATCH 2/7] chore(log): added trace logs --- nix/build-haskell-package.nix | 6 ++++-- nix/modules/project/packages/default.nix | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index bb9e5e85..8876b962 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -52,9 +52,11 @@ lib.pipe root let pkg = if lib.pathExists (lib.concatStringsSep "/" [ root "default.nix" ]) then - callPackageKeepDeriver root { } + (log.traceDebug "${name}.callPackageKeepDeriver ${root}") + callPackageKeepDeriver root { } else - self.callCabal2nix name root { }; + (log.traceDebug "${name}.callCabal2nix ${root}") + self.callCabal2nix name root { }; in (log.traceDebug "${name}.cabal2nixDeriver ${pkg.cabal2nixDeriver.outPath}" pkg) ) diff --git a/nix/modules/project/packages/default.nix b/nix/modules/project/packages/default.nix index 5a39934c..3d681a3d 100644 --- a/nix/modules/project/packages/default.nix +++ b/nix/modules/project/packages/default.nix @@ -61,8 +61,7 @@ in getOrMkPackage = name: cfg: if lib.types.path.check cfg.source then - log.traceDebug "${name}.callCabal2nix ${cfg.source}" - (build-haskell-package name cfg.source) + (build-haskell-package name cfg.source) else log.traceDebug "${name}.callHackage ${cfg.source}" (self.callHackage name cfg.source { }); From 8eecc9ec7f3ee6613513d16191e64e6948a6af5c Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 10 Dec 2024 14:33:09 +0530 Subject: [PATCH 3/7] refactor: removed unused function; added an option for user to set `cabal2nix` cache file. --- nix/build-haskell-package.nix | 47 +++++++++++------------- nix/modules/project/packages/default.nix | 2 +- nix/modules/project/packages/package.nix | 10 +++++ 3 files changed, 32 insertions(+), 27 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index 8876b962..523b7a78 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -23,41 +23,36 @@ let inherit src; }; - callPackageKeepDeriver = src: args: - pkgs.haskell.lib.compose.overrideCabal - (orig: { - inherit src; # Override original source path to use new - passthru = orig.passthru or { } // { - # When using cabal2nix, it is often useful - # to debug a failure by inspecting the Nix expression - # generated by cabal2nix. This can be accessed via this - # cabal2nixDeriver field. - cabal2nixDeriver = src; - }; - }) - (self.callPackage src args); in -name: root: +name: root: cabal2NixFile: lib.pipe root [ # Avoid rebuilding because of changes in parent directories (mkNewStorePath "source-${name}") (x: log.traceDebug "${name}.mkNewStorePath ${x.outPath}" x) - (root: - # Check if cached cabal2nix generated nix expression is present, + (root: + let path = lib.concatStringsSep "/" [ root "${cabal2NixFile}" ]; + in + # Check if cached cabal2nix generated nix expression is present, # if present use it with callPackage # to avoid IFD - let pkg = - if lib.pathExists (lib.concatStringsSep "/" [ root "default.nix" ]) - then - (log.traceDebug "${name}.callPackageKeepDeriver ${root}") - callPackageKeepDeriver root { } - else + if lib.pathIsRegularFile path + then + (log.traceDebug "${name}.callPackage[Cached] ${path}") + self.callPackage + path + { } + else + let + pkg = (log.traceDebug "${name}.callCabal2nix ${root}") - self.callCabal2nix name root { }; - in - (log.traceDebug "${name}.cabal2nixDeriver ${pkg.cabal2nixDeriver.outPath}" pkg) - ) + self.callCabal2nix + name + root + { }; + in + (log.traceDebug "${name}.cabal2nixDeriver ${pkg.cabal2nixDeriver.outPath}" pkg) + ) ] diff --git a/nix/modules/project/packages/default.nix b/nix/modules/project/packages/default.nix index 3d681a3d..6a8b7cd0 100644 --- a/nix/modules/project/packages/default.nix +++ b/nix/modules/project/packages/default.nix @@ -61,7 +61,7 @@ in getOrMkPackage = name: cfg: if lib.types.path.check cfg.source then - (build-haskell-package name cfg.source) + (build-haskell-package name cfg.source cfg.cabal2NixFile) else log.traceDebug "${name}.callHackage ${cfg.source}" (self.callHackage name cfg.source { }); diff --git a/nix/modules/project/packages/package.nix b/nix/modules/project/packages/package.nix index e8921c34..bbf74e1a 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 = '' + The Nix file which contains cached (pre-generated) `cabal2nix` expressions. + + By default, it refers to `cabal.nix` file. + ''; + default = "cabal.nix"; + }; + cabal.executables = mkOption { type = types.nullOr (types.listOf types.str); description = '' From 3fd19e2d4b5060b4556ea9336201a852aa8f1984 Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 17 Dec 2024 01:06:09 +0530 Subject: [PATCH 4/7] chore: use `pathExists` instead; refactor. --- nix/build-haskell-package.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index 523b7a78..b2b81500 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -33,14 +33,14 @@ lib.pipe root (x: log.traceDebug "${name}.mkNewStorePath ${x.outPath}" x) (root: - let path = lib.concatStringsSep "/" [ root "${cabal2NixFile}" ]; + let path = "${root}/${cabal2NixFile}"; in # Check if cached cabal2nix generated nix expression is present, # if present use it with callPackage # to avoid IFD - if lib.pathIsRegularFile path + if builtins.pathExists path then - (log.traceDebug "${name}.callPackage[Cached] ${path}") + (log.traceDebug "${name}.callPackage[cabal2nix] ${path}") self.callPackage path { } From 2190e17e56f5966d3a36d457794fc0abeb43312c Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Tue, 17 Dec 2024 01:36:43 +0530 Subject: [PATCH 5/7] chore: fmt; refactor --- nix/build-haskell-package.nix | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index b2b81500..f0da63d2 100644 --- a/nix/build-haskell-package.nix +++ b/nix/build-haskell-package.nix @@ -41,18 +41,11 @@ lib.pipe root if builtins.pathExists path then (log.traceDebug "${name}.callPackage[cabal2nix] ${path}") - self.callPackage - path - { } + (self.callPackage path { }) else - let - pkg = - (log.traceDebug "${name}.callCabal2nix ${root}") - self.callCabal2nix - name - root - { }; - in - (log.traceDebug "${name}.cabal2nixDeriver ${pkg.cabal2nixDeriver.outPath}" pkg) + lib.pipe (self.callCabal2nix name root { }) + [ + (pkg: log.traceDebug "${name}.callCabal2nix root=${root} deriver=${pkg.cabal2nixDeriver.outPath}" pkg) + ] ) ] From 792e3e8c7b77e6d716e40a9834dd414a7297e18c Mon Sep 17 00:00:00 2001 From: Rohit Singh Date: Thu, 19 Dec 2024 09:25:21 +0530 Subject: [PATCH 6/7] chore: add tests for `IFD` --- flake.nix | 7 +++++ test/cabal2nix/default.nix | 11 ++++++++ test/cabal2nix/flake.nix | 35 +++++++++++++++++++++++++ test/cabal2nix/haskell-flake-test.cabal | 20 ++++++++++++++ test/cabal2nix/src/Main.hs | 6 +++++ 5 files changed, 79 insertions(+) create mode 100644 test/cabal2nix/default.nix create mode 100644 test/cabal2nix/flake.nix create mode 100644 test/cabal2nix/haskell-flake-test.cabal create mode 100644 test/cabal2nix/src/Main.hs 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/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!" From e9e64e9a722f8342475296bcb703dc78cad5f951 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sun, 22 Dec 2024 20:39:25 -0500 Subject: [PATCH 7/7] refactor: consolidate in one place --- nix/build-haskell-package.nix | 59 ++++++++++++++---------- nix/modules/project/packages/default.nix | 9 +--- nix/modules/project/packages/package.nix | 4 +- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/nix/build-haskell-package.nix b/nix/build-haskell-package.nix index f0da63d2..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 @@ -23,29 +23,38 @@ let inherit src; }; -in + # 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; -name: root: cabal2NixFile: -lib.pipe root - [ - # Avoid rebuilding because of changes in parent directories - (mkNewStorePath "source-${name}") - (x: log.traceDebug "${name}.mkNewStorePath ${x.outPath}" x) + # 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: - let path = "${root}/${cabal2NixFile}"; - in - # Check if cached cabal2nix generated nix expression is present, - # if present use it with callPackage - # to avoid IFD - if builtins.pathExists path - then - (log.traceDebug "${name}.callPackage[cabal2nix] ${path}") - (self.callPackage path { }) - else - lib.pipe (self.callCabal2nix name root { }) - [ - (pkg: log.traceDebug "${name}.callCabal2nix root=${root} deriver=${pkg.cabal2nixDeriver.outPath}" pkg) - ] - ) - ] +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 6a8b7cd0..6bcd6e6e 100644 --- a/nix/modules/project/packages/default.nix +++ b/nix/modules/project/packages/default.nix @@ -58,15 +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 - (build-haskell-package name cfg.source cfg.cabal2NixFile) - 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 bbf74e1a..21a5c960 100644 --- a/nix/modules/project/packages/package.nix +++ b/nix/modules/project/packages/package.nix @@ -26,9 +26,9 @@ in cabal2NixFile = lib.mkOption { type = lib.types.str; description = '' - The Nix file which contains cached (pre-generated) `cabal2nix` expressions. + Filename of the cabal2nix generated nix expression. - By default, it refers to `cabal.nix` file. + This gets used if it exists instead of using IFD (callCabal2nix). ''; default = "cabal.nix"; };