Skip to content

Commit

Permalink
Support for cabal2nix for IFD-free eval (#382)
Browse files Browse the repository at this point in the history
If a `cabal.nix` file (customizable) exists, haskell-flake will use `callPackage` on it. This file should ideally be auto-managed by https://github.com/cachix/git-hooks.nix

Note that IFD will still be needed if you override package sources (to source paths or to Hackage versions).
  • Loading branch information
rsrohitsingh682 authored Dec 23, 2024
1 parent f4e3cf1 commit d8a21e6
Show file tree
Hide file tree
Showing 8 changed files with 125 additions and 20 deletions.
7 changes: 7 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@
};
};

test-cabal2nix = {
dir = "test/cabal2nix";
overrideInputs = {
inherit nixpkgs flake-parts haskell-flake;
};
};

test-with-subdir = {
dir = "test/with-subdir";
overrideInputs = {
Expand Down
46 changes: 35 additions & 11 deletions nix/build-haskell-package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
10 changes: 1 addition & 9 deletions nix/modules/project/packages/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
};
}
10 changes: 10 additions & 0 deletions nix/modules/project/packages/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ''
Expand Down
11 changes: 11 additions & 0 deletions test/cabal2nix/default.nix
Original file line number Diff line number Diff line change
@@ -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";
}
35 changes: 35 additions & 0 deletions test/cabal2nix/flake.nix
Original file line number Diff line number Diff line change
@@ -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 = { };
};
};
};
}
20 changes: 20 additions & 0 deletions test/cabal2nix/haskell-flake-test.cabal
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cabal-version: 3.0
name: haskell-flake-test
version: 0.1.0.0
license: NONE
author: Joe
maintainer: [email protected]
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

6 changes: 6 additions & 0 deletions test/cabal2nix/src/Main.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module Main where

import System.Random

main :: IO ()
main = putStrLn "Hello, Haskell!"

0 comments on commit d8a21e6

Please sign in to comment.