原文:Jenkins 文档贡献者,Extending with Shared Libraries。本文为中文翻译整理,依据 2026-10-05 读取的官方手册,保留完整技术流程与重要代码,并补充标明静态安全检查。Jenkins 网站内容采用 CC BY-SA 4.0;本译文按相同许可保留署名及修改说明。原文未提供个人作者署名。
当越来越多项目采用 Pipeline,检出代码、构建、测试和通知往往会出现重复模式。共享库把这些逻辑放进外部源代码仓库,再由各项目的 Jenkinsfile 加载,减少重复维护。这是原文所说的 DRY(Don’t Repeat Yourself)原则。库本身仍然是可执行代码:它的存放位置、权限、加载时机与版本,都会影响流水线行为。

1. 定义库名、来源与版本
一份共享库至少需要一个简短名称和代码获取方式,通常是 SCM,也可指定默认版本。Git 的分支、标签、提交哈希都可作为版本。配置还决定库是否默认隐式加载,或必须由脚本显式请求;管理员可以禁止脚本覆盖默认版本。
优先选用支持按名称检出任意版本的 Modern SCM,原文列举 Git 与 Subversion 插件。尚未支持这套接口的插件可选择 Legacy SCM,并在分支、标签或引用位置嵌入 ${library.yourLibName.version}。例如 Subversion 仓库 URL 可以设置为:
svnserver/project/${library.yourLibName.version}
随后使用 trunk、branches/dev、tags/1.0 之类的版本,检出时由插件展开。支持情况取决于已安装插件,应按当前实例的 Pipeline Syntax 生成器核对。
2. 仓库目录各司其职
(root)
+- src
| +- org
| +- foo
| +- Bar.groovy
+- vars
| +- foo.groovy
| +- foo.txt
+- resources
+- org
+- foo
+- bar.json
src/ 采用 Java 源码目录式的包结构,例如 org/foo/Bar.groovy 定义 org.foo.Bar。执行 Pipeline 时此目录加入 classpath。vars/ 内的脚本以文件名作为流水线可见的全局变量名:vars/log.groovy 中有 info 方法,即可用 log.info "hello world" 调用。同一文件能包含多个方法。
Groovy 文件名应是合法的 Groovy/Java 标识符,惯例是 camelCase。可选的同名 .txt 文件是帮助文档,由系统配置的 markup formatter 处理,因此内容可以实际采用 HTML 或 Markdown,但扩展名必须为 .txt。只有导入该库的 Pipeline 作业成功运行过一次,帮助才会出现在作业侧栏可访问的 Global Variable Reference 中。
src 和 vars 中的 Groovy 代码都经历 Scripted Pipeline 相同的 CPS 转换。resources/ 保存非 Groovy 文件,供外部库的 libraryResource 加载;原文指出内部库不支持此功能。根目录的其他目录保留给未来扩展。
3. 全局、文件夹和自动库的权限差别
在 Manage Jenkins → System → Global Trusted Pipeline Libraries 中可以注册全局可信库,系统中任何 Pipeline 都能调用。可信库可访问 Java、Groovy、Jenkins 内部 API、插件和第三方库,适合把原本危险的底层 API 封装成范围受限的高层功能。但拥有该库仓库推送权限的人,可能获得 Jenkins 的无限制访问能力。配置可信库需要 Overall/RunScripts,通常由管理员拥有;仅有 Overall/Manage 时,只能配置在 Groovy sandbox 中运行的全局不可信库。
文件夹也能配置共享库,其作用域包括该文件夹及子文件夹内的 Pipeline。文件夹级库始终是不可信库。其他插件还可临时定义库,例如 Pipeline: GitHub Groovy Libraries 支持以 github.com/someorg/somerepo 命名,匿名检出该仓库的 master 分支,作为不可信库使用。这里的 master 是原文插件示例,不保证任意仓库都有此分支。
编辑补充:“可信”代表给予执行能力,不代表代码经过安全认证。应对库仓库实施评审、受保护分支及最小写入权限;生产消费端宜使用已审核、可追溯的固定提交。将任意构建参数直接变成可信库版本选择,可能绕过原本预期的发布边界。
4. 在编译期用 @Library 加载
勾选 Load implicitly 后,流水线可以直接使用该库中的类和全局变量。其他库需要 Jenkinsfile 注解:
@Library('my-shared-library') _
@Library('my-shared-library@1.0') _
@Library(['my-shared-library', 'otherlib@abc1234']) _
这三行分别展示默认版本、指定版本、一次导入多个库,实际按需选择使用。注解可放在 Groovy 允许注解的位置。涉及 src 类时,惯常附在 import 上:
@Library('somelib')
import com.mycorp.pipeline.somelib.UsefulClass
只使用 vars 全局变量时,给符号下划线加注解即可,无需多余的 import。不建议 import 全局变量或函数,因为编译器可能将字段、方法视为 static,导致难以理解的错误。共享库在脚本开始执行之前的编译阶段解析和加载,因此可参与静态类型检查及类型声明:
@Library('somelib')
import com.mycorp.pipeline.somelib.Helper
int useSomeLib(Helper helper) {
helper.prepare()
return helper.count()
}
echo useSomeLib(new Helper('some text'))
需要区分:类所在的 classpath 在编译前准备好,全局变量本身则在运行时解析。原文另附共享库资源文件视频,视频示例仓库链接位于其说明中;本文按书面正文翻译,未声称观看或复现视频。
5. 用 library 在运行期动态加载
从 Pipeline: Shared Groovy Libraries 插件 2.7 起,可以在构建运行中用 library 步骤加载尚未隐式加载的库。对 vars 函数:
library 'my-shared-library'
后续即可访问库中的全局变量。src 类也可使用,但遇到 library 时脚本已编译完成,不能再 import,也不能把其类作为静态类型声明。应从 library 的返回对象沿完整包名访问:
library('my-shared-library').com.mycorp.pipeline.Utils.someStaticMethod()
def useSomeLib(helper) {
helper.prepare()
return helper.count()
}
def lib = library('my-shared-library').com.mycorp.pipeline
echo useSomeLib(lib.Helper.new(lib.Constants.SOME_TEXT))
这里 helper 不声明 Helper 类型;静态字段可直接访问,构造器以名为 new 的方法调用。动态方式牺牲静态类型检查,以换取运行时选择。
6. 默认版本、运行时版本与获取方式
隐式加载,或只按名称引用库时,使用配置的 Default version。若没有默认版本,使用方必须明确指定,例如:
@Library('my-shared-library@master') _
开启 Allow default version to be overridden 后,@Library 可覆盖默认版本,包括隐式库的版本。library 也支持版本,并可计算版本值:
library 'my-shared-library@master'
library "my-shared-library@$BRANCH_NAME"
properties([parameters([string(name: 'LIB_VERSION', defaultValue: 'master')])])
library "my-shared-library@${params.LIB_VERSION}"
第二种形式让库使用与多分支 Jenkinsfile 同名的分支;第三种由参数选择。library 无法覆盖已隐式加载的库,因为开始执行时该库已经载入,同一名称的库不能加载两次。
Modern SCM 优先;Legacy SCM 需要在 SCM 配置中使用版本变量,例如 svn://svn.example.com/pipeline-library/branches/${library.my-shared-library.version}。原文截图中的默认版本 Stable、缓存刷新 37 分钟等只用于演示配置,不是推荐通用值。
还可以在 library 步骤中直接给出获取方式,无需事先在 Jenkins 注册:
library identifier: 'custom-lib@master', retriever: modernSCM(
[$class: 'GitSCMSource',
remote: 'git@git.mycorp.com:my-jenkins-utils.git',
credentialsId: 'my-private-key'])
此时必须指定版本。credentialsId 是 Jenkins 凭据存储中的引用,不是私钥内容;精确语法应由当前 SCM 对应的 Pipeline Syntax 生成。仓库地址、身份、可选择的版本都应受控制,不能将不可信输入自由拼接到高权限库来源。
7. 缓存的包含与排除规则
启用库版本缓存可以缩短获取时间。Versions to exclude 接受完整版本名或子串,例如 feature/versions_to_exclude 排除该版本,test/ 排除名称含 test/ 的版本。Versions to include 则仅让匹配版本进入缓存,同样支持完整名称或子串;为空时默认允许缓存。若一个版本同时匹配包含和排除,排除优先。缓存是取回优化,不替代版本固定、评审或访问控制。
8. 编写类,并显式传入 Pipeline 步骤
基础数据结构可以是普通 Groovy 类:
// src/org/foo/Point.groovy
package org.foo
class Point {
float x, y, z
}
库中的普通类不能直接调用 sh、git 等 Pipeline 步骤。一种方法是在脚本主体定义方法,再返回 this:
// src/org/foo/Zot.groovy
package org.foo
def checkOutFrom(repo) {
git url: "git@github.com:jenkinsci/${repo}"
}
return this
// Jenkinsfile 中调用
def z = new org.foo.Zot()
z.checkOutFrom(repo)
这种方式有局限,例如无法在上述脚本式写法中声明超类。另一种方法把流水线脚本的 this 显式传给类:
package org.foo
class Utilities implements Serializable {
def steps
Utilities(steps) { this.steps = steps }
def mvn(args) {
steps.sh "${steps.tool 'Maven'}/bin/mvn -o ${args}"
}}
// Jenkinsfile
@Library('utils') import org.foo.Utilities
def utils = new Utilities(this)
node {
utils.mvn 'clean package'
}
当类保存状态时,应实现 Serializable,才能让使用它的 Pipeline 正确暂停和恢复。需要 env 等全局变量时,也应显式传入类或方法。原文另展示将整个脚本传入静态方法:
package org.foo
class Utilities {
static def mvn(script, args) {
script.sh "${script.tool 'Maven'}/bin/mvn -s ${script.env.HOME}/jenkins.xml -o ${args}"
}
}
// Jenkinsfile
@Library('utils') import static org.foo.Utilities.*
node {
mvn this, 'clean package'
}
静态审查:上两段 mvn 方法保持原文写法,用于说明如何访问步骤。若 args 来自构建参数、分支名或其他不可信输入,插值进入 sh 字符串会产生 Shell 命令注入风险;工具路径、HOME 中的空格也没有得到可靠引用。应把可执行操作限制为受审核的固定命令或严格白名单参数,并正确处理路径及参数引用。这里没有提供一个未经实测的“通用安全 Shell 拼接器”。
9. vars 全局变量要保持无状态
vars 脚本按需实例化为单例,因此可把多个辅助方法放进同一个文件:
// vars/log.groovy
def info(message) {
echo "INFO: ${message}"
}
def warning(message) {
echo "WARNING: ${message}"
}
// Jenkinsfile
@Library('utils') _
log.info 'Starting'
log.warning 'Nothing to do!'
Declarative Pipeline 不允许在 script 之外对对象调用方法,见 JENKINS-42360。因此在声明式流水线中应使用:
@Library('utils') _
pipeline {
agent none
stages {
stage('Example') {
steps {
script {
log.info 'Starting'
log.warning 'Nothing to do!'
}
}
}
}
}
只有库被加载、使用且流水线成功运行后,变量才出现在 Global Variables Reference。共享库全局变量应充当函数集合,不保存构建状态;控制器重启后这种状态可能丢失。应使用适当的类实例及局部变量,并为跨暂停保存的对象满足序列化要求。原文允许只读字段,但不鼓励把可变状态藏在 vars 中:
@groovy.transform.Field
def yourField = "YourConstantValue"
10. 把函数封装成自定义步骤
vars 名称须使用全小写或 camelCase,才能按预期加载。定义 call 方法后,全局变量就能像内置步骤一样调用:
// vars/sayHello.groovy
def call(String name = 'human') {
echo "Hello, ${name}."
}
// Jenkinsfile
sayHello 'Joe'
sayHello()
如果步骤接受一个代码块,call 接收 Closure,显式写出该类型可以表明意图:
// vars/windows.groovy
def call(Closure body) {
node('windows') {
body()
}
}
// Jenkinsfile
windows {
bat "cmd /?"
}
多个项目的流水线结构相似时,可进一步构建领域专用语言。原文以 Jenkins 插件构建为例:
// vars/buildPlugin.groovy
def call(Map config) {
node {
git url: "https://github.com/jenkinsci/${config.name}-plugin.git"
sh 'mvn install'
mail to: '...', subject: "${config.name} plugin build", body: '...'
}}
// Jenkinsfile(Scripted Pipeline)
buildPlugin name: 'git'
此示例须先配置全局或文件夹库,通知中的省略号是原文示意,使用时需提供真实配置。构建仓库内容及 Maven 构建脚本本身可执行代码,不能因只出现固定 mvn 命令就忽略仓库信任问题。原文也提到利用 Closure.DELEGATE_FIRST 的 builder 模式,但因复杂且容易出错而不推荐。
11. 第三方库与资源文件
可信库理论上可通过 @Grab 从 Maven Central 等来源加载第三方 Java 库:
@Grab('org.apache.commons:commons-math3:3.4.1')
import org.apache.commons.math3.primes.Primes
void parallelize(int count) {
if (!Primes.isPrime(count)) {
error "${count} was not prime"
}
// 后续逻辑由调用者定义
}
原文明确不推荐这种方式:它存在多种问题,第三方依赖默认缓存在控制器的 ~/.groovy/grapes/。推荐把所需库封装为独立可执行程序,安装在流水线使用的 agent 上,再用 sh 或 bat 调用。3.4.1 是原文历史示例版本,并不代表当前安全版本;本文没有检查其依赖漏洞数据库,也没有下载或执行依赖。
外部共享库可从 resources/ 读取辅助文件:
def request = libraryResource 'com/mycorp/pipeline/somelib/request.json'
返回值是字符串,可传给 API 或用 writeFile 写入工作区。路径类似 Java 资源加载,采用独有包结构可避免多个库发生资源命名冲突。
12. 用 Replay 和 PR 验证修改
使用不可信库的构建出现问题时,可以通过 Replay 编辑一个或多个库源文件并重新运行;确认行为后,从构建状态页获取差异,应用到库仓库并提交。即使原构建请求的是分支,Replay 仍使用原构建的精确 revision,不会重新检出最新分支。原文指出:可信库不支持 Replay,Replay 也不支持修改资源文件。
如果共享库在 GitHub,且库 SCM 使用 GitHub,可以在消费端 Jenkinsfile 顶部指定待测 PR 的引用:
@Library('my-shared-library@pull/<your-pr-number>/head') _
Assembla、Bitbucket、Gitea、GitLab、Tuleap 等服务需遵循各自 PR/MR 分支命名规则。官方案例是 jenkins-infra/pipeline-library 的 PR 123,由 专用测试仓库 验证:
@Library('pipeline-library@pull/123/head') _
buildPlugin(
useContainerAgent: true,
configurations: [
[platform: 'linux', jdk: 21],
[platform: 'windows', jdk: 17],
])
编辑提醒:待测 PR 等同将未合并代码放进运行环境;需隔离 agent、限制凭据和网络访问,可信库尤其不能允许任意外部贡献直接拿到控制器能力。本文仅说明官方测试方法,未运行该 PR 或示例构建。
13. 在共享库中定义 Declarative Pipeline
从 2017 年 9 月发布的 Declarative 1.2 起,共享库也能定义完整声明式流水线。原文用构建编号奇偶选择两个不同 pipeline:
// vars/evenOrOdd.groovy
def call(int buildNumber) {
if (buildNumber % 2 == 0) {
pipeline {
agent any
stages {
stage('Even Stage') {
steps { echo "The build number is even" }
}
}
}
} else {
pipeline {
agent any
stages {
stage('Odd Stage') {
steps { echo "The build number is odd" }
}
}
}
}
}
// Jenkinsfile
@Library('my-shared-library') _
evenOrOdd(currentBuild.getNumber())
这里必须在 vars/*.groovy 的 call 方法内定义完整 pipeline。一次构建只能执行一个 Declarative Pipeline,尝试执行第二个会失败。不能把“可以写在库里”理解为可以随意拼接多个完整 pipeline。
14. 本文审查范围
本稿依据完整书面源文整理,保留定义、目录、作用域、两种加载方式、版本获取、缓存、步骤访问、全局状态、自定义 DSL、第三方库、资源、Replay、声明式流水线和 PR 测试的全部实质主题。代码仅做静态检查:已指出 args 拼接、可变版本选择和可信库仓库权限的实际风险;未发现真实硬编码秘密,但不代表不存在漏洞。本文没有 Jenkins 控制器、agent、SCM 凭据或插件运行测试,示例输出与成功行为均属于源文说明,不是本次执行结果。
原文配置界面图
以下七幅为Jenkins官方原图,按原比例保留,便于对应前述选项;并非本文执行截图。图像及文档来源:Jenkins文档贡献者,原文。原图未改动,中文图注由未完纪补充。







本文译文、中文补充及源图按Creative Commons Attribution-ShareAlike 4.0 International发布;修改日期2026-10-05,按原样提供,不作担保,不暗示Jenkins项目认可。












暂无评论内容