Shake构建如何避免依赖未变更时重复编译Vivado xpr项目文件
我有一套复杂的构建流程:先生成Vivado项目文件,再运行Vivado综合工具读取该项目文件并生成bitfile。在最后这一步中,Vivado会更新项目文件中的部分字段。
以下是该场景的自包含复现模型:
import Development.Shake import Development.Shake.FilePath import Data.Time.Clock import Control.Concurrent outDir = "_build" vivadoXPR :: IO () vivadoXPR = do putStrLn "vivadoXPR" threadDelay $ 1 * 1000 * 1000 writeFile (outDir </> "xpr") "xpr" vivadoBitfile :: IO () vivadoBitfile = do putStrLn "vivadoBitfile" threadDelay $ 1 * 1000 * 1000 time <- getCurrentTime writeFile (outDir </> "bitfile") (show time) writeFile (outDir </> "xpr") (show time) main = shakeArgs shakeOptions{ shakeFiles = outDir {-, shakeChange = ChangeModtimeAndDigestInput -} } $ do outDir </> "manifest" %> \out -> do alwaysRerun writeFileChanged out "manifest" outDir </> "tcl" %> \out -> do need [outDir </> "manifest"] writeFileChanged out "tcl" outDir </> "xpr" %> \out -> do need [outDir </> "tcl"] liftIO vivadoXPR outDir </> "bitfile" %> \out -> do need [outDir </> "xpr"] liftIO vivadoBitfile
我想要避免全量构建后重新编译xpr的问题,因为该操作会使Vivado所有内部状态失效,触发全量重综合。请注意xpr的所有依赖都没有发生变更。
使用--trace参数两次构建_build/bitfile的输出如下:
$ rm -rf _build && \ stack exec -- shake --trace --trace _build/bitfile && \ stack exec -- shake --trace --trace _build/bitfile % 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 = 4 % Before usingLockFile on _build/.shake.lock % After usingLockFile % Missing -> Running, _build/bitfile # _build/bitfile % Missing -> Running, _build/xpr # _build/xpr % Missing -> Running, _build/tcl # _build/tcl % Missing -> Running, _build/manifest # _build/manifest % Missing -> Running, alwaysRerun % Running -> Ready, alwaysRerun = ((),"") (changed) % Running -> Ready, _build/manifest = ((Just File {mod=0x540BBC70,size=0x8,digest=NEQ},"")) (changed) % Running -> Ready, _build/tcl = ((Just File {mod=0x540BBC70,size=0x3,digest=NEQ},"")) (changed) vivadoXPR % Running -> Ready, _build/xpr = ((Just File {mod=0x178FA5A0,size=0x3,digest=NEQ},"")) (changed) vivadoBitfile % Running -> Ready, _build/bitfile = ((Just File {mod=0xECA7F588,size=0x21,digest=NEQ},"")) (changed) Build completed in 2.01s % 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 = 4 % 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] 0a000600000000000000000000000100000001000000cd02cc3700000000 Id 6 = (alwaysRerun,Loaded (Result {result = "", built = Step 1, changed = Step 1, depends = [], execution = 2.432e-5, traces = []})) % Chunk 2 [len 73] 0800050000000f0000005f6275696c642f6d616e696665737414000000000000000000000072bc0b540a000000010000000100000001000000d6ea4439080000000400000006000000 Id 5 = (_build/manifest,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NULr\188\vT\n\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 6]], execution = 1.87795e-4, traces = []})) % Chunk 3 [len 68] 0800040000000a0000005f6275696c642f74636c14000000000000000000000072bc0b5405000000010000000100000001000000b1ef8d39080000000400000005000000 Id 4 = (_build/tcl,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NULr\188\vT\ENQ\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 5]], execution = 2.70722e-4, traces = []})) % Chunk 4 [len 68] 0800030000000a0000005f6275696c642f787072140000000000000000000000a2a58f17050000000100000001000000010000002c32803f080000000400000004000000 Id 3 = (_build/xpr,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\162\165\143\ETB\ENQ\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 4]], execution = 1.0015311, traces = []})) % Chunk 5 [len 72] 0800020000000e0000005f6275696c642f62697466696c651400000000000000000000008af5a7ec23000000010000000100000001000000263a803f080000000400000003000000 Id 2 = (_build/bitfile,Loaded (Result {result = "\NUL\NUL\NUL\NUL\NUL\NUL\NUL\NUL\138\245\167\236#\NUL\NUL\NUL\SOH\NUL\NUL\NUL", built = Step 1, changed = Step 1, depends = [[Id 3]], execution = 1.0017745, traces = []})) % Chunk 6 [len 50] 0000070000000000000000000000010000000100000000000000080000000400000002000000080000001e4500401e450040 Id 7 = (Root,Loaded (Result {result = "", built = Step 1, changed = Step 1, depends = [[Id 2]], execution = 0.0, traces = [Trace {traceMessage = "", traceStart = 2.0042186, traceEnd = 2.0042186}]})) % Read 7 chunks, plus 0 slop % Found at most 8 distinct entries out of 7 % Loaded -> Running, _build/bitfile % Loaded -> Running, _build/xpr % Loaded -> Running, _build/tcl % Loaded -> Running, _build/manifest % Loaded -> Running, alwaysRerun % Running -> Ready, alwaysRerun = ((),"") (changed) # _build/manifest % Running -> Ready, _build/manifest = ((Just File {mod=0x540BBC70,size=0x8,digest=NEQ},"")) (unchanged) % Running -> Ready, _build/tcl = ((Just File {mod=0x540BBC70,size=0x3,digest=NEQ},"")) (changed) # _build/xpr vivadoXPR % Running -> Ready, _build/xpr = ((Just File {mod=0xDC183BD8,size=0x3,digest=NEQ},"")) (changed) # _build/bitfile vivadoBitfile % Running -> Ready, _build/bitfile = ((Just File {mod=0x9F9C2120,size=0x21,digest=NEQ},"")) (changed) Build completed in 2.01s
请问如何避免第二次运行shake时执行vivadoXPR步骤?
问题根源
第二次构建触发vivadoXPR重跑的核心原因是:第一次构建的vivadoBitfile步骤会修改上游产物xpr文件的内容,Shake默认会校验所有生成文件的内容和缓存值是否一致,发现xpr的实际内容和vivadoXPR生成的初始内容不符,就会触发xpr的重构建。
可选解法
解法1:开启ChangeModtimeAndDigestInput校验模式
Shake的shakeChange参数设置为ChangeModtimeAndDigestInput后,仅对非构建生成的源文件同时校验修改时间和内容摘要,对于构建过程中生成的文件,只会校验其依赖是否变更,不会校验文件本身是否被修改。你原代码中已经注释了该配置,直接取消注释即可:
main = shakeArgs shakeOptions{ shakeFiles = outDir, shakeChange = ChangeModtimeAndDigestInput -- 取消该配置的注释 } $ do -- 原有规则保持不变
这个方案适合整个构建流程中存在多处下游修改上游产物的场景,改动最小。
解法2:显式声明bitfile规则会修改xpr
在bitfile的生成规则中用produces函数声明当前规则会修改xpr文件,Shake就会知道xpr的最终状态由bitfile规则确定,不会因为其内容和初始生成状态不一致触发重构建:
outDir </> "bitfile" %> \out -> do need [outDir </> "xpr"] produces [outDir </> "xpr"] -- 新增这行,声明该规则会修改xpr liftIO vivadoBitfile
这个方案更精准,仅针对xpr被下游修改的场景做适配,不会影响其他构建规则的校验逻辑。
内容的提问来源于stack exchange,提问作者Cactus

