以下是关于 Jenkins Pipeline 触发多平台Job、并行执行及制品下载 的详细攻略,涵盖核心概念、关键配置和完整示例。
一、核心场景与目标
假设我们需要在一个主Pipeline中:
- 自动识别当前代码分支(如
wip/jenkins); - 对分支名中的特殊字符(如
/)进行转义,适配Jenkins的Job命名规则; - 并发触发3个平台的子Job(MacOSX、Windows、Linux),等待它们执行完成(
!!!还有一种方式值直接下载其他job最后一次成功的制品,但是每个job的制品可能版本就不一样,就比如可能不是最新一次提交而生成的制品,有可能这次提交,其他job还没有执行完成,又或者执行失败了!!!); - 从每个子Job中下载构建好的制品(
如可执行文件,下载制品也可以通过Jenkins自带的插件下载,直接通过URL下载是由于一直有一个权限问题,目前不知道为什么); - 聚合所有平台的制品,打包存档。
二、关键概念与细节
1. 分支名转义:为什么需要处理 /?
Jenkins中,多分支Pipeline的Job名称会自动将分支名中的 / 编码为 %2F(URL编码规则)。例如:
- 原始分支名:
wip/jenkins - 对应的Jenkins Job名称:
wip%2Fjenkins
处理方式 :用 sed 命令将 / 替换为 %2F,确保后续拼接Job路径时匹配Jenkins实际名称。
2. 触发Job的URL vs 下载制品的URL
Jenkins中,"触发Job"和"下载制品"的路径格式不同,这是最容易出错的点:
| 场景 | 路径格式说明 | 示例(基于分支 wip/jenkins) |
|---|---|---|
| 触发Job | 用「Jenkins项目全称」,层级用 / 分隔,无 job/ 前缀(Jenkins内部识别格式)。 |
Middleware/Whiteboard/WhiteboardTools/WbresOnMacOSX/wip%2Fjenkins |
| 下载制品 | 用「浏览器URL格式」,每个层级需加 job/ 前缀(HTTP请求路径格式)。 |
http://jenkins-url/job/Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresOnMacOSX/job/wip%2Fjenkins/3/artifact/... |
核心区别 :触发Job时Jenkins接受"项目全称"(无 job/),而下载制品时的HTTP URL必须用 job/ 分隔层级。
3. 并行触发Job:为什么需要并行?
如果3个平台的子Job无依赖关系(可独立执行),并行触发能大幅缩短总构建时间:
- 顺序执行:总时间 = t1 + t2 + t3
- 并行执行:总时间 ≈ max(t1, t2, t3)
Jenkins通过 parallel 块实现并行,需注意语法(子阶段用键值对定义,支持 failFast 快速失败)。
三、详细步骤与配置
步骤1:定义分支名并转义
在Pipeline开始阶段,获取当前分支名并转义 / 为 %2F,确保后续路径正确。
groovy
stage('Set BRANCH_CODING') {
steps {
script {
// 获取当前分支名(Jenkins内置变量 BRANCH_NAME,如 wip/jenkins)
// 转义 / 为 %2F,适配Jenkins Job名称格式
env.BRANCH_CODING = sh(
script: '''echo "${BRANCH_NAME}" | sed 's/\\//%2F/g' ''', // 关键:用sed替换 / 为 %2F
returnStdout: true
).trim()
echo "转义后的分支名(适配Jenkins):${env.BRANCH_CODING}" // 输出:wip%2Fjenkins
}
}
}
步骤2:并行触发多平台子Job
用 parallel 块并发触发3个平台的子Job,每个子Job包含"触发→等待完成→下载制品"逻辑。
关键配置:
triggerJobPath:触发Job用的"项目全称"(无job/);dependJobPath:下载制品用的"URL路径"(含job/);build步骤:wait: true等待Job完成,propagate: true子Job失败时主Job也失败;failFast: true:可选,一个子Job失败则立即终止所有并行任务。
步骤3:下载制品并验证
子Job执行完成后,通过构建编号(buildNumber)精准定位最新制品,用 curl 下载并验证文件是否存在。
步骤4:聚合制品并存档
所有平台制品下载完成后,打包为zip并存档到Jenkins。
四、完整Pipeline脚本(带详细注释)
groovy
pipeline {
agent {
node {
label 'UBUNTU' // 运行主Pipeline的节点标签
}
}
// 可选:手动指定测试分支(实际场景用Jenkins内置的BRANCH_NAME)
environment {
// BRANCH_NAME = 'wip/jenkins' // 测试时可手动设置,正式环境注释掉
}
stages {
// 步骤1:处理分支名,转义 / 为 %2F
stage('Set BRANCH_CODING') {
steps {
script {
// 从Jenkins内置变量获取当前分支名(如 git 分支)
// 用sed将 / 替换为 %2F(适配Jenkins Job名称)
env.BRANCH_CODING = sh(
script: '''echo "${BRANCH_NAME}" | sed 's/\\//%2F/g' ''',
returnStdout: true
).trim()
echo "✅ 转义后的分支名:${env.BRANCH_CODING}" // 示例输出:wip%2Fjenkins
}
}
}
// 步骤2:并行触发3个平台的子Job并下载制品
stage('并行触发子Job并下载制品') {
steps {
script {
// 并行执行3个子阶段(MacOSX/Windows/Linux)
parallel(
failFast: true, // 快速失败:一个子Job失败,所有并行任务终止
// 子阶段1:MacOSX平台
"MacOSX": {
// 1. 定义路径(触发Job和下载制品的路径分开)
def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '') // 去除URL末尾的 /
// 触发Job用的项目全称(无 job/)
def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresOnMacOSX/${env.BRANCH_CODING}"
// 下载制品用的URL路径(含 job/)
def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresOnMacOSX/job/${env.BRANCH_CODING}"
def artifactSourcePath = 'tools/wbres/bin/macos/wbres' // 子Job中制品的相对路径
def artifactTargetDir = 'bin/macos/' // 本地保存路径
// 2. 触发子Job并等待完成
echo "=== [MacOSX] 触发子Job:${triggerJobPath} ==="
def buildResult = build(
job: triggerJobPath, // 传入项目全称(无 job/)
wait: true, // 等待子Job执行完成
propagate: true, // 子Job失败时,主Job也失败
quietPeriod: 0 // 立即触发,不延迟
)
def buildNumber = buildResult.getNumber() // 获取子Job的构建编号(如 3)
echo "=== [MacOSX] 子Job执行完成,构建编号:${buildNumber} ==="
// 3. 构建下载URL并下载制品
def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
sh "mkdir -p ${artifactTargetDir}" // 创建本地目录
echo "=== [MacOSX] 下载URL:${downloadUrl} ==="
// 用Jenkins凭证下载(避免明文密码)
withCredentials([usernamePassword(
credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090', // 你的凭证ID
usernameVariable: 'JENKINS_USER',
passwordVariable: 'JENKINS_PWD'
)]) {
sh """
# 用curl下载制品,-f确保失败时返回非0状态码
curl -f -L -v \
-u "${JENKINS_USER}:${JENKINS_PWD}" \
"${downloadUrl}" \
-o "${artifactTargetDir}/wbres"
# 验证下载结果
echo "=== [MacOSX] 验证制品 ==="
ls -l "${artifactTargetDir}"
if [ -f "${artifactTargetDir}/wbres" ]; then
echo "✅ [MacOSX] 制品下载成功"
else
echo "❌ [MacOSX] 制品下载失败"
exit 1 # 失败时终止Pipeline
fi
"""
}
},
// 子阶段2:Windows x64平台(逻辑与MacOSX一致,仅路径不同)
"Windows x64": {
def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '')
def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresX64OnWindows/${env.BRANCH_CODING}"
def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresX64OnWindows/job/${env.BRANCH_CODING}"
def artifactSourcePath = 'tools/wbres/bin/win32/wbres.exe'
def artifactTargetDir = 'bin/win32/'
echo "=== [Windows] 触发子Job:${triggerJobPath} ==="
def buildResult = build(job: triggerJobPath, wait: true, propagate: true, quietPeriod: 0)
def buildNumber = buildResult.getNumber()
echo "=== [Windows] 子Job执行完成,构建编号:${buildNumber} ==="
def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
sh "mkdir -p ${artifactTargetDir}"
echo "=== [Windows] 下载URL:${downloadUrl} ==="
withCredentials([usernamePassword(credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090', usernameVariable: 'JENKINS_USER', passwordVariable: 'JENKINS_PWD')]) {
sh """
curl -f -L -v -u "${JENKINS_USER}:${JENKINS_PWD}" "${downloadUrl}" -o "${artifactTargetDir}/wbres.exe"
echo "=== [Windows] 验证制品 ==="
ls -l "${artifactTargetDir}"
if [ -f "${artifactTargetDir}/wbres.exe" ]; then
echo "✅ [Windows] 制品下载成功"
else
echo "❌ [Windows] 制品下载失败"
exit 1
fi
"""
}
},
// 子阶段3:Linux (Docker)平台
"Linux (Docker)": {
def jenkinsUrl = env.JENKINS_URL.trim().replaceAll(/\/$/, '')
def triggerJobPath = "Middleware/Whiteboard/WhiteboardTools/WbresGccLinuxDocker/${env.BRANCH_CODING}"
def dependJobPath = "Middleware/job/Whiteboard/job/WhiteboardTools/job/WbresGccLinuxDocker/job/${env.BRANCH_CODING}"
def artifactSourcePath = 'tools/wbres/bin/linux/wbres'
def artifactTargetDir = 'bin/linux/'
echo "=== [Linux] 触发子Job:${triggerJobPath} ==="
def buildResult = build(job: triggerJobPath, wait: true, propagate: true, quietPeriod: 0)
def buildNumber = buildResult.getNumber()
echo "=== [Linux] 子Job执行完成,构建编号:${buildNumber} ==="
def downloadUrl = "${jenkinsUrl}/job/${dependJobPath}/${buildNumber}/artifact/${artifactSourcePath}"
sh "mkdir -p ${artifactTargetDir}"
echo "=== [Linux] 下载URL:${downloadUrl} ==="
withCredentials([usernamePassword(credentialsId: '2c66b483-ef17-436e-a0d2-114d00bdf090', usernameVariable: 'JENKINS_USER', passwordVariable: 'JENKINS_PWD')]) {
sh """
curl -f -L -v -u "${JENKINS_USER}:${JENKINS_PWD}" "${downloadUrl}" -o "${artifactTargetDir}/wbres"
echo "=== [Linux] 验证制品 ==="
ls -l "${artifactTargetDir}"
if [ -f "${artifactTargetDir}/wbres" ]; then
echo "✅ [Linux] 制品下载成功"
else
echo "❌ [Linux] 制品下载失败"
exit 1
fi
"""
}
}
)
}
}
}
// 步骤3:聚合所有平台的制品并存档
stage('聚合制品并存档') {
steps {
script {
// 将所有平台的制品打包为zip
sh 'mkdir -p bin && zip -r wbres_all_platforms.zip bin/'
}
// 存档到Jenkins(仅当所有步骤成功时)
archiveArtifacts(
artifacts: 'wbres_all_platforms.zip',
fingerprint: true,
onlyIfSuccessful: true
)
echo "✅ 所有平台制品已聚合并存档:wbres_all_platforms.zip"
}
}
}
// 构建结束后通知(可选)
post {
success {
echo "🎉 主Pipeline执行成功!所有制品已就绪。"
// 可添加邮件/企业微信通知:emailext to: 'xxx@example.com', subject: '构建成功', body: '...'
}
failure {
echo "❌ 主Pipeline执行失败,请查看日志。"
// 失败通知:emailext to: 'xxx@example.com', subject: '构建失败', body: '...'
}
}
}
五、调试与验证技巧
-
验证分支名转义 :
查看
Set BRANCH_CODING阶段的输出,确认BRANCH_CODING正确转义(如wip/jenkins→wip%2Fjenkins)。 -
检查触发Job的路径 :
在Jenkins网页端导航到子Job,查看浏览器URL中的"项目全称"(如
Middleware/Whiteboard/.../wip%2Fjenkins),与脚本中的triggerJobPath对比是否一致。 -
验证下载URL :
下载阶段输出的
downloadUrl可直接复制到浏览器,用Jenkins账号密码访问,确认能否下载制品(排除URL格式错误)。 -
并行执行日志 :
在Jenkins Pipeline页面,并行阶段会显示为"分支",可点击单个分支查看详细日志(如MacOSX子阶段的触发和下载过程)。
六、常见问题与解决
-
"No item named ... found" :
原因:
triggerJobPath与Jenkins实际项目全称不匹配(如未转义/或多了job/)。解决:按"触发Job的URL格式"修正
triggerJobPath,确保无job/且/已转义为%2F。 -
并行阶段语法错误 :
原因:
parallel块内用stage(...) { ... }定义子阶段,或failFast位置错误。解决:子阶段用键值对
"名称": { ... }定义,failFast作为parallel函数的参数(如parallel(failFast: true, "子阶段1": { ... }))。 -
制品下载404 :
原因:
downloadUrl中的dependJobPath未加job/层级,或构建编号错误。解决:确保
dependJobPath每个层级都有job/,并通过buildResult.getNumber()获取正确的构建编号。
通过以上步骤,你可以实现"并行触发多平台Job→等待完成→下载制品→聚合存档"的完整流程,大幅提升构建效率并确保制品版本一致性。