Building and Using a Jenkins Shared Library

Abbreviations

Abbreviation Full name Meaning here
CI / CD Continuous Integration / Continuous Delivery The build-test-deploy automation a shared library standardises
DSL Domain-Specific Language The small, readable syntax a library exposes to Jenkinsfiles (buildApp { ... })
CPS Continuation-Passing Style How Jenkins rewrites Pipeline Groovy so a build can pause, survive a restart and resume
SCM Source Code Management The Git repository Jenkins loads the library from
IDE Integrated Development Environment Editor support such as completion inside DSL closures
API Application Programming Interface The public steps a library promises not to break
JVM Java Virtual Machine The single process in which all builds on a controller share classes

1. The short answer

A Jenkins Shared Library is a Git repository of Pipeline Groovy code that many Jenkinsfiles load and call. Instead of copy-pasting the same 80 lines of "log in to the registry, install, test, build, push" into every repository, each Jenkinsfile shrinks to a few readable calls, and the logic lives --- versioned, reviewed and tested --- in one place.

A well-structured library has three layers:

Layer Location Job
DSL entry point vars/<step>.groovy Becomes a global step (buildApp). Thin: wire up config, delegate.
Configuration object src/.../<Step>Config.groovy Holds the user's settings, separate from behaviour.
Implementation class src/.../<Step>.groovy Does the work by calling real Jenkins steps (sh, withCredentials, ...).

The rest of this article builds one such step, buildApp, end to end.

2. Repository layout

text 复制代码
jenkins-shared-library/
├── vars/                       # global steps: file name = step name
│   ├── buildApp.groovy
│   └── buildApp.txt            # help text shown in the Pipeline Syntax page
├── src/                        # ordinary Groovy classes on the classpath
│   └── com/example/jenkins/
│       └── build/
│           ├── BuildApp.groovy
│           └── BuildAppConfig.groovy
├── resources/                  # non-Groovy files, read with libraryResource
│   └── com/example/jenkins/build/settings.xml
├── test/                       # unit tests (JenkinsPipelineUnit)
└── README.md
  • vars/ --- each foo.groovy becomes a global variable foo available in every Pipeline that loads the library. A call method makes foo(...) / foo { ... } work; other methods are reached as foo.bar(...).
  • src/ --- standard Java-style package directories, added to the classpath. Import them with import com.example....
  • resources/ --- loaded as text with libraryResource 'com/example/jenkins/build/settings.xml', handy for config templates and scripts.

3. How the pieces fit

#mermaid-svg-uw1bJjAi4Pjhfzju{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uw1bJjAi4Pjhfzju .error-icon{fill:#552222;}#mermaid-svg-uw1bJjAi4Pjhfzju .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uw1bJjAi4Pjhfzju .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uw1bJjAi4Pjhfzju .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .marker.cross{stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uw1bJjAi4Pjhfzju p{margin:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label text{fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label span{color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster-label span p{background-color:transparent;}#mermaid-svg-uw1bJjAi4Pjhfzju .label text,#mermaid-svg-uw1bJjAi4Pjhfzju span{fill:#333;color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .node rect,#mermaid-svg-uw1bJjAi4Pjhfzju .node circle,#mermaid-svg-uw1bJjAi4Pjhfzju .node ellipse,#mermaid-svg-uw1bJjAi4Pjhfzju .node polygon,#mermaid-svg-uw1bJjAi4Pjhfzju .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .rough-node .label text,#mermaid-svg-uw1bJjAi4Pjhfzju .node .label text,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label,#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label{text-anchor:middle;}#mermaid-svg-uw1bJjAi4Pjhfzju .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .rough-node .label,#mermaid-svg-uw1bJjAi4Pjhfzju .node .label,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label,#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label{text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .node.clickable{cursor:pointer;}#mermaid-svg-uw1bJjAi4Pjhfzju .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .arrowheadPath{fill:#333333;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uw1bJjAi4Pjhfzju .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster text{fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju .cluster span{color:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uw1bJjAi4Pjhfzju .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uw1bJjAi4Pjhfzju rect.text{fill:none;stroke-width:0;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape p,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uw1bJjAi4Pjhfzju .icon-shape .label rect,#mermaid-svg-uw1bJjAi4Pjhfzju .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uw1bJjAi4Pjhfzju .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uw1bJjAi4Pjhfzju .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uw1bJjAi4Pjhfzju :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Jenkinsfile

