Skip to content

Add way to bundle libraries without exposing them to plugins#26600

Open
daniel-beck wants to merge 11 commits into
jenkinsci:masterfrom
daniel-beck:core-library
Open

Add way to bundle libraries without exposing them to plugins#26600
daniel-beck wants to merge 11 commits into
jenkinsci:masterfrom
daniel-beck:core-library

Conversation

@daniel-beck
Copy link
Copy Markdown
Member

@daniel-beck daniel-beck commented Apr 8, 2026

Fixes #18156

This adds a way to bundle libraries with Jenkins that does not make them available, even shaded, on plugin classpaths. This allows us to add them, replace them, or remove them at will.

At a high level, this PR includes the following components:

  • A relatively small code change in core that adds support for loading "core libraries" (Jenkins and CoreLibClassLoader)
  • Changes to various pom.xml, including the addition of some Groovy scripts, that ensure we're bundling the libraries in the correct location, outside the usual directory. This is the most awkward part of the PR, but I haven't found a way to make this nicer while keeping the functionality as intended (works with java -jar jenkins.war, mvn -pl war jetty:run, mvn hpi:run, and core + plugin tests).
  • A new Maven module jenkins-core-libs that holds implementations of dedicated META-INF services.
  • A service definition and a caller (in core/) and its implementation (in core-libs/) using owasp-java-html-sanitizer, to sanitize the plugin excerpt provided by update sites.

The HTML sanitizer use case nicely demonstrates the usefulness of this system: We want to be able to sanitize HTML, but not with the configured "markup formatter" (different use case), don't want to implement this ourselves, and do not want to make the library used available to plugins.

(And, just in case: This does not fix a vulnerability. You need to trust the update sites you're using anyway. This is an improvement to protect from improper sanitization in the update site generator providing those JSON files. The rules we're applying here are identical to those in update-center2, so makes no difference to the vast majority of users.)

Testing done

Autotests and interactive.
Tested with antisamy-markup-formatter to see whether it sees classes new in the newer OWASP Sanitizer library dependency, it doesn't, as expected: jenkinsci/antisamy-markup-formatter-plugin#177

About Jenkins correctly lists the libraries. image

Screenshots (UI changes only)

n/a

Proposed changelog entries

  • Major RFE: Add a way to bundle libraries in Jenkins without making them available to plugins
  • RFE: Apply HTML sanitization to plugin descriptions provided by update sites.

Proposed changelog category

/label major-rfe

Proposed upgrade guidelines

N/A

Submitter checklist

  • The issue, if it exists, is well-described.
  • The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood (see examples). Fill in the Proposed upgrade guidelines section only if there are breaking changes or changes that may require extra steps from users during upgrade.
  • There is automated testing or an explanation as to why this change has no tests.
  • New public classes, fields, and methods are annotated with @Restricted or have @since TODO Javadocs, as appropriate.
  • New deprecations are annotated with @Deprecated(since = "TODO") or @Deprecated(forRemoval = true, since = "TODO"), if applicable.
  • UI changes do not introduce regressions when enforcing the current default rules of Content Security Policy Plugin. In particular, new or substantially changed JavaScript is not defined inline and does not call eval to ease future introduction of Content Security Policy (CSP) directives (see documentation).
  • For dependency updates, there are links to external changelogs and, if possible, full differentials.
  • For new APIs and extension points, there is a link to at least one consumer.

Desired reviewers

@mention

Before the changes are marked as ready-for-merge:

Maintainer checklist

  • There are at least two (2) approvals for the pull request and no outstanding requests for change.
  • Conversations in the pull request are over, or it is explicit that a reviewer is not blocking the change.
  • Changelog entries in the pull request title and/or Proposed changelog entries are accurate, human-readable, and in the imperative mood.
  • Proper changelog labels are set so that the changelog can be generated automatically.
  • If the change needs additional upgrade steps from users, the upgrade-guide-needed label is set and there is a Proposed upgrade guidelines section in the pull request title (see example).
  • If it would make sense to backport the change to LTS, be a Bug or Improvement, and either the issue or pull request must be labeled as lts-candidate to be considered.

Copilot AI review requested due to automatic review settings April 8, 2026 16:18
@comment-ops-bot comment-ops-bot Bot added the major-rfe For changelog: Major enhancement. Will be highlighted on the top label Apr 8, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new mechanism for bundling “core-only” libraries inside Jenkins so core can use third-party dependencies without exposing them on plugin classpaths, and demonstrates the mechanism by sanitizing update-site plugin excerpts using OWASP Java HTML Sanitizer.

