Switch to forking jvm for development mode with hpi:run (#27239)

This commit is contained in:
Tim Jacomb authored and GitHub committed 2026-08-15 21:55:31 +01:00
1 parent 2e228ff40b
commit 62d8d525c7
7 files changed
+121 -118

No files matched your search

-9
View File
@@ -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"],
+19 -4
View File
@@ -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 <http://localhost:8080/jenkins/> 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:
+1 -2
View File
@@ -28,7 +28,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci</groupId>
<artifactId>jenkins</artifactId>
<version>2.1391.vb_93f2a_83691d</version>
<version>2.1395.v8534005506ec</version>
<relativePath />
</parent>
@@ -103,7 +103,6 @@ THE SOFTWARE.
<!-- Skips node installation, "yarn install" and "yarn build". Only safe when
war/src/main/webapp/jsbundles is already populated from an earlier build. -->
<skip.frontend>false</skip.frontend>
<!-- Make sure to keep the jetty-ee9-maven-plugin version in war/pom.xml in sync with the Jetty release in Winstone: -->
<winstone.version>8.1047.v331d3186f463</winstone.version>
<node.version>24.19.0</node.version>
</properties>
+4 -3
View File
@@ -30,16 +30,17 @@ Ensures that NetBeans knows which goals to run when Run Project is selected.
<action>
<actionName>run</actionName>
<goals>
<goal>jetty:run</goal>
<goal>hpi:run</goal>
</goals>
</action>
<action>
<actionName>debug</actionName>
<goals>
<goal>jetty:run</goal>
<goal>hpi:run</goal>
</goals>
<properties>
<jpda.listen>maven</jpda.listen>
<!-- Jenkins runs in a forked JVM, so suspend it and attach to the port it prints. -->
<maven.hpi.debug>true</maven.hpi.debug>
</properties>
</action>
</actions>
+64 -62
View File
@@ -262,28 +262,6 @@ THE SOFTWARE.
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
<execution>
<!-- see jetty-ee9-maven-plugin config and WebAppMain -->
<id>support-log-formatter</id>
<goals>
<goal>copy</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<artifactItems>
<artifactItem>
<groupId>io.jenkins.lib</groupId>
<artifactId>support-log-formatter</artifactId>
<version>123.v72513b_f61500</version>
</artifactItem>
</artifactItems>
<outputDirectory>${project.build.directory}</outputDirectory>
<stripVersion>true</stripVersion>
<overWriteIfNewer>true</overWriteIfNewer>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>true</overWriteSnapshots>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
@@ -329,35 +307,75 @@ THE SOFTWARE.
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty.ee9</groupId>
<artifactId>jetty-ee9-maven-plugin</artifactId>
<!--
Make sure to keep the Winstone version in pom.xml in sync with this version. If the current Winstone release
contains a version of Jetty that is older than this, trigger Dependabot in jenkinsci/winstone and release the
result before proceeding with the update here.
Development instance: mvn -pl war hpi:run. Serves the exploded WAR under Winstone, the same way
jenkins.war is served in production, so build the WAR first.
-->
<version>12.1.12</version>
<groupId>org.jenkins-ci.tools</groupId>
<artifactId>maven-hpi-plugin</artifactId>
<!-- version specified in grandparent pom -->
<configuration>
<webAppFile>${project.build.directory}/${project.build.finalName}.war</webAppFile>
<explodedWar>${project.build.directory}/${project.build.finalName}</explodedWar>
<webApp>
<contextPath>${contextPath}</contextPath>
</webApp>
<!--
Reload webapp when you hit ENTER. (See JETTY-282 for more)
Serve these from the source tree rather than from the copies in the exploded WAR, so that
editing them (or running "yarn start", which writes to src/main/webapp/jsbundles) takes
effect without repackaging.
Never link WEB-INF/classes as a whole: it is real build output holding the symbol SVGs that
Symbol.loadSymbol reads off the class path as images/symbols/*.svg, and replacing it makes
every icon render as the placeholder cross. Link the individual roots instead. A miss in
WEB-INF/classes falls through to jenkins-core.jar, so the generated .stapler and .javadoc
resources under these packages still resolve.
Putting the core roots in front of jenkins-core.jar is what lets an edited Messages.properties
be picked up on the next restart rather than needing "mvn -pl core install" and a repackage.
Only on restart: ResourceBundleHolder caches each bundle for the life of the JVM. Views and
their sibling .properties are live, via stapler.resourcePath below.
-->
<scan>0</scan>
<httpConnector>
<host>${host}</host>
<port>${port}</port>
</httpConnector>
<loginServices>
<loginService implementation="org.eclipse.jetty.security.HashLoginService">
<name>default</name>
<config implementation="org.eclipse.jetty.maven.MavenResource">
<resourceAsString>${basedir}/src/realm.properties</resourceAsString>
</config>
</loginService>
</loginServices>
<webrootLinks>
<webrootLink>
<path>jsbundles</path>
<target>${project.basedir}/src/main/webapp/jsbundles</target>
</webrootLink>
<webrootLink>
<path>images</path>
<target>${project.basedir}/src/main/webapp/images</target>
</webrootLink>
<webrootLink>
<path>scripts</path>
<target>${project.basedir}/src/main/webapp/scripts</target>
</webrootLink>
<webrootLink>
<path>css</path>
<target>${project.basedir}/src/main/webapp/css</target>
</webrootLink>
<webrootLink>
<path>help</path>
<target>${project.basedir}/src/main/webapp/help</target>
</webrootLink>
<webrootLink>
<path>WEB-INF/classes/images</path>
<target>${project.basedir}/src/main/resources/images</target>
</webrootLink>
<webrootLink>
<path>WEB-INF/classes/hudson</path>
<target>${project.basedir}/../core/src/main/resources/hudson</target>
</webrootLink>
<webrootLink>
<path>WEB-INF/classes/jenkins</path>
<target>${project.basedir}/../core/src/main/resources/jenkins</target>
</webrootLink>
<webrootLink>
<path>WEB-INF/classes/lib</path>
<target>${project.basedir}/../core/src/main/resources/lib</target>
</webrootLink>
</webrootLinks>
<systemProperties>
<JENKINS_HOME>${JENKINS_HOME}</JENKINS_HOME>
<!-- always reload views during debugging -->
<stapler.jelly.noCache>true</stapler.jelly.noCache>
<hudson.Main.development>true</hudson.Main.development>
<!-- show the stapler evaluation during execution -->
<stapler.trace>true</stapler.trace>
<!-- show the full stack trace on errors (the oops page) -->
@@ -365,26 +383,10 @@ THE SOFTWARE.
<!-- always reload scripts during debugging -->
<hudson.script.noCache>true</hudson.script.noCache>
<!-- load view resources from the source directly, again for real time change -->
<stapler.resourcePath>../core/src/main/resources;</stapler.resourcePath>
<!-- enable the plugins in main by default -->
<!-- run "mvn install" once will generate the.hpl -->
<hudson.bundled.plugins>${project.build.directory}/${project.build.finalName}/WEB-INF/plugins/*.hpi</hudson.bundled.plugins>
<stapler.resourcePath>${project.basedir}/../core/src/main/resources</stapler.resourcePath>
<!-- stat collection pointless -->
<hudson.model.UsageStatistics.disabled>true</hudson.model.UsageStatistics.disabled>
<hudson.Main.development>true</hudson.Main.development>
<!--
Prevent org.apache.xalan.processor.TransformerFactoryImpl from being loaded from the Maven classpath,
instead preferring com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl as is used in
production contexts.
-->
<javax.xml.transform.TransformerFactory>com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl</javax.xml.transform.TransformerFactory>
</systemProperties>
<webApp>
<!-- Allow resources to be reloaded and enable nicer console logging. -->
<extraClasspath>${project.basedir}/../core/src/main/resources,${project.build.directory}/support-log-formatter.jar</extraClasspath>
<contextPath>${contextPath}</contextPath>
<webInfIncludeJarPattern>.*(jenkins-core|target/classes).*</webInfIncludeJarPattern>
</webApp>
</configuration>
</plugin>
</plugins>
+33 -9
View File
@@ -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<String> 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));
}
/**
-29
View File
@@ -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