如何让 cabal 和 nix 一起工作

How to get cabal and nix work together

据我了解,Nixcabal 沙箱 的替代方案。 我终于成功安装了 Nix,但我仍然不明白它如何替代沙箱。

我知道您不需要使用 Nix 和 GHC 包装版本的 cabal;但是如果你想要 发布一个包,你需要在某个时候使用 cabal 打包它。因此,您需要能够在 NIX 中编写和测试您的 cabal 配置。你是怎么做到的?

理想情况下,我想要一个类似于 cabal 沙箱但 "contained" 在 NIX 中的环境,这可能吗?事实上,我真正想要的是嵌套沙箱的等价物——因为我通常处理由多个包组成的项目。

更新我当前的工作流程

目前我从事 2 或 3 个独立项目(P1、P2、P3),每个项目由 2 或 3 个 cabal modules/packages 组成,假设 P1:L11、L12(库) 和 E11(可执行文件)。 E11 依赖于 L12,而 L12 又依赖于 L11。我主要将可执行文件从库中分离出来,因为它们是私有的并保存在私有 git 存储库中。

理论上,每个项目都可以有自己的沙箱(在其子模块之间共享)。我试过了(有一个用于 L11 L12 和 E11 的通用沙箱),但这很快就很烦人,因为如果你修改 L11,你不能重建它,因为 E11 依赖它,所以我必须先卸载 E11 才能重新编译 L11。 可能并非如此,但我遇到了类似的问题。 如果我偶尔修改L11就好了,但在实践中,我对它的修改比E11更多。

由于共享沙箱不起作用,所以我回到了每个包解决方案的一个沙箱。它正在工作,但不太理想。 主要问题是如果我修改 L11,我需要编译它两次(一次在 L11 中,然后在 E11 中再次编译)。另外,众所周知,每次我启动一个新的沙盒时,我都需要等待一段时间才能下载并重新编译所有包。

因此,通过使用 Nix,我希望能够为每个项目设置单独的 cabal "environments",从而解决上述所有问题。

希望这更清楚。

这些天我使用 Nix 和 cabal 进行所有开发,我可以很高兴地说它们非常和谐地工作。我当前的工作流程非常新,因为它依赖于 nixpkgs 中刚刚到达 master 分支的功能。因此,您需要做的第一件事是从 Github:

克隆 nixpkgs
cd ~
git clone git://github.com/nixos/nixpkgs

(以后不需要,现在是)

单个项目使用

现在我们有了一个 nixpkgs 克隆,我们可以开始使用 haskellng 包集了。 haskellng 重写了我们在 Nix 中打包东西的方式,我们很感兴趣,因为它更可预测(包名称与 Hackage 包名称匹配)和更可配置。首先,我们将安装 cabal2nix 工具,它可以为我们自动执行一些操作,我们还将安装 cabal-install 以提供 cabal 可执行文件:

nix-env -f ~/nixpkgs -i -A haskellngPackages.cabal2nix -A haskellngPackages.cabal-install

从这一点来看,一切都非常顺利。

如果您正在开始一个新项目,您可以像往常一样在新目录中调用 cabal init。当您准备好构建时,您可以将此 .cabal 文件转换为开发环境:

cabal init
# answer the questions
cabal2nix --shell my-project.cabal > shell.nix

这为您提供了一个 shell.nix 文件,可以与 nix-shell 一起使用。不过你不需要经常使用它——你通常唯一一次使用它是 cabal configure:

nix-shell -I ~ --command 'cabal configure'

cabal configure 缓存所有内容的绝对路径,所以现在当你想要构建时,你只需像往常一样使用 cabal build

cabal build

每当您的 .cabal 文件发生变化时,您都需要重新生成 shell.nix - 只需 运行 上面的命令,然后 cabal configure 之后。

多个项目使用

该方法可以很好地扩展到多个项目,但它需要更多的手动工作才能 "glue" 将所有内容放在一起。为了演示这是如何工作的,让我们考虑一下我的 socket-io library. This library depends on engine-io,我通常同时开发两者。

Nix-ifying 这个项目的第一步是在每个单独的 .cabal 文件旁边生成 default.nix 表达式:

cabal2nix engine-io/engine-io.cabal > engine-io/default.nix
cabal2nix socket-io/socket-io.cabal > socket-io/default.nix

这些default.nix表达式是函数,所以我们现在不能做太多。为了调用这些函数,我们编写了自己的 shell.nix 文件来解释如何组合所有内容。对于engine-io/shell.nix,我们不需要做任何特别聪明的事情:

with (import <nixpkgs> {}).pkgs;
(haskellngPackages.callPackage ./. {}).env

对于socket-io,我们需要依赖engine-io:

with (import <nixpkgs> {}).pkgs;
let modifiedHaskellPackages = haskellngPackages.override {
      overrides = self: super: {
        engine-io = self.callPackage ../engine-io {};
        socket-io = self.callPackage ./. {};
      };
    };
in modifiedHaskellPackages.socket-io.env

现在每个环境中都有shell.nix,所以我们可以像以前一样使用cabal configure

这里的关键观察是每当 engine-io 发生变化时,我们都需要重新配置 socket-io 以检测这些变化。这就像运行ning

一样简单
cd socket-io; nix-shell -I ~ --command 'cabal configure'

Nix 会注意到 ../engine-io 发生了变化,并在 运行ning cabal configure.

之前重建它