You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 02:18:04