Changes:

  • Introduce CoreLibClassLoader + Jenkins#getCoreLibrary(...) to load internal services from WEB-INF/core-lib/ via ServiceLoader.
  • Add a new core-libs Maven module (jenkins-core-libs) providing internal service implementations (initially PluginExcerptSanitizer).
  • Update WAR packaging to copy core-libs artifacts into WEB-INF/core-lib/ and prevent duplication into WEB-INF/lib, plus add tests validating classloader isolation and sanitization behavior.

Reviewed changes

Copilot reviewed 16 out of 18 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
core/src/main/java/jenkins/model/Jenkins.java Adds core-libs classloader initialization, service lookup API, and shutdown cleanup.
core/src/main/java/jenkins/core/CoreLibClassLoader.java Implements isolated classloader loading jars from WEB-INF/core-lib/.
core/src/main/java/jenkins/core/PluginExcerptSanitizer.java Defines internal service interface for excerpt sanitization.
core/src/main/java/hudson/model/UpdateSite.java Sanitizes plugin excerpt field via core-libs service (fallback to escaping).
core-libs/pom.xml New module that bundles OWASP sanitizer and hosts service implementations.
core-libs/src/main/java/jenkins/security/OwaspPluginExcerptSanitizer.java OWASP-based sanitizer implementation exposed via META-INF services.
core-libs/src/main/resources/META-INF/bundled-libraries.properties Declares artifactIds expected to be copied into WEB-INF/core-lib/.
core-libs/validate-dependencies.groovy Build-time validation that declared bundled libs match actual dependencies.
war/pom.xml Packages core-libs + transitives into WEB-INF/core-lib/, excludes from WEB-INF/lib, adds Groovy helper + dev copy-to-src.
war/extract-core-lib-artifact-ids.groovy Extracts bundled library artifactIds from the core-libs JAR to drive WAR packaging config.
pom.xml Adds core-libs module + cleans generated war/.../WEB-INF/core-lib artifacts.
test/pom.xml Excludes jenkins-core-libs from test classpath to validate isolation.
test/src/test/java/jenkins/core/CoreLibClassLoaderTest.java Verifies sanitizer availability + excerpt sanitization behavior.
test/src/test/java/jenkins/core/CoreLibIsolationTest.java Tests core access vs plugin isolation for core-libs.
test/src/test/java/jenkins/core/CoreLibIsolationRealTest.java RealJenkinsExtension test ensuring synthetic plugin cannot load OWASP classes.
test/src/test/java/jenkins/core/corelib_test_plugin/ClassLoaderProbe.java Helper class for the synthetic plugin isolation test.
.gitignore Ignores generated war/src/main/webapp/WEB-INF/core-lib.
.idea/encodings.xml Adds IntelliJ encoding entries for the new core-libs module.
Files not reviewed (1)
  • .idea/encodings.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/src/main/java/jenkins/core/CoreLibClassLoader.java Outdated
project.properties['core-lib-artifact-ids'] = artifactIds

// Generate regex pattern for packaging excludes (include jenkins-core-libs itself + all artifact IDs)
def allIds = (['jenkins-core-libs'] + props.keySet()).join('|')
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

core-lib-packaging-excludes is built by concatenating artifactIds into a regex alternation, but the artifactIds are not regex-escaped. If a future bundled artifactId contains regex metacharacters (e.g., ., +), the pattern could misbehave and accidentally include/exclude the wrong JARs. Escape each artifactId (or generate non-regex excludes) before building the pattern.

Suggested change
def allIds = (['jenkins-core-libs'] + props.keySet()).join('|')
def allIds = (['jenkins-core-libs'] + props.keySet())
.collect { java.util.regex.Pattern.quote(it.toString()) }
.join('|')

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best do that ^^^

Comment thread test/src/test/java/jenkins/core/CoreLibIsolationTest.java Outdated
Comment thread core/src/main/java/jenkins/model/Jenkins.java
Comment thread core/src/main/java/jenkins/model/Jenkins.java Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 18 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • .idea/encodings.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core-libs/pom.xml
Comment on lines +38 to +58
<dependencies>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20260313.1</version>
</dependency>

<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.11</version>
<scope>provided</scope>
</dependency>
</dependencies>

Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike other Maven modules in this repo (e.g., core/pom.xml, cli/pom.xml), this new module does not import the jenkins-bom in <dependencyManagement>. That can lead to inconsistent/transitively-selected dependency versions versus the rest of the reactor. Consider adding a dependencyManagement section importing org.jenkins-ci.main:jenkins-bom:${project.version} for consistency with the established module pattern.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure about this one. Seems like something that maven-enforcer-plugin would identify.
A problem for when we get there?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using jenkins-bom as above would make sense to me.

