为什么 Shake 认为这个文件已经改变了?

Why does Shake think this file has changed?

完整 Shakefile.hs:

import Development.Shake
import Development.Shake.FilePath

outDir = "_build"

expensiveProcess :: IO ()
expensiveProcess = do
    putStrLn "expensiveProcess"
    writeFile (outDir </> "b") "b"

main = shakeArgs shakeOptions{ shakeFiles = outDir } $ do
    outDir </> "a" %> \out -> do
        alwaysRerun
        writeFileChanged out "a"

    outDir </> "b" %> \out -> do
        need [outDir </> "a"]
        liftIO expensiveProcess

假设我从一个空的 _build 目录开始,然后摇动 _build/b。在那种情况下,_build/a 已经改变(因为它是一个新文件),所以我们 运行 一些昂贵的过程来生成 _build/b:

$ rm -rf _build; stack exec -- shake --trace --trace _build/b 
% Starting run
% Number of actions = 1
% Number of builtin rules = 9 [FilesQ,DoesDirectoryExistQ,GetDirectoryContentsQ,AlwaysRerunQ,GetDirectoryDirsQ,DoesFileExistQ,GetDirectoryFilesQ,GetEnvQ,FileQ]
% Number of user rule types = 1
% Number of user rules = 2
% Before usingLockFile on _build/.shake.lock
% After usingLockFile
% Missing -> Running, _build/b
# _build/b
% Missing -> Running, _build/a
# _build/a
% Missing -> Running, alwaysRerun
% Running -> Ready, alwaysRerun
    = ((),"") (changed)
% Running -> Ready, _build/a
    = ((Just File {mod=0x5F12F628,size=0x1,digest=NEQ},"")) (changed)
expensiveProcess
% Running -> Ready, _build/b
    = ((Just File {mod=0x4D7E9358,size=0x1,digest=NEQ},"")) (changed)
Build completed in 0.00s

如果我现在再次摇动 _build/b,那么 expensiveProcess 不会 运行(很好!),因为它的输入 _build/a 没有改变。但是,它的输出被标记为 changed 这可能会导致下游出现问题,如果有任何其他依赖于 _build/b:

$ stack exec -- shake --trace --trace _build/b 
% Starting run
% Number of actions = 1
% Number of builtin rules = 9 [FilesQ,DoesDirectoryExistQ,GetDirectoryContentsQ,AlwaysRerunQ,GetDirectoryDirsQ,DoesFileExistQ,GetDirectoryFilesQ,GetEnvQ,FileQ]
% Number of user rule types = 1
% Number of user rules = 2
% Before usingLockFile on _build/.shake.lock
% After usingLockFile
% Chunk 0 [len 34] 01000100000000000000040000000100000001000000010000000000000000000000 Id 1 = (StepKey (),Loaded (Result {result = "\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [], execution = 0.0, traces = []}))
% Chunk 1 [len 30] 0a000400000000000000000000000100000001000000728ad03600000000 Id 4 = (alwaysRerun,Loaded (Result {result = "", built = Step 1, changed = Step 1, depends = [], execution = 6.215e-6, traces = []}))
% Chunk 2 [len 66] 080003000000080000005f6275696c642f611400000000000000000000002af6125f03000000010000000100000001000000fd957b39080000000400000004000000 Id 3 = (_build/a,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL*6\DC2_\ETX\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 4]], execution = 2.39931e-4, traces = []}))
% Chunk 3 [len 66] 080002000000080000005f6275696c642f621400000000000000000000005a937e4d03000000010000000100000001000000a1942b39080000000400000003000000 Id 2 = (_build/b,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NULZ7~M\ETX\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 3]], execution = 1.63632e-4, traces = []}))
% Chunk 4 [len 50] 000005000000000000000000000001000000010000000000000008000000040000000200000008000000ed872a3aed872a3a Id 5 = (Root,Loaded (Result {result = "", built = Step 1, changed = Step 1, depends = [[Id 2]], execution = 0.0, traces = [Trace {traceMessage = "", traceStart = 6.50524e-4, traceEnd = 6.50524e-4}]}))
% Read 5 chunks, plus 0 slop
% Found at most 6 distinct entries out of 5
% Loaded -> Running, _build/b
% Loaded -> Running, _build/a
% Loaded -> Running, alwaysRerun
% Running -> Ready, alwaysRerun
    = ((),"") (changed)
# _build/a
% Running -> Ready, _build/a
    = ((Just File {mod=0x5F12F628,size=0x1,digest=NEQ},"")) (unchanged)
% Running -> Ready, _build/b
    = ((Just File {mod=0x4D7E9358,size=0x1,digest=NEQ},"")) (changed)
Build completed in 0.00s

为什么_build/b在这一秒运行被标记为changed

Shake v0.19.6 有一个错误(已在 90a52d9 中修复)如果规则根本没有重建,它会报告 (changed) 如果最后一次规则 运行 它变了。在你的例子中,第一个构建导致 _build/b 改变,所以如果 _build/b 没有再次 运行,它会继续说 (changed)。这不是很有用,因此 Shake 现在报告 (didn't run) 没有 运行 的规则,这就是它现在第二次报告 _build/b 的规则。

% Running -> Ready, _build/a
    = ((Just File {mod=0xE205992F,size=0x1,digest=NEQ},"")) (unchanged)
% Running -> Ready, _build/b
    = ((Just File {mod=0xE208A5E4,size=0x1,digest=NEQ},"")) (didn't run)

此错误仅影响启用跟踪的输出,Shake 内部知道不会继续构建,即使在此修复之前也是如此。