From 62d8d525c75ffe39a8cf83343f9743995869d6af Mon Sep 17 00:00:00 2001
From: Tim Jacomb <21194782+timja@users.noreply.github.com>
Date: Sat, 15 Aug 2026 21:55:31 +0100
Subject: [PATCH] Switch to forking jvm for development mode with hpi:run
(#27239)
---
.github/renovate.json | 9 --
CONTRIBUTING.md | 23 ++++-
pom.xml | 3 +-
war/nbactions.xml | 7 +-
war/pom.xml | 126 +++++++++++++------------
war/src/main/java/executable/Main.java | 42 +++++++--
war/src/realm.properties | 29 ------
7 files changed, 121 insertions(+), 118 deletions(-)
delete mode 100644 war/src/realm.properties
diff --git a/.github/renovate.json b/.github/renovate.json
index ea9952e5a3..e8b7afefd4 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -4,15 +4,6 @@
"prHourlyLimit": 0,
"prConcurrentLimit": 0,
"packageRules": [
- {
- "description": "Should be upgraded in lockstep in order to keep their corresponding Jetty versions aligned",
- "matchManagers": ["maven"],
- "groupName": "Winstone and Jetty",
- "matchPackageNames": [
- "org.eclipse.jetty.ee9:jetty-ee9-maven-plugin",
- "org.jenkins-ci:winstone"
- ]
- },
{
"description": "Provided by Jetty and should be aligned with the version provided by the version of Jetty we deliver. See: https://github.com/jenkinsci/jenkins/pull/5211",
"matchManagers": ["maven"],
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8e3fba0624..77d14d54cf 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -59,10 +59,22 @@ and then attach IDE Debugger to it.
To launch a development instance, after [building the WAR file](#building-the-war-file), run:
```sh
-MAVEN_OPTS='--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED' mvn -pl war jetty:run
+mvn -pl war hpi:run
```
-(Beware that `maven-plugin` builds will not work in this mode, due to class loading conflicts.)
+This forks a JVM that serves `war/target/jenkins` under Winstone, the same way `jenkins.war` is
+served in production. Jenkins is available on and uses
+`war/work` as its `JENKINS_HOME`; pass `-Dport=`, `-Dhost=` or `-DjenkinsHome=` to change that.
+
+Edits to `war/src/main/webapp`, and to Jelly views and their sibling `.properties` files under
+`core/src/main/resources`, are picked up on the next page load. Edits to a `Messages.properties`
+take effect when you restart the development instance — no repackaging needed, but the bundle is
+cached for the life of the JVM. Changes to Java code still need a rebuild.
+
+Because Jenkins now runs in its own JVM, starting Maven under a debugger no longer attaches to
+Jenkins. Pass `-Dmaven.hpi.debug=true` to have the forked JVM suspend and wait for a debugger, or
+give the option a JVM debug string of your own; see
+[maven-hpi-plugin#862](https://github.com/jenkinsci/maven-hpi-plugin/pull/862).
### Running the Yarn frontend build
@@ -85,10 +97,10 @@ yarn
To work on the `war` module frontend assets, two processes are needed at the same time:
-On one terminal, start a development server that will not process frontend assets:
+On one terminal, start a development server:
```sh
-MAVEN_OPTS='--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.io=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED' mvn -pl war jetty:run -Dskip.frontend
+mvn -pl war hpi:run
```
Open another terminal and start a [webpack](https://webpack.js.org/) dev server, after [optionally adding Node and Yarn to your path](#running-the-yarn-frontend-build):
@@ -97,6 +109,9 @@ Open another terminal and start a [webpack](https://webpack.js.org/) dev server,
yarn start
```
+`yarn start` writes to `war/src/main/webapp/jsbundles`, which the development server serves
+directly, so a browser reload is enough to pick up the rebuilt bundles.
+
### Linting
For linting we use a number of tools:
diff --git a/pom.xml b/pom.xml
index 62509cd890..d0425f13e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -28,7 +28,7 @@ THE SOFTWARE.
org.jenkins-ci
jenkins
- 2.1391.vb_93f2a_83691d
+ 2.1395.v8534005506ec
@@ -103,7 +103,6 @@ THE SOFTWARE.
false
-
8.1047.v331d3186f463
24.19.0
diff --git a/war/nbactions.xml b/war/nbactions.xml
index 04a8d17042..1867202abb 100644
--- a/war/nbactions.xml
+++ b/war/nbactions.xml
@@ -30,16 +30,17 @@ Ensures that NetBeans knows which goals to run when Run Project is selected.
run
- jetty:run
+ hpi:run
debug
- jetty:run
+ hpi:run
- maven
+
+ true
diff --git a/war/pom.xml b/war/pom.xml
index c4c6e505b3..b361e9c66a 100644
--- a/war/pom.xml
+++ b/war/pom.xml
@@ -262,28 +262,6 @@ THE SOFTWARE.
true
-
-
- support-log-formatter
-
- copy
-
- generate-resources
-
-
-
- io.jenkins.lib
- support-log-formatter
- 123.v72513b_f61500
-
-
- ${project.build.directory}
- true
- true
- false
- true
-
-
@@ -329,35 +307,75 @@ THE SOFTWARE.
- org.eclipse.jetty.ee9
- jetty-ee9-maven-plugin
- 12.1.12
+ org.jenkins-ci.tools
+ maven-hpi-plugin
+
+ ${project.build.directory}/${project.build.finalName}.war
+ ${project.build.directory}/${project.build.finalName}
+
+ ${contextPath}
+
- 0
-
- ${host}
- ${port}
-
-
-
- default
-
- ${basedir}/src/realm.properties
-
-
-
+
+
+ jsbundles
+ ${project.basedir}/src/main/webapp/jsbundles
+
+
+ images
+ ${project.basedir}/src/main/webapp/images
+
+
+ scripts
+ ${project.basedir}/src/main/webapp/scripts
+
+
+ css
+ ${project.basedir}/src/main/webapp/css
+
+
+ help
+ ${project.basedir}/src/main/webapp/help
+
+
+ WEB-INF/classes/images
+ ${project.basedir}/src/main/resources/images
+
+
+ WEB-INF/classes/hudson
+ ${project.basedir}/../core/src/main/resources/hudson
+
+
+ WEB-INF/classes/jenkins
+ ${project.basedir}/../core/src/main/resources/jenkins
+
+
+ WEB-INF/classes/lib
+ ${project.basedir}/../core/src/main/resources/lib
+
+
- ${JENKINS_HOME}
-
- true
+ true
true
@@ -365,26 +383,10 @@ THE SOFTWARE.
true
- ../core/src/main/resources;
-
-
- ${project.build.directory}/${project.build.finalName}/WEB-INF/plugins/*.hpi
+ ${project.basedir}/../core/src/main/resources
true
- true
-
- com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
-
-
- ${project.basedir}/../core/src/main/resources,${project.build.directory}/support-log-formatter.jar
- ${contextPath}
- .*(jenkins-core|target/classes).*
-
diff --git a/war/src/main/java/executable/Main.java b/war/src/main/java/executable/Main.java
index 61ae444930..ca982a458d 100644
--- a/war/src/main/java/executable/Main.java
+++ b/war/src/main/java/executable/Main.java
@@ -213,6 +213,19 @@ public class Main {
}
}
+ File explodedWar = null;
+ for (String arg : args) {
+ if (arg.startsWith("--explodedWar=")) {
+ explodedWar = new File(arg.substring("--explodedWar=".length()));
+ if (!new File(explodedWar, "WEB-INF/web.xml").isFile()) {
+ System.err.println("The explodedWar value is not an expanded WAR file: no WEB-INF/web.xml in " + explodedWar);
+ System.exit(1);
+ }
+ // if specified multiple times, the first one wins
+ break;
+ }
+ }
+
for (String arg : args) {
if (arg.startsWith("--pluginroot=")) {
System.setProperty("hudson.PluginManager.workDir",
@@ -231,14 +244,22 @@ public class Main {
// figure out the arguments
trimOffOurOptions(arguments);
- arguments.add(0, "--warfile=" + me.getAbsolutePath());
- if (!hasOption(arguments, "--webroot=")) {
- // defaults to ~/.jenkins/war since many users reported that cron job attempts to clean up
- // the contents in the temporary directory.
- final File jenkinsHome = getJenkinsHome();
- final File webRoot = new File(jenkinsHome, "war");
- System.out.println("webroot: " + webRoot);
- arguments.add("--webroot=" + webRoot);
+ if (explodedWar != null) {
+ // Serve the directory as it is. Passing --warfile as well would make Winstone extract the
+ // WAR over the top of it, wiping it in the process.
+ arguments.removeIf(arg -> arg.startsWith("--webroot="));
+ System.out.println("webroot: " + explodedWar + " (already expanded)");
+ arguments.add("--webroot=" + explodedWar);
+ } else {
+ arguments.add(0, "--warfile=" + me.getAbsolutePath());
+ if (!hasOption(arguments, "--webroot=")) {
+ // defaults to ~/.jenkins/war since many users reported that cron job attempts to clean up
+ // the contents in the temporary directory.
+ final File jenkinsHome = getJenkinsHome();
+ final File webRoot = new File(jenkinsHome, "war");
+ System.out.println("webroot: " + webRoot);
+ arguments.add("--webroot=" + webRoot);
+ }
}
// only do a cleanup if you set the extractedFilesFolder property.
@@ -292,6 +313,8 @@ public class Main {
"\n" +
"Options:\n" +
" --webroot = folder where the WAR file is expanded into. Default is ${JENKINS_HOME}/war\n" +
+ " --explodedWar = folder holding an already expanded WAR file, served as is. No extraction\n" +
+ " is performed and --webroot is ignored\n" +
" --pluginroot = folder where the plugin archives are expanded into. Default is ${JENKINS_HOME}/plugins\n" +
" (NOTE: this option does not change the directory where the plugin archives are stored)\n" +
" --extractedFilesFolder = folder where extracted files are to be located. Default is the temp folder\n" +
@@ -348,7 +371,8 @@ public class Main {
private static void trimOffOurOptions(List arguments) {
arguments.removeIf(arg -> arg.startsWith("--extractedFilesFolder")
- || arg.startsWith("--pluginroot") || arg.startsWith(ENABLE_FUTURE_JAVA_CLI_SWITCH));
+ || arg.startsWith("--pluginroot") || arg.startsWith("--explodedWar")
+ || arg.startsWith(ENABLE_FUTURE_JAVA_CLI_SWITCH));
}
/**
diff --git a/war/src/realm.properties b/war/src/realm.properties
deleted file mode 100644
index 5211348e39..0000000000
--- a/war/src/realm.properties
+++ /dev/null
@@ -1,29 +0,0 @@
-# The MIT License
-#
-# Copyright (c) 2004-2009, Sun Microsystems, Inc., Kohsuke Kawaguchi
-#
-# Permission is hereby granted, free of charge, to any person obtaining a copy
-# of this software and associated documentation files (the "Software"), to deal
-# in the Software without restriction, including without limitation the rights
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-# copies of the Software, and to permit persons to whom the Software is
-# furnished to do so, subject to the following conditions:
-#
-# The above copyright notice and this permission notice shall be included in
-# all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-# THE SOFTWARE.
-
-# For the syntax of this file see:
-# https://wiki.eclipse.org/Jetty/Tutorial/Realms#HashLoginService
-
-guest: guest
-admin: admin,admin
-alice: alice,admin
-david: david,developer