ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VCS多lib编译实战:模块重命名与增量编译脚本

VCS多lib编译实战:模块重命名与增量编译脚本 1. 多lib编译到底难在哪先搞清楚问题本质做过数字IC验证的人都知道一个中大型SoC项目里Verilog代码通常不会全部堆在一个文件里而是按功能模块拆分成多个library库比如cpu_lib、ddr_lib、periph_lib、top_lib。每个lib内部有自己的文件列表、include路径、宏定义甚至不同的编译选项。这种拆分方式本身是合理的工程管理手段但一旦落到VCS编译环节麻烦就来了。最典型的场景是这样的你手上有三个liblib_a里定义了一个模块叫fifo_ctrllib_b里也有一个模块叫fifo_ctrl但两者内容完全不同。VCS默认把所有文件丢进同一个编译单元compilation unit里模块名冲突直接报错。又或者lib_a和lib_b都需要一个公共的defines.vh但各自的宏定义值不一样你没法用同一份头文件。再或者某个lib需要单独编译成增量库下次只重编改动的部分但VCS的增量编译机制在多lib场景下经常让人摸不着头脑。这些问题的根源在于VCS本质上是一个“扁平化”的编译器它不天然理解你的library层次结构。你需要通过-work、-libmap、-y、libext、-f等选项人为地告诉VCS“哪些文件属于哪个库”“库与库之间的依赖关系是什么”“哪些模块名需要重命名”。如果这些配置没做对要么编译报错要么仿真行为跟预期不一致排查起来非常痛苦。这篇文章要解决的就是这套流程的完整落地方法。我会从lib拆分策略讲起然后逐个拆解VCS的多lib编译选项重点讲清楚Verilog模块重命名的几种手段和分开编译的增量管理思路最后给出一套可以直接复用的脚本框架。适合正在搭建验证环境的中高级工程师也适合刚接触VCS多lib流程、被各种选项搞得头大的朋友。2. 多lib工程的组织策略与编译单元划分2.1 为什么不能把所有文件塞进一个-f列表很多人图省事把所有lib的.v文件路径写进一个filelist.f然后vcs -f filelist.f一把梭。小项目确实能跑但到了中大型项目就会暴露三个致命问题。第一模块名冲突无法隔离。假设lib_a和lib_b各有一个arbiter模块VCS在elaboration阶段会直接报“module already defined”。你可能会说“那我改名字不就行了”——但如果是第三方IP或者不同团队维护的库你根本没有权限改源码。第二编译粒度太粗增量编译失效。每次只改了一个lib里的一个文件VCS却要把所有文件重新分析一遍。项目大了之后一次全量编译可能十几分钟甚至半小时迭代效率极低。第三宏定义和include路径互相污染。不同lib可能依赖不同版本的defines.vh或者同一个宏在不同lib里含义不同。扁平化编译时define是全局生效的你没法给每个lib单独指定。所以正确的做法是按lib划分编译单元每个lib独立管理自己的文件列表和编译选项最后在top层做统一elaboration。这跟软件工程里的“静态库可执行文件”思路是一样的每个lib先编译成.a或.sotop层链接时再解析符号。2.2 lib划分的粒度怎么定lib拆得太粗等于没拆拆得太细管理成本又上去了。我的经验是遵循三个原则按功能域划分CPU、GPU、DDR、外设、总线各自一个lib边界清晰。按复用性划分会被多个项目共用的模块单独成lib项目专属的放另一个lib。按编译选项差异划分如果两个模块集需要的宏定义或优化选项不同就拆成两个lib。举个实际例子一个典型的SoC验证环境可以这样拆lib名称内容编译特点common_lib通用工具模块、断言库、覆盖率模型编译选项稳定很少改动ip_lib第三方IP的RTL和模型只读不修改单独编译一次dut_lib待测设计的RTL频繁改动需要增量编译tb_libTestbench、激励、scoreboard改动频繁依赖dut_libtop_lib顶层wrapper、连接逻辑最后编译依赖所有下层lib这个划分方式的好处是common_lib和ip_lib编译一次后基本不动dut_lib和tb_lib可以独立增量编译top_lib只负责例化和连接。每次迭代只需要重编改动的lib时间从半小时降到几分钟。2.3 目录结构约定为了让脚本好写、好维护目录结构最好固定下来。我通常用这样的布局project/ ├── libs/ │ ├── common_lib/ │ │ ├── rtl/ │ │ ├── filelist.f │ │ └── compile.cfg │ ├── ip_lib/ │ │ ├── rtl/ │ │ ├── filelist.f │ │ └── compile.cfg │ ├── dut_lib/ │ │ ├── rtl/ │ │ ├── filelist.f │ │ └── compile.cfg │ └── tb_lib/ │ ├── tb/ │ ├── filelist.f │ └── compile.cfg ├── top/ │ ├── top.sv │ └── filelist.f ├── scripts/ │ ├── compile_lib.sh │ ├── compile_all.sh │ └── clean.sh └── build/ ├── common_lib/ ├── ip_lib/ └── ...每个lib目录下的compile.cfg记录该lib专属的编译选项比如define、incdir、-y搜索路径等。filelist.f只列本lib的文件。build/目录存放编译产物按lib名分目录方便清理和增量判断。注意filelist.f里的路径建议用相对路径相对于项目根目录。这样整个工程可以整体拷贝到别的机器上不用改路径。VCS的-f选项支持在filelist里写incdir和define但为了清晰我建议把这些放在compile.cfg里filelist只放文件路径。3. VCS多lib编译的核心选项拆解3.1 -work与-libmap告诉VCS库的归属VCS从较新版本开始支持-work和-libmap选项这是多lib编译最核心的机制。-work用来指定一个逻辑库名-libmap用来把逻辑库名映射到物理目录。基本用法是这样的vcs -work common_lib./build/common_lib \ -work ip_lib./build/ip_lib \ -libmap ./libmap.cfg \ -f ./libs/common_lib/filelist.f \ -f ./libs/ip_lib/filelist.f \ ...libmap.cfg的内容类似common_lib ./build/common_lib ip_lib ./build/ip_lib dut_lib ./build/dut_lib tb_lib ./build/tb_lib这样VCS就知道common_lib里的模块编译后放到./build/common_lib目录ip_lib的放到./build/ip_lib。后续elaboration时VCS会从这些目录里查找模块定义。为什么需要这个机制因为VCS默认把所有模块放在一个叫work的默认库里。当你有两个同名模块时它们会冲突。通过-work把不同lib的模块分到不同的逻辑库VCS就能区分“common_lib里的fifo”和ip_lib里的fifo”。在Verilog里引用时可以用common_lib.fifo这样的层次名来明确指定。不过要注意-work和-libmap在不同VCS版本里行为略有差异。有些老版本不支持-libmap只能用-work libnamepath的形式。建议先vcs -help | grep work确认一下你的版本支持哪些选项。3.2 -y与libext自动搜索模块定义-y选项用来指定库目录VCS会在这些目录里自动搜索模块定义。libext.v.sv告诉VCS搜索哪些扩展名的文件。vcs -y ./libs/common_lib/rtl libext.v.sv \ -y ./libs/ip_lib/rtl libext.v.sv \ ...这个机制的好处是你不需要在filelist里显式列出每个文件VCS会根据模块名自动去-y指定的目录里找。对于文件多、改动频繁的lib这能省不少维护filelist的功夫。但-y有个坑搜索顺序很重要。如果两个-y目录里都有同名模块VCS会用先找到的那个。所以-y的顺序要跟lib的优先级一致。另外-y搜索是递归的吗默认不是只搜索指定目录一层。如果lib内部有子目录需要把每个子目录都加到-y里或者用-y加递归选项部分版本支持。我的建议是对于稳定的lib用-y对于频繁改动的lib用显式filelist。因为-y搜索在文件多的时候会变慢而且不好控制编译顺序。3.3 incdir与define的lib级隔离incdir指定include搜索路径define定义宏。这两个选项默认是全局的但你可以通过-work配合-f文件里的局部选项来实现lib级隔离。具体做法是在每个lib的compile.cfg里写该lib专属的选项然后在编译脚本里按lib分别调用VCS。比如# 编译common_lib vcs -work common_lib./build/common_lib \ incdir./libs/common_lib/include \ defineCOMMON_DEBUG \ -f ./libs/common_lib/filelist.f \ -Mdir ./build/common_lib/csrc \ -o ./build/common_lib/simv \ -c # 编译ip_lib vcs -work ip_lib./build/ip_lib \ incdir./libs/ip_lib/include \ defineIP_VERSION2 \ -f ./libs/ip_lib/filelist.f \ -Mdir ./build/ip_lib/csrc \ -o ./build/ip_lib/simv \ -c注意-c选项它表示只编译不elaboration。这样每个lib先独立编译成中间产物最后top层再统一elaboration。-Mdir指定生成的C代码目录按lib分开避免互相覆盖。提示define在多个lib里定义同名宏时后编译的会覆盖先编译的——如果你分开编译每个lib的宏就是独立的不会互相干扰。这正是分开编译的价值之一。3.4 -elaborate与top层链接所有lib编译完后top层需要做elaboration把所有lib的模块解析并链接成最终的可执行文件vcs -work top_lib./build/top_lib \ -libmap ./libmap.cfg \ -f ./top/filelist.f \ -Mdir ./build/top_lib/csrc \ -o ./build/simv \ -elaborate这里-libmap告诉VCS去哪里找之前编译好的lib。VCS会从./build/common_lib、./build/ip_lib等目录里加载模块定义然后跟top层的模块做连接。如果某个模块在多个lib里都有定义VCS会报“ambiguous module”错误。这时候就需要用到下一节要讲的模块重命名技术。4. Verilog模块重命名的四种实战手段4.1 为什么需要重命名模块重命名的需求通常来自三种场景同名不同实现两个lib里都有fifo模块但一个用于综合一个用于仿真不能混用。版本共存同一个IP的v1和v2版本需要同时存在于环境中做对比验证。避免命名污染第三方IP的模块名太通用比如ram、clk_gen容易跟自己的模块冲突。VCS提供了多种重命名手段各有适用场景。下面逐个讲。4.2 手段一-map选项做模块映射-map选项可以在elaboration时把一个模块名映射到另一个名字。语法是vcs -map old_module new_module ...比如lib_a里有个fifolib_b里也有个fifo你想在top层分别例化它们vcs -map lib_a.fifo fifo_a \ -map lib_b.fifo fifo_b \ ...这样在top层就可以用fifo_a和fifo_b来例化两个不同的fifo。-map的优点是不改源码纯编译期映射。缺点是只对elaboration阶段有效如果模块内部有层次化引用比如fifo.ctrl映射后层次名也会变需要同步调整。4.3 手段二ifdef条件编译做源码级隔离如果两个lib的源码你可以改最干净的方式是用ifdef把同名模块包起来ifdef USE_LIB_A module fifo (...); // lib_a的实现 endmodule else module fifo (...); // lib_b的实现 endmodule endif然后编译时用defineUSE_LIB_A或defineUSE_LIB_B来选择。这种方式的优点是行为明确、可读性好缺点是需要改源码而且如果两个实现差异很大代码会变得很臃肿。适合自己维护的lib不适合第三方IP。4.4 手段三用wrapper做层次隔离如果不想改源码也不想用-map可以写一个wrapper模块// fifo_a_wrapper.v module fifo_a_wrapper (...); fifo u_fifo (...); endmodule // fifo_b_wrapper.v module fifo_b_wrapper (...); fifo u_fifo (...); endmodule然后top层例化wrapper而不是直接例化fifo。编译时通过-y搜索路径的顺序来控制用哪个fifo实现。这种方式的优点是灵活、不改源码缺点是多了一层层次仿真性能略有下降而且wrapper的端口列表要跟原模块完全一致维护成本不低。4.5 手段四-libmap配合库限定名这是最“正规”的方式。通过-work把不同lib分到不同逻辑库然后在top层用库限定名来引用// top.sv module top; lib_a.fifo u_fifo_a (...); lib_b.fifo u_fifo_b (...); endmoduleVCS在elaboration时会根据-libmap去对应的物理目录里找lib_a.fifo和lib_b.fifo。这种方式的优点是语义清晰、不需要改源码、不需要wrapper缺点是需要VCS版本支持库限定名语法而且有些老代码可能不兼容。4.6 四种手段的对比与选型建议手段是否改源码是否改top适用场景缺点-map否否快速映射临时方案层次名会变ifdef是否自己维护的lib代码臃肿wrapper否是第三方IP隔离多一层层次-libmap限定名否是正规多lib工程版本依赖我的建议是优先用-libmap库限定名这是最干净的方案。如果VCS版本不支持退而求其次用-map。ifdef和wrapper只在特定场景下用。5. 分开编译与增量管理的完整脚本实现5.1 脚本整体设计思路脚本的核心目标是每个lib独立编译只重编改动的lib最后统一elaboration。为了实现这个目标需要解决三个问题依赖管理lib之间有依赖关系比如tb_lib依赖dut_libdut_lib依赖common_lib。编译顺序要按依赖拓扑排序。增量判断怎么知道一个lib需不需要重编可以用文件时间戳也可以用VCS自己的增量机制。清理与重建提供一键清理和全量重建的能力。我用bash脚本实现因为Linux环境下最通用。核心逻辑是遍历lib列表对每个lib检查源文件是否有更新如果有就重编没有就跳过。5.2 lib依赖关系的定义用一个简单的配置文件lib_deps.cfg定义依赖common_lib: ip_lib: dut_lib: common_lib tb_lib: dut_lib common_lib top_lib: tb_lib dut_lib ip_lib common_lib格式是lib名: 依赖的lib列表。脚本读取这个文件后用拓扑排序确定编译顺序。拓扑排序的bash实现可以用Kahn算法先找入度为0的节点输出后删除其出边重复直到所有节点输出。如果还有剩余节点说明有循环依赖报错退出。5.3 增量编译的时间戳判断对每个lib记录上次编译成功的时间戳存在build/lib/.last_build文件里。编译前用find命令找出该lib目录下所有.v、.sv、.vh文件中比.last_build新的文件。如果有就重编没有就跳过。check_need_rebuild() { local lib$1 local stamp_file./build/${lib}/.last_build if [ ! -f $stamp_file ]; then return 0 # 没有时间戳需要编译 fi local newer$(find ./libs/${lib} -name *.v -o -name *.sv -o -name *.vh \ -newer $stamp_file | head -1) if [ -n $newer ]; then return 0 # 有更新的文件需要编译 fi return 1 # 不需要编译 }这个判断逻辑简单有效但有个边界情况如果lib的编译选项compile.cfg改了也应该重编。所以find的时候把compile.cfg也加进去。5.4 完整脚本代码下面是完整的compile_lib.sh#!/bin/bash # VCS多lib编译脚本 # 用法: ./compile_lib.sh [lib名] # 不指定lib则编译所有 set -e PROJ_ROOT$(cd $(dirname $0)/.. pwd) BUILD_DIR${PROJ_ROOT}/build LIBS_DIR${PROJ_ROOT}/libs DEPS_FILE${PROJ_ROOT}/scripts/lib_deps.cfg LOG_DIR${BUILD_DIR}/logs mkdir -p $LOG_DIR # 读取依赖配置返回拓扑排序后的lib列表 get_sorted_libs() { local -A deps local -A indegree local all_libs() while IFS: read -r lib dep_str; do lib$(echo $lib | xargs) [ -z $lib ] continue all_libs($lib) deps[$lib]$dep_str indegree[$lib]0 done $DEPS_FILE # 计算入度 for lib in ${all_libs[]}; do for dep in ${deps[$lib]}; do indegree[$dep]$(( ${indegree[$dep]:-0} 1 )) done done # Kahn拓扑排序 local queue() for lib in ${all_libs[]}; do if [ ${indegree[$lib]} -eq 0 ]; then queue($lib) fi done local sorted() while [ ${#queue[]} -gt 0 ]; do local cur${queue[0]} queue(${queue[]:1}) sorted($cur) for lib in ${all_libs[]}; do for dep in ${deps[$lib]}; do if [ $dep $cur ]; then indegree[$lib]$(( ${indegree[$lib]} - 1 )) if [ ${indegree[$lib]} -eq 0 ]; then queue($lib) fi fi done done done if [ ${#sorted[]} -ne ${#all_libs[]} ]; then echo ERROR: 循环依赖 detected 2 exit 1 fi echo ${sorted[]} } # 检查lib是否需要重编 need_rebuild() { local lib$1 local stamp${BUILD_DIR}/${lib}/.last_build [ ! -f $stamp ] return 0 local newer newer$(find ${LIBS_DIR}/${lib} \ \( -name *.v -o -name *.sv -o -name *.vh -o -name compile.cfg \) \ -newer $stamp 2/dev/null | head -1) [ -n $newer ] return 0 return 1 } # 编译单个lib compile_one_lib() { local lib$1 local lib_dir${LIBS_DIR}/${lib} local out_dir${BUILD_DIR}/${lib} local log_file${LOG_DIR}/${lib}.log echo 编译 ${lib} ... mkdir -p $out_dir # 读取lib专属编译选项 local extra_opts if [ -f ${lib_dir}/compile.cfg ]; then extra_opts$(grep -v ^\s*# ${lib_dir}/compile.cfg | grep -v ^\s*$ | tr \n ) fi # 组装VCS命令 local vcs_cmdvcs -full64 -sverilog vcs_cmd -work ${lib}${out_dir} vcs_cmd -Mdir ${out_dir}/csrc vcs_cmd -o ${out_dir}/simv vcs_cmd -c vcs_cmd ${extra_opts} vcs_cmd -f ${lib_dir}/filelist.f vcs_cmd -l ${log_file} echo 命令: $vcs_cmd if eval $vcs_cmd; then touch ${out_dir}/.last_build echo ${lib} 编译成功 else echo ${lib} 编译失败见 ${log_file} 2 exit 1 fi } # 主流程 main() { local target_lib$1 local sorted_libs sorted_libs$(get_sorted_libs) for lib in $sorted_libs; do if [ -n $target_lib ] [ $lib ! $target_lib ]; then continue fi if need_rebuild $lib; then compile_one_lib $lib else echo 跳过 ${lib}无改动 fi done # 如果编译了所有lib做top层elaboration if [ -z $target_lib ]; then echo Top层 elaboration ... local top_out${BUILD_DIR}/simv vcs -full64 -sverilog \ -libmap ${PROJ_ROOT}/scripts/libmap.cfg \ -f ${PROJ_ROOT}/top/filelist.f \ -Mdir ${BUILD_DIR}/top_csrc \ -o $top_out \ -l ${LOG_DIR}/top.log \ -elaborate echo 最终可执行文件: $top_out fi } main $配套的libmap.cfgcommon_lib ./build/common_lib ip_lib ./build/ip_lib dut_lib ./build/dut_lib tb_lib ./build/tb_lib top_lib ./build/top_lib5.5 脚本使用示例与效果假设你改了dut_lib里的一个文件运行./compile_lib.sh输出大概是 跳过 common_lib无改动 跳过 ip_lib无改动 编译 dut_lib ... dut_lib 编译成功 编译 tb_lib ... tb_lib 编译成功 Top层 elaboration ... 最终可执行文件: ./build/simvcommon_lib和ip_lib被跳过了只重编了dut_lib和依赖它的tb_lib最后做elaboration。整个过程可能只要一两分钟比全量编译快得多。如果你想只编译某个lib比如调试时可以./compile_lib.sh dut_lib脚本只会编译dut_lib不做elaboration。6. 常见问题与排查技巧实录6.1 模块重复定义报错怎么定位最常见的报错是Error-[MODDUP] Module already defined Module fifo is already defined in file ...排查思路用grep -rn module fifo ./libs/找出所有定义fifo的文件。确认这些文件是否被同时编译进了同一个逻辑库。如果是用-work把它们分到不同库。如果确实需要同名共存用-map或库限定名来区分。实操心得VCS报这个错的时候只会告诉你“已经定义过”但不会告诉你“在哪里定义的”。我通常会在编译命令里加-debug_accessall然后看csrc目录下的*.v文件里面会有VCS展开后的模块列表能快速定位冲突来源。6.2 增量编译不生效的排查有时候改了文件脚本却判断“无改动”跳过了编译。原因通常是时间戳精度问题某些文件系统的时间戳精度是秒级如果改动和上次编译在同一秒内find -newer可能判断不出来。解决办法是用touch手动更新.last_build或者改用文件内容的MD5校验。文件路径不在find范围内如果lib的文件不在libs/lib/目录下find就找不到。检查filelist.f里的路径是否都在lib目录内。符号链接问题如果lib目录是符号链接find默认不跟随。加-L选项。6.3 elaboration阶段找不到模块报错类似Error-[URMI] Unresolved module instance Instance u_fifo of module fifo is not resolved这说明VCS在elaboration时找不到fifo的定义。排查步骤确认fifo所在的lib是否已经编译成功build/lib/目录下是否有.last_build文件。确认libmap.cfg里是否包含了该lib的映射。确认top层的filelist.f里是否引用了该lib的模块。如果用了-y搜索确认-y路径是否正确libext是否包含了正确的扩展名。6.4 常见问题速查表问题现象可能原因解决方法模块重复定义同名模块在同一逻辑库用-work分库或-map重命名增量编译不触发时间戳精度/路径问题改用MD5校验检查find范围elaboration找不到模块lib未编译或libmap缺失检查build目录和libmap.cfg宏定义冲突全局define互相覆盖分开编译lib级隔离include文件找不到incdir路径不对检查compile.cfg里的路径编译速度慢全量编译无增量用脚本做增量判断库限定名语法报错VCS版本不支持升级VCS或改用-map循环依赖lib_deps.cfg配置错误检查依赖关系消除环6.5 几个踩过的坑坑一-Mdir不分开导致C代码互相覆盖。早期我没给每个lib单独指定-Mdir结果所有lib的C代码都生成到同一个目录增量编译时VCS分不清哪些是哪个lib的经常出现莫名其妙的链接错误。后来按lib分目录就好了。坑二define在分开编译时的作用域。分开编译时每个lib的define只在该lib的编译过程中生效不会影响其他lib。这本来是好事但如果你有个宏需要在多个lib里保持一致就得在每个lib的compile.cfg里都写一遍。我后来用一个公共的common_defines.cfg在每个lib的编译命令里source一下保证一致性。坑三top层elaboration时的-libmap路径。libmap.cfg里的路径是相对于执行VCS命令的当前目录的不是相对于libmap.cfg文件本身的。所以脚本里最好用绝对路径或者在执行前cd到项目根目录。坑四VCS版本差异。不同版本的VCS对-work、-libmap、库限定名的支持程度不一样。我们团队之前用的是一个较老的版本不支持-libmap只能用-work libpath的形式。后来升级后才用上-libmap。建议在脚本里加个版本检查或者把VCS选项做成可配置的。7. 脚本的扩展与定制思路7.1 支持Verdi联合仿真如果要做Verdi联合仿真需要在编译时加-kdb选项生成知识数据库elaboration时加-lca。在脚本里可以加一个--verdi参数if [ $ENABLE_VERDI 1 ]; then vcs_cmd -kdb -lca fi这样编译产物就能直接被Verdi加载做波形调试和源码追踪。7.2 支持覆盖率收集覆盖率编译需要加-cm linecondfsmtglbranchelaboration时也要加同样的选项。可以在compile.cfg里按lib配置是否开启覆盖率比如dut_lib开ip_lib不开因为IP内部覆盖率通常不需要收集。7.3 支持多仿真器切换有些项目需要VCS和Xcelium双跑。可以把编译命令抽象成一个函数根据SIMULATOR环境变量选择不同的命令模板。VCS用vcsXcelium用xrun选项映射关系整理成一张表。7.4 并行编译加速如果lib之间没有依赖关系比如common_lib和ip_lib可以并行编译。用bash的和wait实现compile_one_lib common_lib compile_one_lib ip_lib wait但要注意并行编译时日志会混在一起需要每个lib单独写日志文件。另外如果机器内存不够并行编译可能导致OOM建议根据机器配置限制并行度。7.5 与CI/CD集成在CI流水线里可以把compile_lib.sh作为构建步骤编译成功后把build/simv和build/logs/作为产物归档。如果编译失败脚本返回非零退出码CI自动标记失败。增量编译在CI里意义不大因为每次都是干净环境可以加--clean参数强制全量编译。8. 一些个人经验与建议这套多lib编译流程我在几个中大型SoC项目里用过最大的感受是前期花时间把lib划分和脚本搭好后期迭代效率的提升是数量级的。一个典型的例子是之前全量编译要25分钟搭好增量编译后改一个tb文件只要3分钟就能跑仿真迭代速度完全不一样。几个建议给正在搭类似流程的朋友第一lib划分宁细勿粗。一开始可能觉得拆太多麻烦但后面lib多了之后增量编译的粒度更细收益更大。当然也不能太细一般5到10个lib比较合适。第二compile.cfg要版本化管理。每个lib的编译选项都进git这样任何人改了选项都能追溯。不要把这些选项散落在脚本里否则时间一长没人记得为什么这么配。第三日志要保留。每次编译的日志按lib和时间戳归档出问题的时候可以对比“上次成功编译”和“这次失败编译”的日志差异快速定位。第四定期做全量编译验证。增量编译虽然快但有时候会有“增量状态不一致”的问题比如某个文件被删除但增量编译没检测到。建议每天至少做一次全量编译确保环境干净。第五脚本要能一键清理。提供一个clean.sh删除build/目录下所有产物回到初始状态。出问题的时候先clean再全量编译能排除大部分环境问题。这套流程不是银弹不同项目可能需要调整。但核心思路——按lib隔离编译单元、增量判断、统一elaboration——是通用的。把这套骨架搭好后面根据项目特点填充细节就行。
返回列表