@Library('jenkins-shared-library') _
vars/buildApp.groovy

(DSL entry point)
BuildAppConfig

(settings)
BuildApp

(implementation)
Pipeline steps

withCredentials, withEnv, sh
Build / test / deploy

on the agent

This diagram answers: when a Jenkinsfile calls buildApp, which file does what?

4. The configuration object

Settings live in a plain class, kept apart from the code that acts on them:

groovy 复制代码
// src/com/example/jenkins/build/BuildAppConfig.groovy
package com.example.jenkins.build

class BuildAppConfig implements Serializable {
    String credentialsId
    String registryUrl

    void validate(def script) {
        if (!credentialsId) script.error('buildApp: credentialsId is required')
        if (!registryUrl)   script.error('buildApp: registryUrl is required')
    }
}

Why Serializable? Pipeline Groovy runs under CPS (Continuation-Passing Style): at every step boundary Jenkins may serialise the whole program state to disk so the build can survive a controller restart. Any object still referenced at that point --- including this config --- must be serialisable, or the build fails with NotSerializableException.

5. The implementation class

The implementation receives the Pipeline script object so it can call Jenkins steps on it. Classes in src/ do not see sh or withCredentials on their own --- those are methods of the running Pipeline script.

groovy 复制代码
// src/com/example/jenkins/build/BuildApp.groovy
package com.example.jenkins.build

class BuildApp implements Serializable {
    private final def script
    private final BuildAppConfig config

    BuildApp(def script, BuildAppConfig config) {
        this.script = script
        this.config = config
    }

    void execute(Closure body) {
        config.validate(script)

        script.withCredentials([
            script.usernamePassword(
                credentialsId: config.credentialsId,
                usernameVariable: 'REGISTRY_USER',
                passwordVariable: 'REGISTRY_PASSWORD'
            )
        ]) {
            script.withEnv(["PACKAGE_REGISTRY=${config.registryUrl}"]) {
                body()
            }
        }
    }
}

The caller's closure runs inside the credentials and environment blocks, so its sh commands see REGISTRY_USER, REGISTRY_PASSWORD and PACKAGE_REGISTRY. withCredentials also masks the secret values in the build log.

6. The DSL entry point in vars/

The vars/ file is the public face. Keep it thin: find the configuration, build the implementation, delegate.

groovy 复制代码
// vars/buildApp.groovy
import com.example.jenkins.build.BuildApp
import com.example.jenkins.build.BuildAppConfig

// buildApp.configure { credentialsId = '...'; registryUrl = '...' }
def configure(@DelegatesTo(BuildAppConfig) Closure closure) {
    closure.delegate = config()
    closure.resolveStrategy = Closure.DELEGATE_FIRST
    closure()
}

// buildApp { sh '...' }
def call(Closure body) {
    new BuildApp(this, config()).execute(body)
}

private BuildAppConfig config() {
    if (!binding.hasVariable('__buildAppConfig')) {
        binding.setVariable('__buildAppConfig', new BuildAppConfig())
    }
    binding.getVariable('__buildAppConfig')
}

Three details carry the design:

  1. Closure.DELEGATE_FIRST --- inside configure { credentialsId = 'x' }, the bare name credentialsId is resolved against the delegate (the config object) before anything else. That is what turns a closure into a configuration block.
  2. @DelegatesTo(BuildAppConfig) --- no runtime effect; it tells the IDE (Integrated Development Environment) what the delegate is, so completion and type checking work inside the block.
  3. Passing this --- in a vars/ script, this is a Pipeline script object, so BuildApp can call script.sh, script.withCredentials and script.error through it.

Why the configuration is kept in the binding

configure and call are two separate calls, so the settings must survive from one to the other. A vars/ script is instantiated once per build (a per-build singleton), and its binding lives as long as that build. Storing the config there means:

  • buildApp.configure { ... } followed later by buildApp { ... } in the same build shares the same settings;
  • two builds running at the same time never see each other's settings.

Do not use static fields for this. Static state lives in the library's class loader, outside any single build, and concurrent builds would overwrite each other's credentials ID or registry URL.

Help text

vars/buildApp.txt (plain text or HTML) appears under Pipeline Syntax → Global Variables Reference once the library has been loaded by a build. Treat it as the user documentation of the step.

7. Making the library available

Register the library once in Manage Jenkins → System → Global Trusted Pipeline Libraries (or on a folder, for a team-scoped library):