Comment thread core/src/main/java/jenkins/core/CoreLibClassLoader.java Outdated
Comment thread core/src/main/java/jenkins/core/CoreLibClassLoader.java Outdated
Comment thread war/pom.xml
Copilot AI review requested due to automatic review settings April 14, 2026 20:15
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 18 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • .idea/encodings.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread core/src/main/java/jenkins/core/CoreLibClassLoader.java Outdated
@daniel-beck daniel-beck marked this pull request as ready for review April 16, 2026 08:53
Copilot AI review requested due to automatic review settings April 16, 2026 08:53
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 4 comments.

Files not reviewed (1)
  • .idea/encodings.xml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

for (String resourcePath : resourcePaths) {
if (resourcePath.endsWith(".jar")) {
try {
urls.add(context.getResource(resourcePath));
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ServletContext#getResource(String) can return null when the resource cannot be resolved. Right now a null would be added to urls and later cause an NPE inside URLClassLoader. Consider checking for a null URL and throwing an IllegalStateException that includes the resourcePath to make startup failures diagnosable.

Suggested change
urls.add(context.getResource(resourcePath));
URL url = context.getResource(resourcePath);
if (url == null) {
throw new IllegalStateException("Failed to resolve resource: " + resourcePath);
}
urls.add(url);

Copilot uses AI. Check for mistakes.
Comment thread core-libs/pom.xml
Comment on lines +38 to +54
<dependencies>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20260313.1</version>
</dependency>

<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.11</version>
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This module goes straight from the <parent> block to hardcoded dependency versions without importing org.jenkins-ci.main:jenkins-bom in <dependencyManagement>, unlike other reactor modules (e.g., core/pom.xml:48-65, war/pom.xml:55-65, cli/pom.xml:23-33). Consider adding the BOM import here and dropping explicit <version> tags that are managed there to keep versions aligned across the reactor.

Suggested change
<dependencies>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
<version>20260313.1</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>
<version>1.11</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-bom</artifactId>
<version>${project.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
<artifactId>owasp-java-html-sanitizer</artifactId>
</dependency>
<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.kohsuke.metainf-services</groupId>
<artifactId>metainf-services</artifactId>

Copilot uses AI. Check for mistakes.
ServiceLoader<T> loader = ServiceLoader.load(clazz, coreLibClassLoader);
result = loader.findFirst().or(Optional::empty);
if (result.isEmpty()) {
LOGGER.log(Level.WARNING, "No service implementation found for: {0}", clazz.getName());
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getCoreLibrary logs WARNING when no implementation is found. Since this API returns null by design (and callers like UpdateSite.Plugin already handle null), missing services may be an expected condition and the warning can become noisy in normal operation. Consider logging at FINE/CONFIG for the empty-result case and reserving WARNING for ServiceConfigurationError (similar to jenkins/websocket/WebSockets.java:55-63, which logs service misconfiguration at FINE).

Suggested change
LOGGER.log(Level.WARNING, "No service implementation found for: {0}", clazz.getName());
LOGGER.log(FINE, "No service implementation found for: {0}", clazz.getName());

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I think. IIUC this would only happen in a unit test?

Comment on lines +75 to +79
final Set<String> resourcePaths = context.getResourcePaths("/WEB-INF/core-lib/");

if (resourcePaths == null || resourcePaths.isEmpty()) {
throw new IllegalStateException("No WEB-INF/core-lib/ resources found");
}
Copy link

Copilot AI Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resourcePaths being non-empty doesn’t guarantee there are any JARs to load (e.g., only subdirectories/markers). After filtering to *.jar, urls can still be empty and the classloader will initialize without core-lib content. Consider validating that at least one JAR URL was found (and failing fast with a clear message) before returning the classloader.

Copilot uses AI. Check for mistakes.
@daniel-beck daniel-beck requested a review from Kevin-CB April 22, 2026 13:55
@daniel-beck daniel-beck requested a review from a team April 28, 2026 15:54
Copy link
Copy Markdown
Contributor

@Kevin-CB Kevin-CB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Changes to various pom.xml, including the addition of some Groovy scripts, that ensure we're bundling the libraries in the correct location, outside the usual directory. This is the most awkward part of the PR, but I haven't found a way to make this nicer while keeping the functionality as intended (works with java -jar jenkins.war, mvn -pl war jetty:run, mvn hpi:run, and core + plugin tests).

I tried to think about a simpler approach. And didn't mange to have a real simplification. However, we could maybe try to have core-libs to create a ZIP file with the validated core-lib JARs (maven-assembly?), after checking bundled-libraries.properties against the resolved dependencies. Then the war could just unpack this ZIP into WEB-INF/core-lib with a single dependency:unpack.

This would make things clearer between the modules:

  • core-libs manages and validates the list of bundled libraries
  • war just uses the ZIP and doesn’t need to figure out artifact IDs from jenkins-core-libs.jar

That said, I’m not sure this would work in all cases (java -jar jenkins.war, mvn -pl war jetty:run, etc.).

Comment thread core/src/main/java/jenkins/core/CoreLibClassLoader.java Outdated
Comment thread core/src/main/java/jenkins/model/Jenkins.java Outdated
@daniel-beck daniel-beck requested a review from Kevin-CB May 4, 2026 06:35
@daniel-beck
Copy link
Copy Markdown
Member Author

we could maybe try

Feel free to do that and report back how well that works.

This would make things clearer between the modules:

* `core-libs` manages and validates the list of bundled libraries

* `war` just uses the ZIP and doesn’t need to figure out artifact IDs from `jenkins-core-libs.jar`

To clarify, you'd remove the jenkins-core-libs dependency from the war/pom.xml? Or how would that work?

Copy link
Copy Markdown
Member

@timja timja left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


/label ready-for-merge


This PR is now ready for merge, after ~24 hours, we will merge it if there's no negative feedback.

Thanks!

@comment-ops-bot comment-ops-bot Bot added the ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback label May 4, 2026
Comment thread .gitignore
war/src/main/webapp/jsbundles/

# Generated core-libs to ensure correct classpath in jetty:run, see `copy-core-lib-to-src` in `war/pom.xml`.
war/src/main/webapp/WEB-INF/core-lib
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels really wrong. A Maven build should never be creating files under src/. Did you mean to edit

jenkins/war/pom.xml

Lines 805 to 810 in 8a2cb98

<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>
instead?

Comment thread war/pom.xml
</goals>
<phase>generate-resources</phase>
<configuration>
<outputDirectory>${project.basedir}/src/main/webapp/WEB-INF/core-lib</outputDirectory>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As above: please avoid doing this if at all possible.

* THE SOFTWARE.
*/

package jenkins.security;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Different JAR so you should use a distinct package name to avoid IllegalAccessErrors.

Comment thread core-libs/src/main/resources/META-INF/bundled-libraries.properties
if (excerpt == null) {
return null;
}
final Jenkins jenkins = Jenkins.getInstanceOrNull();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not get()? This should not be called when Jenkins.instance == null.

@timja timja removed the ready-for-merge The PR is ready to go, and it will be merged soon if there is no negative feedback label May 4, 2026
}
final Jenkins jenkins = Jenkins.getInstanceOrNull();
final PluginExcerptSanitizer sanitizer = jenkins != null ? jenkins.getCoreLibrary(PluginExcerptSanitizer.class) : null;
if (sanitizer == null) {
Copy link
Copy Markdown
Member

@jglick jglick May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would this be null? Unit tests?

}
}
}
return new CoreLibClassLoader(urls.toArray(new URL[0]), parent);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or stylistically

Suggested change
return new CoreLibClassLoader(urls.toArray(new URL[0]), parent);
return new CoreLibClassLoader(urls.toArray(URL[]::new), parent);

Comment on lines +37 to +38
@CheckForNull
String sanitize(@CheckForNull String html);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both should be non-null I think; caller should do any required null check.

*/
private final transient CoreLibClassLoader coreLibClassLoader;

private final transient HashMap<Class<?>, Optional<?>> coreLibs = new HashMap<>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Document the key/value types, which are not apparent from reading.

@Restricted(NoExternalUse.class)
public <T> T getCoreLibrary(Class<T> clazz) {
synchronized (coreLibs) {
if (!coreLibs.containsKey(clazz)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use compute. (And then you can use a ConcurrentHashMap if you like.)

Comment thread pom.xml
<followSymlinks>false</followSymlinks>
</fileset>
<fileset>
<directory>war/src/main/webapp/WEB-INF/core-lib</directory>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as before

Comment thread test/pom.xml
<type>executable-war</type>
<scope>test</scope>
<exclusions>
<!-- Remove from test classpath -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +30 to +32
private static class VerifyIsolationStep implements RealJenkinsExtension.Step {
@Override
public void run(JenkinsRule r) throws Exception {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More idiomatic to use a static method handle.

ClassLoader pluginClassLoader = plugin.classLoader;

// The following assertion is fairly brittle, as plugin classloaders have access to dependencies in the local Maven repo that don't match actual behavior during Jenkins runtime.
// Once https://github.com/jenkinsci/antisamy-markup-formatter-plugin/pull/174, this assertion will probably fail and should be removed.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not clear from this comment which assertion would fail, how, or what we should be asserting instead.

PluginWrapper plugin = j.jenkins.getPluginManager().getPlugins().stream().filter(p -> !"antisamy-markup-formatter".equals(p.getShortName())).findFirst().orElseThrow();
ClassLoader pluginClassLoader = plugin.classLoader;

// The following assertion is fairly brittle, as plugin classloaders have access to dependencies in the local Maven repo that don't match actual behavior during Jenkins runtime.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So what is the purpose of this test, if the RealJenkinsRule version covers what we care about?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most tests are written as JenkinsRule, and this test asserts reasonable behavior for those. I guess we could use another synthetic plugin here to eliminate the risk of ending up with a (transitive) dependency that causes the test to fall over.

Comment thread war/extract-core-lib-artifact-ids.groovy

// Generate regex pattern for packaging excludes (include jenkins-core-libs itself + all artifact IDs)
def allIds = (['jenkins-core-libs'] + props.keySet()).join('|')
project.properties['core-lib-packaging-excludes'] = "%regex[WEB-INF/lib/(" + allIds + ")-.*\\.jar]"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
project.properties['core-lib-packaging-excludes'] = "%regex[WEB-INF/lib/(" + allIds + ")-.*\\.jar]"
project.properties['core-lib-packaging-excludes'] = "%regex[WEB-INF/lib/(" + allIds + ")-.*[.]jar]"

arguably more legible

Comment thread war/pom.xml
Comment on lines +215 to +216
<!-- Exclude everything already in WEB-INF/core-lib from being duplicated in WEB-INF/lib -->
<packagingExcludes>${core-lib-packaging-excludes}</packagingExcludes>
Copy link
Copy Markdown
Member

@jglick jglick May 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems messy. Maybe rather than a dependency on jenkins-core-libs you wanted to copy jenkins-core-libs.jar itself to WEB-INF/core-lib/? Which IIUC would avoid the need for extract-core-lib-artifact-ids.groovy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually you are already doing that copy? So why is it a dep also? I am not following.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure what you're asking for here. I need a way to get the jars into the war's core-lib/ directory. Kevin suggested bundling them up as a zip and then extracting them here, which to me means having a dependency on a packaging/classifier artifact. Is that where you're going, or a different approach?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I was unclear.

AFAICT you could delete

jenkins/war/pom.xml

Lines 83 to 87 in 8a2cb98

<dependency>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>jenkins-core-libs</artifactId>
<version>${project.version}</version>
</dependency>
<exclude>org.jenkins-ci.main:jenkins-core-libs</exclude>

jenkins/war/pom.xml

Lines 215 to 216 in 8a2cb98

<!-- Exclude everything already in WEB-INF/core-lib from being duplicated in WEB-INF/lib -->
<packagingExcludes>${core-lib-packaging-excludes}</packagingExcludes>
given that you are doing

jenkins/war/pom.xml

Lines 680 to 694 in 8a2cb98

<execution>
<!-- Copy jenkins-core-libs JAR itself -->
<id>core-lib-module</id>
<goals>
<goal>copy-dependencies</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<includeArtifactIds>jenkins-core-libs</includeArtifactIds>
<excludeTransitive>true</excludeTransitive>
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/core-lib</outputDirectory>
<stripVersion>false</stripVersion>
<overWriteIfNewer>true</overWriteIfNewer>
</configuration>
</execution>
anyway, if you just switched from the copy-dependencies goal to copy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…which would I think also address #26600 (comment) by allowing

jenkins/test/pom.xml

Lines 169 to 175 in 8a2cb98

<exclusions>
<!-- Remove from test classpath -->
<exclusion>
<groupId>${project.groupId}</groupId>
<artifactId>jenkins-core-libs</artifactId>
</exclusion>
</exclusions>
to be deleted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

major-rfe For changelog: Major enhancement. Will be highlighted on the top

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JENKINS-68940] Solution to add core features backed by libraries without exposing them to plugins

5 participants