问题描述
简而言之,我的问题是“如何在 cabal 管理的多库 haskell 项目存储库中获得快速的保存-重新测试工作流程?”
我已经尝试了一些方法并做了一些研究。在深入了解更多细节之前,请先查看典型的项目存储库结构,然后将问题分解为更多细节:
存储库开发结构
我从事多个 Haskell 项目,这些项目通常具有以下形式:
.
├── foo
│ ├── foo.cabal
│ ├── src
│ ├── unit-test
│ └── ...
├── bar
│ ├── bar.cabal
│ ├── src
│ ├── unit-test
│ └── ...
├── baz
│ ├── baz.cabal
│ ├── src
│ ├── unit-test
│ └── ...
├── stack.yaml
├── cabal.project
├── nix
│ └── ...
└── ...
cabal.project
文件如下所示:
packages:
foo
bar
baz
...
tests: True
run-tests: True
堆栈文件包含基本相同的项目列表和 LTS ID,所以我可以只使用 IOHK's haskell.nix 中的 stackProject
nix 函数来为自己提供一个包含 cabal 等的 nix shell。 (这个问题更多是关于 cabal 处理的,所以我认为这里的这段文字只是一个背景说明,我认为与这个堆栈溢出问题无关。)
这个设置让我可以在项目的任何地方运行 cabal test all
,这很棒。这是我在关闭下一个 git commit 之前查看我是否破坏了什么的简单方法。
快速保存-重新测试工作流程
在我使用 nix 之前,我使用了 stack build/test --watch
,这很好,因为我现在可以打开一个 shell,在我更改任何地方的任何内容后,它总是重新测试并重建整个项目。
这可以用 inotify
模拟:
while true; do
inotifywait -e modify -r ./;
cabal test all
done
这不是很快,但它也能完成工作。
在我了解 GHCID 后,我惊讶于它的惊人的速度。
与 cabal repl
一起使用也很容易。
不幸的是(也提到了这个问题,但在此处的评论中没有回答How to run test suite in ghcid with cabal?),GHCID 可以在一个特定的单元测试套件上运行,并且不会检测单元测试应该检查的库上的更改. (将所有库模块放入 cabal 文件中的单元测试描述中,我认为这是一个丑陋的黑客行为,我宁愿避免这种情况)
此外,我似乎无法像 cabal test all
或 stack test --watch
那样在 整个存储库 上运行 GHCID。
GHCID 的极速是我在工作流程中真正想要的。
cabal
是一个在堆栈之前已经存在很长时间的工具,人们如何在他们的多库存储库上工作以快速概览他们在多个库中编辑多个文件后破坏的所有测试用例?如果 GHCID 方法不能很好地工作, 方法是什么?
解决方法
我使用带有堆栈的脚本如下:
ghcid -c="stack ghci <test-suite>.hs" -T="main" --warnings $@
这意味着:
- 运行
ghcid
- 使用
stack ghci
而不是 vanillaghci
- 还将测试套件模块加载到
ghci
- 在加载
main
时运行ghci
(来自测试套件) - 即使编译生成 GHC 警告也运行测试
您可以轻松地将其调整为使用,例如,cabal repl
而不是 stack ghci
。
这有以下缺点:
- 测试模块的名称需要硬编码,不能从
package.yaml
/cabal 文件中提取。 - 它不支持多个测试套件。这些都可以传递给脚本,但您需要一个自定义的
main
来调用它们。
我以前通过使用 :!stack test
作为命令解决了这些问题,该命令运行所有测试套件,但由于这是通过命令行发出而不是加载到 ghci 中,因此测试运行速度要慢得多。此外,它不会在修改测试时热重载。
底线是:要获得 ghcid 的好处,需要告知 ghcid 要查看哪些源文件。通过 shell 命令重新加载 ghcid 将需要由 ghci 完全重新加载和重新编译,而不是利用 ghci 的快速热重新加载功能。如果此信息存储在构建系统配置文件中,则您的构建系统需要与 ghcid 集成(没有自定义脚本。)我猜这对于 cabal 来说太低级了,但我已经为堆。如果您想看到这种情况发生,请在此处添加评论或反应!