Setting Recommendation
Name jenkins-shared-library --- the name Jenkinsfiles refer to
Default version A tag or a stable branch (v1.4.0, main)
Allow default version to be overridden On, so a Jenkinsfile can pin or test a version
Load implicitly Usually off --- explicit @Library makes the dependency visible
Retrieval method Modern SCM → Git, pointing at the library repository

Trust levels matter. A global library runs outside the Groovy sandbox and can call any Jenkins API; a folder-level library runs inside the sandbox. Protect the global library's repository like production code: required reviews, protected branches, tagged releases.

8. Loading it from a Jenkinsfile

groovy 复制代码
@Library('jenkins-shared-library') _            // default version
@Library('jenkins-shared-library@v1.4.0') _     // pinned to a tag
@Library('jenkins-shared-library@feature/x') _  // try a branch before release

The trailing _ is not a typo: a Groovy annotation must decorate something , and _ is a throwaway target when you only need the global steps from vars/. If you also want to import classes from src/, put the annotation on the import instead:

groovy 复制代码
@Library('jenkins-shared-library') import com.example.jenkins.build.BuildAppConfig

The library step loads a library dynamically at run time, for example when the version comes from a parameter:

groovy 复制代码
library "jenkins-shared-library@${params.LIB_VERSION}"

9. A complete Jenkinsfile

groovy 复制代码
@Library('jenkins-shared-library@v1.4.0') _

pipeline {
    agent any

    stages {
        stage('Build') {
            steps {
                script {
                    buildApp.configure {
                        credentialsId = 'registry-credentials'
                        registryUrl   = 'https://packages.example.com/'
                    }
                }

                buildApp {
                    sh '''
                        set -eu
                        package-manager install
                        package-manager test
                        package-manager build
                    '''
                }
            }
        }
    }
}

Why is configure in a script {} block? Declarative Pipeline allows steps in steps {} --- and a global variable's call method counts as a step, so buildApp { ... } is fine. But buildApp.configure { ... } is a method call on an object, which Declarative rejects outside script {}. A scripted Pipeline (node { ... }) has no such restriction.

10. What happens at run time

Jenkins steps BuildApp BuildAppConfig vars/buildApp Jenkinsfile Jenkins steps BuildApp BuildAppConfig vars/buildApp Jenkinsfile #mermaid-svg-vsFxxDBN7KeTTA8s{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vsFxxDBN7KeTTA8s .error-icon{fill:#552222;}#mermaid-svg-vsFxxDBN7KeTTA8s .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vsFxxDBN7KeTTA8s .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vsFxxDBN7KeTTA8s .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vsFxxDBN7KeTTA8s .marker.cross{stroke:#333333;}#mermaid-svg-vsFxxDBN7KeTTA8s svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vsFxxDBN7KeTTA8s p{margin:0;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vsFxxDBN7KeTTA8s .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .sequenceNumber{fill:white;}#mermaid-svg-vsFxxDBN7KeTTA8s #sequencenumber{fill:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-vsFxxDBN7KeTTA8s .messageText{fill:#333;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s .labelText,#mermaid-svg-vsFxxDBN7KeTTA8s .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .loopText,#mermaid-svg-vsFxxDBN7KeTTA8s .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-vsFxxDBN7KeTTA8s .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-vsFxxDBN7KeTTA8s .noteText,#mermaid-svg-vsFxxDBN7KeTTA8s .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-vsFxxDBN7KeTTA8s .actorPopupMenu{position:absolute;}#mermaid-svg-vsFxxDBN7KeTTA8s .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-vsFxxDBN7KeTTA8s .actor-man circle,#mermaid-svg-vsFxxDBN7KeTTA8s line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-vsFxxDBN7KeTTA8s :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} buildApp.configure { ... } create, store in binding run closure with config as delegate buildApp { sh ... } fetch from binding new BuildApp(this, config) validate() withCredentials + withEnv run caller's closure (sh ...) closure finished step returns

This diagram answers: in what order do the two calls touch the config, the implementation and Jenkins?

11. CPS pitfalls worth knowing

Most "it works in plain Groovy but not in Jenkins" bugs come from CPS:

Symptom Cause Fix
NotSerializableException A non-serialisable object (a Matcher, an iterator, a JSON parser result) is alive across a step boundary Make your classes Serializable; keep such objects in local scope inside a @NonCPS method
A collection method silently processes only the first element Some Groovy closures (each, collect, sort with a closure) behave oddly under CPS in older versions Prefer a plain for loop, or move the pure-data logic into a @NonCPS method
@NonCPS method fails when calling sh @NonCPS methods run as normal Groovy and cannot call Pipeline steps Use @NonCPS only for pure computation; call steps from normal methods
RejectedAccessException Code in a sandboxed (folder-level) library or Jenkinsfile calls an unapproved method Move the code into a trusted global library, or get the signature approved

12. Testing

Do not test a shared library only by pushing a branch and watching a real build. Unit-test it with JenkinsPipelineUnit (com.lesfurets:jenkins-pipeline-unit), which runs the scripts with mocked steps:

groovy 复制代码
// test/BuildAppTest.groovy
import com.lesfurets.jenkins.unit.BasePipelineTest
import org.junit.Before
import org.junit.Test

class BuildAppTest extends BasePipelineTest {
    @Before
    void setUp() {
        super.setUp()
        helper.registerAllowedMethod('usernamePassword', [Map]) { it }
        helper.registerAllowedMethod('withCredentials', [List, Closure]) { l, c -> c() }
        helper.registerAllowedMethod('withEnv', [List, Closure]) { l, c -> c() }
    }

    @Test
    void runsCallerClosureInsideCredentials() {
        def step = loadScript('vars/buildApp.groovy')
        step.configure {
            credentialsId = 'creds'
            registryUrl = 'https://packages.example.com/'
        }
        step.call { sh 'make build' }

        assert helper.callStack.find { it.methodName == 'sh' }
        assert helper.callStack.find { it.methodName == 'withCredentials' }
    }
}

Cover at least:

  • configuration defaults, validation and the error message for missing settings;
  • that the caller's closure runs inside withCredentials and withEnv;
  • the environment variables passed to the closure;
  • failure paths (error is called, nothing runs);
  • that no secret value is passed to echo or written to a file.

Then add one integration check: a throwaway Jenkinsfile that loads the library at @your-branch on a real controller, run before tagging a release.

13. Design guidelines

  • Keep vars/ thin; put logic in src/. Classes are easier to test, reuse and refactor.
  • Separate settings from behaviour. Config objects hold data; implementation classes act.
  • Validate early. Fail with a clear error('buildApp: registryUrl is required'), not a cryptic sh failure ten minutes later.
  • Never hard-code secrets or environment-specific URLs. Take credential IDs as parameters and resolve them with withCredentials.
  • No mutable static state. Builds run concurrently in one JVM.
  • Treat public steps as an API. Jenkinsfiles across the company depend on buildApp's syntax. Do not break it in a minor release; use semantic version tags and a changelog.
  • Pin versions in important pipelines. @Library('lib@v1.4.0') makes a library change an explicit, reviewable bump instead of a surprise.
  • Document every step in its vars/<step>.txt and the README.

14. Conclusion

A Jenkins Shared Library turns copy-pasted Pipeline code into a versioned,workspace/uploads/swagger-ui-integration-spring-boot tested internal product:

  • the vars/ entry point gives Jenkinsfiles a small, readable DSL;
  • the configuration object holds settings for one build, safely in the binding;
  • the implementation class does the work through real Pipeline steps, inside credential and environment scopes.

Add CPS awareness, unit tests with JenkinsPipelineUnit and disciplined versioning, and every repository gets the same reliable build behaviour from a few lines of Jenkinsfile --- with one place to fix it when it needs to change.

References

相关推荐
flytalei3 小时前
逐浪笔记 GitOps 实战:Jenkins 构建、Argo CD 同步与 Git 回滚
笔记·git·jenkins
flytalei1 天前
从 Jenkins 到 Argo CD:搭建一条完整的 GitOps 实战链路
运维·jenkins
你看我名字1 天前
LAMP 架构演练:从云主机准备到 Apache、PHP、MySQL 上线验证
运维·ci/cd·架构·kubernetes·jenkins
成为你的宁宁1 天前
【基于 SSH 与 JNLP 协议的 Jenkins 分布式构建方案】
分布式·jenkins
小白的码BUG之路5 天前
Jenkins -- 配置jdk和maven
java·jenkins·maven
9144062325 天前
安装jenkins遇到的问题总结
运维·jenkins
小白的码BUG之路5 天前
Jenkins -- 配置ssh
运维·ssh·jenkins
王琦03186 天前
jenkins使用手册
运维·jenkins
不老刘6 天前
Maven 多医院分支场景下 groupId 的设计与 Jenkins 落地实践
servlet·jenkins·maven