Use this guide when you want to move a plugin build from org.jenkins-ci.jpi to org.jenkins-ci.jpi2.
The short version is that jpi2 is the path for modern Gradle builds, while jpi is now the legacy option for older Gradle versions.
Plan to migrate when you need Gradle 8 or newer.
Keep a copy of your current jpi build script nearby while you make the switch.
If you need to stay on Gradle 7 or earlier for now, keep using jpi and refer to legacy-jpi.md.
Replace the legacy plugin ID with org.jenkins-ci.jpi2.
plugins {
id 'org.jenkins-ci.jpi' version '<old-version>'
}plugins {
id("org.jenkins-ci.jpi2") version "<current-version>"
}The legacy plugin could configure repositories for you.
jpi2 expects you to declare the repositories you need in your build.
repositories {
mavenCentral()
jenkinsPublic()
}jenkinsPublic() is the shorthand for the Jenkins public repository.
jenkinsIncrementals() is the shorthand for the Jenkins incrementals repository.
jenkinsSnapshots() is the shorthand for the Jenkins snapshots repository.
These same shortcuts are available inside publishing { repositories { } }.
publishToJenkins() is a convenience shortcut that automatically selects the right publishing repository based on the project version.
Several legacy settings map directly to jpi2.
In Kotlin DSL, these can be written with direct assignment, which keeps the migrated block close to the old shape.
jenkinsVersion = "..."staysjenkinsVersion = "...".shortName = "..."becomespluginId = "...".displayName = "..."staysdisplayName = "...".url = "..."becomeshomePage = uri("...").compatibleSinceVersion = "..."stayscompatibleSinceVersion = "...".pluginFirstClassLoader = truestayspluginFirstClassLoader = true.maskClasses = "a b"becomesmaskClasses.add("a")andmaskClasses.add("b").fileExtension = "hpi"becomesarchiveExtension = "hpi".developers { ... }andlicenses { ... }still exist.
Here is a minimal before and after example.
jenkinsPlugin {
jenkinsVersion = '2.440.1'
shortName = 'example'
displayName = 'Example Plugin'
url = 'https://github.com/jenkinsci/example-plugin'
fileExtension = 'hpi'
}jenkinsPlugin {
jenkinsVersion = "2.492.3"
pluginId = "example"
displayName = "Example Plugin"
homePage = uri("https://github.com/jenkinsci/example-plugin")
archiveExtension = "hpi"
}The jpi2 extension intentionally drops several jpi convenience settings.
Handle these concerns directly in your build instead of expecting them on jenkinsPlugin.
configureRepositoriesis no longer needed because repositories are declared explicitly inrepositories { ... }.configurePublishingis no longer needed becausejpi2always configures Maven publication wiring.repoUrl,snapshotRepoUrl, andincrementalsRepoUrlare replaced by thepublishToJenkins()shortcut inpublishing { repositories { } }. It selects the correct repository URL based on the project version and uses GradlePasswordCredentialsfor authentication. See the README for details.gitHubUrlandscmTagdo not have directjpi2extension equivalents.disabledTestInjectiondoes not have ajpi2replacement on the extension.enableSpotBugs(),enableCheckstyle(), andenableJacoco()are not built intojpi2.
workDir is available on the jpi2 extension and defaults to ${projectDir}/work.
That value is used by both server and hplRun.
If you need a one-off override, pass -Pjpi2.workDir=....
The Gradle property takes precedence over jenkinsPlugin.workDir.
If you were using custom repoUrl or snapshotRepoUrl values to publish to a private repository, configure it directly in publishing { repositories { } }.
publishing {
repositories {
maven {
name = "internal"
url = uri("https://maven.example.com/releases")
credentials {
username = providers.gradleProperty("internalUsername").get()
password = providers.gradleProperty("internalPassword").get()
}
}
}
}See the Gradle publishing documentation for the full set of repository options.
If you need custom publishing metadata, configure the generated MavenPublication directly.
publishing {
publications.withType<MavenPublication>().configureEach {
pom {
scm {
connection.set("scm:git:https://github.com/jenkinsci/example-plugin.git")
developerConnection.set("scm:git:git@github.com:jenkinsci/example-plugin.git")
tag.set("HEAD")
url.set("https://github.com/jenkinsci/example-plugin")
}
}
}
}Most day-to-day task names stay familiar, but a few details change.
jpistill builds the plugin archive.serverstill starts a local Jenkins instance.hplRunandtestHplRunare newjpi2tasks for HPL-based development and verification.localizeMessagesis the supported localization task in both plugins.localizerOutputDirshould be replaced with direct configuration of thelocalizeMessagestask.
tasks.named<org.jenkinsci.gradle.plugins.jpi2.localization.LocalizationTask>("localizeMessages") {
outputDir.set(layout.buildDirectory.dir("generated-src/localizer"))
}For the Jenkins HTTP port, set the server.port system property instead of using the legacy server --port=... convention.
./gradlew server -Dserver.port=8090For the Jenkins work directory, use the extension for the steady-state default.
jenkinsPlugin {
workDir = layout.projectDirectory.dir("custom-work")
}testServer and testHplRun use temporary work directories automatically so they can run safely in parallel.
Set jpi2.preserveTestWorkDir=true when you want to keep those directories for debugging.
Legacy jpi users often relied on generateGitVersion.
jpi2 still supports Git-based versions, but you now choose the source explicitly with versionSource.
jenkinsPlugin {
versionSource.set(org.jenkinsci.gradle.plugins.jpi2.VersionSource.GIT)
gitVersion {
allowDirty.set(true)
versionFormat.set("%d.%s")
}
}Use VersionSource.PROJECT when project.version should stay authoritative.
Use VersionSource.FIXED when you want the plugin to publish a fixed string that is separate from project.version.
After the migration, run your normal verification tasks and then boot Jenkins locally.
At a minimum, verify ./gradlew check, ./gradlew jpi, and either ./gradlew server or ./gradlew hplRun.