Jenkins教程(八)实现 GitLab 触发 Jenkins 自动按模块发布前端
楔子
上篇文章解决了提交/合并请求自动触发的需求,但所有前端模块都在同一个代码仓库里,如何获取变更文件路径确定要发布哪个模块呢?本文将带你解决这个问题。
思路
分别解决 3 个问题:
- 获取变更的文件列表
- 根据文件列表判断所属模块
- 构建与发布脚本
过程
GitLab 事件触发 Jenkins 构建只是一个启动信号,获取变更文件列表需要知晓上一次构建时某个仓库的版本号,这里 Jenkins 的插件 git-plugin
已经帮我们实现了这部分工作。所以只需要通过 git-plugin
检出代码即可。
检出代码
checkout([ $class: 'GitSCM', branches: [[name: "*/$branchName"]], doGenerateSubmoduleConfigurations: false, extensions: [ [$class: 'RelativeTargetDirectory', relativeTargetDir: "$relativeTarget"] ], submoduleCfg: [], userRemoteConfigs: [ [credentialsId: "$credentialsId", url: "$gitUrl"] ] ])
请自行替换
$branchName
为分支名,$relativeTarget
为检出相对路径,$credentialsId
为用户凭据,$gitUrl
即 GIT仓库地址。
获取变更文件列表
//获取变更文件列表,返回HashSet,注意添加的影响文件路径不含仓库目录名 @NonCPS def getChangeFilePathSet() { def changedFiles = new HashSet<String>(); echo "开始获取变更的文件列表" for (int i = 0; i < currentBuild.changeSets.size(); i++) { def entries = currentBuild.changeSets[i].items for (int j = 0; j < entries.length; j++) { def entry = entries[j] changedFiles.addAll(entry.getAffectedPaths()); } } println '输出修改文件列表:' + changedFiles return changedFiles; }
这个方法可以放到 pipeline
块外,直接在 script
块中引用。实现思路是访问 currentBuild.changeSets
获取所有本次构建相比上次构建的变更列表,返回的是 HashSet
是为了方便,用其他容器也是可以的。
注意:变更文件列表的各个文件是相对于它所在仓库的路径!
变更文件列表截字符串,获取模块列表并去重
//获取合并报表前端自动发布模块set集合。 //pathPrefix为模块路径前缀,如develop/@gc @NonCPS def getAutoPublishModuleSet(pathPrefix) { //使用Set容器去重,保证待发布模块只有一份 def modulePaths = new HashSet<String>(); for(def filePath in getChangeFilePathSet()){ //忽略非前端模块的文件,比如 Jenkinsfile 等 if(filePath.startsWith(pathPrefix)){ //从超过模块前缀长度的下标开始,获取下一个/的位置。即分串位置 int index = filePath.indexOf('/', pathPrefix.length()+1) //分串得到模块路径,比如 develop/@gc/test def modulePath = filePath.substring(0, index) println 'add module path: ' + modulePath modulePaths.add(modulePath) } } println '输出待发布模块列表:' + modulePaths return modulePaths; }
写个构建发布 Shell 脚本
publish-web-module.sh
#!/bin/bash #此脚本用于构建发布前端模块,@author: Hellxz #$1:发布版本/$2:模块目录 set -eu echo "------------开始发布$2模块------------>" cd $2 echo "清理dist node_modules package-lock.json ……" rm -rf dist node_modules package-lock.json echo "正在安装依赖 ……" npm i echo "开始构建 ……" npm run build:dev echo "开始发布 ……" npm --no-git-tag-version version $1 npm publish echo "<------------发布$2模块完成------------" cd ${WORKSPACE}/web; #回到前端源码目录 exit 0;
循环调用构建发布脚本
for(def modulePath in modulePaths){ sh label: "构建发布前端模块 ${publishVersion} ${modulePath}", script: "bash ${SHELL_PATH}/publish-web-module.sh ${publishVersion} ${modulePath}" }
流水线示例
需将下列 Jenkinsfile 与 publish-web-module.sh 提交到同一仓库中
Jenkinsfile
pipeline{ agent any; environment{ gitUrl="http://xxxxxxxx/xxxx/web.git" branchName=dev relativeTarget="web" credentialsId=credentials('git-user') pathPrefix="develop/@gc" publishVersion="v1.0" npmRepo="http://xxxxxx/nexus/repository/npm-public/" npmToken=credentials('npm-token') shellPath="${WORKSPACE}/jenkins" //脚本与Jenkinsfile在同级目录中 } stages{ stage("检出代码"){ steps{ script { cleanWs() checkoutRepo("master", "jenkins", "${credentialsId}", "http://xxxxxxxx/xxxx/jenkins.git") checkoutRepo("${branchName}", "${relativeTarget}", "${credentialsId}", "${gitUrl}") } } } stage("构建发布"){ steps{ script{ sh label: "设置npm仓库", script: "npm set registry ${npmRepo}" sh label: "登录npm仓库", script: "npm config set //xxxxxx/nexus/repository/npm-public/:_authToken ${npmToken}" def modulePaths = getAutoPublishModuleSet(env.pathPrefix) for(def modulePath in modulePaths){ sh label: "构建发布前端模块 ${publishVersion} ${modulePath}", script: "bash ${shellPath}/publish-web-module.sh ${publishVersion} ${modulePath}" } } } post{ always{ script{ cleanWs() } } } } } } //抽取检出代码方法 @NonCPS def checkoutRepo(branchName, relativeTarget, credentialsId, gitUrl){ checkout([ $class: 'GitSCM', branches: [[name: "*/$branchName"]], doGenerateSubmoduleConfigurations: false, extensions: [ [$class: 'RelativeTargetDirectory', relativeTargetDir: "$relativeTarget"] ], submoduleCfg: [], userRemoteConfigs: [ [credentialsId: "$credentialsId", url: "$gitUrl"] ] ]) } //获取变更文件列表,返回HashSet,注意添加的影响文件路径不含仓库目录名 @NonCPS def getChangeFilePathSet() { def changedFiles = new HashSet<String>(); echo "开始获取变更的文件列表" for (int i = 0; i < currentBuild.changeSets.size(); i++) { def entries = currentBuild.changeSets[i].items for (int j = 0; j < entries.length; j++) { def entry = entries[j] changedFiles.addAll(entry.getAffectedPaths()); } } println '输出修改文件列表:' + changedFiles return changedFiles; } //获取合并报表前端自动发布模块set集合。 @NonCPS def getAutoPublishModuleSet(pathPrefix) { //使用Set容器去重,保证待发布模块只有一份 def modulePaths = new HashSet<String>(); for(def filePath in getChangeFilePathSet()){ //忽略非前端模块的文件,比如 Jenkinsfile 等 if(filePath.startsWith(pathPrefix)){ //从超过模块前缀长度的下标开始,获取下一个/的位置。即分串位置 int index = filePath.indexOf('/', pathPrefix.length()+1) //分串得到模块路径,比如 develop/@gc/test def modulePath = filePath.substring(0, index) println 'add module path: ' + modulePath modulePaths.add(modulePath) } } println '输出待发布模块列表:' + modulePaths return modulePaths; }
仅供抛砖引玉,抽取出来的方法本人将它们放到共享库中,写脚本就更清晰简短了。
还有什么问题
- 首次构建会识别不到提交记录,可能会漏发一次
- 切到未构建过的分支,也会漏发一次
- 限于文章篇幅,未添加手动传参指定模块发布的功能
对于多分支首次检出漏发的问题,这是因为没有上一个可供参考的相同分支提交ID作参考,本身不是技术问题,预先将所有前端发版分支提交点内容,只要构建触发了,后续就不会再漏发了。
最后
希望对您能有所启发,如果您有更优雅的实现方式 或者 文中有错误,希望您能不吝赐教评论指出,感谢。
本文同步发布于博客园(东北小狐狸 https://www.cnblogs.com/hellxz/)与CSDN(东北小狐狸-Hellxz https://blog.csdn.net/u012586326)禁止转载。
本文作者:东北小狐狸
本文链接:https://www.cnblogs.com/hellxz/p/15310001.html
版权声明:本作品采用自由转载-非商用-非衍生-保持署名 (CC BY-NC-ND 3.0)许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步