Mar*_*nek 10 caching haskell cabal haskell-stack github-actions
使用 本地构建我的 Haskell 项目时stack build
,仅重新编译更改的源文件。不幸的是,我无法让 Stack 在 GitHub Actions 上表现得像这样。请问有什么建议吗?
例子
我用Lib.hs
and创建了一个简单的例子Fib.hs
,我什至检查缓存的 .stack-work 文件夹是否在构建之间更新,但它总是编译两个文件,即使只更改一个文件。
这是示例:
Lib.hs
和Fib.hs
+ 依赖项):https : //github.com/MarekSuchanek/stack-test/runs/542163994Lib.hs
更改,同时构建Lib.hs
和Fib.hs
):https : //github.com/MarekSuchanek/stack-test/runs/542174351我可以从日志(详细堆栈)中观察到缓存中的某些内容正在更新,但我完全不清楚是什么以及为什么。它正确地发现只有Lib.hs
被改变了:“ stack-test-0.1.0.0: unregistering (local file changes: src/Lib.hs)
”所以我不明白为什么所有的都被编译了。我注意到,在2Fib.hi
没有更新,.stack-work
但其他人(Fib.o
,Fib.dyn_hi
,和Fib.dyn_o
)是。
笔记
当没有更改源文件时, ~/.stack 缓存是可以的,也可以不构建。当然,这是一个虚拟示例,但我们有不同的项目,其中包含更多源文件,可以显着加快构建速度。当更改非源文件(例如 README 文件)时,不会按预期构建任何内容。
这个问题的罪魁祸首是堆栈使用时间戳(和许多其他工具一样)来确定源文件是否已更改。当您在 CI 上恢复缓存并正确执行时,不会重建任何依赖项,但是源文件的问题是,当 CI 提供程序为您克隆一个 repo 时,repo 中所有文件的时间戳都已设置到克隆的日期和时间。
希望重新编译未更改源文件的原因现在有意义。我们如何解决这个问题。获得它的唯一真正方法是恢复更改特定文件的最后一次 git 提交的时间戳。我很久以前就注意到了这一点,谷歌搜索给了我一些关于 SO 的答案,我认为这是其中之一:在 Git 中恢复文件的修改时间
A 修改了一下以适应我的需求,这就是我最终得到的结果:
git ls-tree -r --name-only HEAD | while read filename; do
TS="$(git log -1 --format="%ct" -- ${filename})"
touch "${filename}" -mt "$(date --date="@$TS" "+%Y%m%d%H%M.%S")"
done
Run Code Online (Sandbox Code Playgroud)
那个工人在 Ubuntu CI 上对我来说很棒一段时间,但是当我需要设置 Azure CI 时,我不想用 bash 以与操作系统无关的方式解决这个问题。出于这个原因,我编写了一个适用于所有 GHC-8.2 和更新版本的 Haskell 脚本,不需要任何非核心依赖项。我将它用于我的所有项目,我将在这里嵌入它的精华,但也提供一个指向永久要点的链接:
git ls-tree -r --name-only HEAD | while read filename; do
TS="$(git log -1 --format="%ct" -- ${filename})"
touch "${filename}" -mt "$(date --date="@$TS" "+%Y%m%d%H%M.%S")"
done
Run Code Online (Sandbox Code Playgroud)
您将如何在没有太多开销的情况下使用它。诀窍是:
stack
自身来运行脚本以上两点将确保不会安装多余的依赖项或 ghc 版本。所有的一切的只有两件事情需要的是stack
让人觉得curl
还是wget
,它会工作的跨平台:
# Script for restoring source files modification time from commit to avoid recompilation.
curl -sSkL https://gist.githubusercontent.com/lehins/fd36a8cc8bf853173437b17f6b6426ad/raw/4702d0252731ad8b21317375e917124c590819ce/git-modtime.hs -o git-modtime.hs
# Restore mod time and setup ghc, if it wasn't restored from cache
stack script --resolver ${RESOLVER} git-modtime.hs --package base --package time --package directory --package process
Run Code Online (Sandbox Code Playgroud)
这是一个使用这种方法的真实项目,您可以深入了解它是如何工作的: massiv-io
在评论中编辑@Simon Michael 提到他无法在本地重现此问题。这样做的原因是 CI 上的所有内容都与本地相同。很多时候绝对路径是不同的,例如,可能是我现在想不到的其他事情。这些东西与源文件时间戳一起导致源文件的重新编译。
例如按照以下步骤,您会发现您的项目将被重新编译:
~/tmp$ git clone git@github.com:fpco/safe-decimal.git
~/tmp$ cd safe-decimal
~/tmp/safe-decimal$ stack build
safe-decimal> configure (lib)
[1 of 2] Compiling Main
...
Configuring safe-decimal-0.2.0.0...
safe-decimal> build (lib)
Preprocessing library for safe-decimal-0.2.0.0..
Building library for safe-decimal-0.2.0.0..
[1 of 3] Compiling Numeric.Decimal.BoundedArithmetic
[2 of 3] Compiling Numeric.Decimal.Internal
[3 of 3] Compiling Numeric.Decimal
...
~/tmp/safe-decimal$ cd ../
~/tmp$ mv safe-decimal safe-decimal-moved
~/tmp$ cd safe-decimal-moved/
~/tmp/safe-decimal-moved$ stack build
safe-decimal-0.2.0.0: unregistering (old configure information not found)
safe-decimal> configure (lib)
[1 of 2] Compiling Main
...
Run Code Online (Sandbox Code Playgroud)
您将看到项目的位置触发了项目构建。尽管项目本身已重建,但您会注意到没有重新编译任何源文件。现在,如果将该过程与touch
源文件的 a结合起来,该源文件将被重新编译。
把它们加起来: