如何使用CI隔离构建存在依赖关系的项目?
哈哈,这个场景我太熟了!之前维护公司内部的共享库解决方案时,也被这个问题折腾过好一阵——全量构建不仅慢,还浪费资源,完全没必要。给你几个我实际落地过的方案,应该能解决你的困惑:
基于依赖图谱的增量构建
首先得把整个解决方案的项目依赖关系梳理清楚,不管是用工具生成依赖树(比如.NET的dotnet list package、Java的mvn dependency:tree),还是自己整理一份依赖映射表都行。CI触发时,第一步先通过脚本检测本次提交变更的项目:# 示例:用git diff找出变更的项目文件夹 changed_projects=$(git diff --name-only HEAD~1 | grep -E 'src/[^/]+/' | cut -d'/' -f2 | uniq)接着根据依赖映射,找出所有与变更项目相关的关联项目——比如项目A依赖项目B,当B变更时,只需要构建B和所有依赖B的项目(也就是A),而非整个解决方案。主流CI工具都支持自定义逻辑筛选要构建的项目,只对这些项目执行构建、测试步骤,轻松实现隔离。
模块化CI配置,按项目拆分Job
别把所有项目都塞进一个大CI配置里,给每个独立项目单独写CI Job,再通过依赖关系串联起来。比如在GitLab CI里,用needs关键字指定Job依赖,同时通过only:changes控制触发条件:job-build-projectB: script: dotnet build src/ProjectB only: changes: - src/ProjectB/**/* job-build-projectA: script: dotnet build src/ProjectA needs: [job-build-projectB] only: changes: - src/ProjectA/**/* - src/ProjectB/**/*这样一来,只有ProjectA变更时,只会触发它自己的构建Job;如果ProjectB变更,会先跑B的构建,再自动触发依赖B的A的构建,完全不会影响无关项目,隔离效果拉满。
临时替换本地依赖,实现独立构建
如果你的共享库是作为本地项目引用(而非发布成Nu包/ Maven包),构建依赖项目时很容易不小心拉整个解决方案。这时候可以在CI里用个小技巧:把变更后的项目先打包成临时本地包,再替换依赖项目的引用为这个临时包,这样依赖项目就能独立构建了。比如.NET环境下的示例脚本:# 打包变更的ProjectB为临时Nu包 dotnet pack src/ProjectB -o ./temp-nuget # 修改ProjectA的csproj,将项目引用替换为临时Nu包引用 sed -i 's/<ProjectReference Include="..\/ProjectB\/ProjectB.csproj"/<PackageReference Include="ProjectB" Version="1.0.0-temp"/>' src/ProjectA/ProjectA.csproj # 添加临时Nu源 dotnet nuget add source ./temp-nuget # 独立构建ProjectA dotnet build src/ProjectA这个方法适合依赖关系复杂,但又需要完全隔离构建的场景,避免加载无关项目拖慢速度。
给构建产物加缓存,优化效率
最后,为了进一步提升隔离构建的效率,可以给每个项目的构建产物单独加缓存。CI工具大多支持缓存功能,比如GitHub Actions的actions/cache:- name: Cache ProjectB build artifacts uses: actions/cache@v3 with: path: | src/ProjectB/bin src/ProjectB/obj key: ${{ runner.os }}-projectB-${{ hashFiles('src/ProjectB/**/*.csproj') }}这样只有当项目的代码或配置文件变更时,才会重新构建,没变更的项目直接用缓存产物,省时间又省资源。
内容的提问来源于stack exchange,提问作者James R. Twine

