Skip to content

Commit 77aea3e

Browse files
committed
Migrate LoadedClasses.srcUrl, and switching persisted field to LibraryRecord.directoryName to avoid use of $JENKINS_HOME in program.dat
1 parent 698ae68 commit 77aea3e

File tree

17 files changed

+308
-26
lines changed

17 files changed

+308
-26
lines changed

src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java

Lines changed: 40 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,10 @@
3939
import hudson.model.TaskListener;
4040
import hudson.scm.SCM;
4141
import hudson.security.AccessControlled;
42-
import java.io.File;
4342
import java.io.IOException;
4443
import java.io.Serializable;
4544
import java.lang.reflect.Method;
4645
import java.lang.reflect.Modifier;
47-
import java.net.URI;
4846
import java.net.URL;
4947
import java.util.ArrayList;
5048
import java.util.Collection;
@@ -250,20 +248,40 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
250248
private final @NonNull String prefix;
251249
/** {@link Class#getName} minus package prefix */
252250
private final @CheckForNull String clazz;
253-
/** {@code /…/libs/$hash.jar}, or null if resuming a pre-dir2Jar build */
254-
private final @Nullable String jar;
251+
/** {@code file:/…/libs/NAME/src/} */
252+
@Deprecated
253+
private @NonNull String srcUrl;
254+
/** {@link LibraryRecord#getDirectoryName}, or null if resuming a pre-dir2Jar build */
255+
private final @Nullable String directoryName;
255256

256257
LoadedClasses(String library, String libraryDirectoryName, boolean trusted, Boolean changelog, Run<?,?> run) {
257-
this(library, trusted, changelog, "", null, /* cf. LibraryAdder.retrieve */ new File(run.getRootDir(), "libs/" + libraryDirectoryName + ".jar").getAbsolutePath());
258+
this(library, trusted, changelog, "", null, libraryDirectoryName);
258259
}
259260

260-
LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String jar) {
261+
LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String directoryName) {
261262
this.library = library;
262263
this.trusted = trusted;
263264
this.changelog = changelog;
264265
this.prefix = prefix;
265266
this.clazz = clazz;
266-
this.jar = jar;
267+
this.directoryName = directoryName;
268+
}
269+
270+
private static final Pattern SRC_URL = Pattern.compile("file:/.+/([0-9a-f]{64})/src/");
271+
272+
private Object readResolve() throws IllegalAccessException {
273+
if (srcUrl != null) {
274+
Matcher m = SRC_URL.matcher(srcUrl);
275+
if (!m.matches()) {
276+
// Perhaps predating hash-based naming (ace0de3, Feb 2022):
277+
throw new IllegalAccessException("Unexpected form of library source URL: " + srcUrl);
278+
}
279+
String inferredDirectoryName = m.group(1);
280+
LOGGER.fine(() -> "deserializing to " + inferredDirectoryName);
281+
return new LoadedClasses(library, trusted, changelog, prefix, clazz, inferredDirectoryName);
282+
} else {
283+
return this;
284+
}
267285
}
268286

269287
@Override public Object getProperty(String property) {
@@ -287,12 +305,12 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
287305
String fullClazz = clazz != null ? clazz + '$' + property : property;
288306
loadClass(prefix + fullClazz);
289307
// OK, class really exists, stash it and await methods
290-
return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, jar);
308+
return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, directoryName);
291309
} else if (clazz != null) {
292310
throw new MissingPropertyException(property, loadClass(prefix + clazz));
293311
} else {
294312
// Still selecting package components.
295-
return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, jar);
313+
return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, directoryName);
296314
}
297315
}
298316

@@ -338,7 +356,7 @@ private static boolean isSandboxed() {
338356

339357
// TODO putProperty for static field set
340358

341-
private static final Pattern JAR_URL = Pattern.compile("jar:(file:/.+[.]jar)!/.+");
359+
private static final Pattern JAR_URL = Pattern.compile("jar:file:/.+/([0-9a-f]{64})[.]jar!/.+");
342360

343361
private Class<?> loadClass(String name) {
344362
CpsFlowExecution exec = CpsThread.current().getExecution();
@@ -352,23 +370,19 @@ private Class<?> loadClass(String name) {
352370
if (definingLoader != loader) {
353371
throw new IllegalAccessException("cannot access " + c + " via library handle: " + definingLoader + " is not " + loader);
354372
}
355-
if (jar != null) {
356-
URL res = loader.getResource(name.replaceFirst("[$][^.]+$", "").replace('.', '/') + ".groovy");
357-
if (res == null) {
358-
throw new IllegalAccessException("Unknown where " + name + " (" + c.getProtectionDomain().getCodeSource().getLocation() + ") was loaded from");
359-
}
360-
Matcher m = JAR_URL.matcher(res.toString());
361-
if (!m.matches()) {
362-
throw new IllegalAccessException("Unexpected URL " + res);
363-
}
364-
File actual = new File(URI.create(m.group(1)));
365-
if (!actual.equals(new File(jar))) {
366-
throw new IllegalAccessException(name + " was defined in " + actual + " rather than the expected " + jar);
367-
}
368-
LOGGER.fine(() -> "loaded " + name + " from " + res + " ~ " + actual + " as expected");
369-
} else {
370-
LOGGER.fine(() -> "loaded " + name + " but resuming from an old build which did not properly record JAR location");
373+
URL res = loader.getResource(name.replaceFirst("[$][^.]+$", "").replace('.', '/') + ".groovy");
374+
if (res == null) {
375+
throw new IllegalAccessException("Unknown where " + name + " (" + c.getProtectionDomain().getCodeSource().getLocation() + ") was loaded from");
376+
}
377+
Matcher m = JAR_URL.matcher(res.toString());
378+
if (!m.matches()) {
379+
throw new IllegalAccessException("Unexpected URL " + res);
380+
}
381+
String actual = m.group(1);
382+
if (!actual.equals(directoryName)) {
383+
throw new IllegalAccessException(name + " was defined in " + res + " rather than the expected " + directoryName);
371384
}
385+
LOGGER.fine(() -> "loaded " + name + " from " + res + " ~ " + actual + " as expected");
372386
if (!Modifier.isPublic(c.getModifiers())) { // unlikely since Groovy makes classes implicitly public
373387
throw new IllegalAccessException(c + " is not public");
374388
}

src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
import org.jvnet.hudson.test.Issue;
5959
import org.jvnet.hudson.test.JenkinsRule;
6060
import org.jvnet.hudson.test.LoggerRule;
61+
import org.jvnet.hudson.test.recipes.LocalData;
6162

6263
@Issue("JENKINS-39450")
6364
public class LibraryStepTest {
@@ -345,4 +346,27 @@ public class LibraryStepTest {
345346
r.assertLogContains("/lib/java", b);
346347
r.assertLogContains("/pipeline/java", b);
347348
}
349+
350+
@LocalData
351+
@Test public void classesWhenResumingPreDir2JarBuild() throws Exception {
352+
// LocalData captured as of 1091aea7fa252acae11389588addf603a505e195:
353+
/*
354+
sampleRepo.init();
355+
sampleRepo.write("src/pkg/C.groovy", "package pkg; class C {static void m() {23}}");
356+
sampleRepo.git("add", "src");
357+
sampleRepo.git("commit", "--message=init");
358+
GlobalLibraries.get().setLibraries(Collections.singletonList(new LibraryConfiguration("stuff", new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)))));
359+
WorkflowJob p = r.createProject(WorkflowJob.class, "p");
360+
p.setDefinition(new CpsFlowDefinition("def lib = library('stuff@master'); sleep 180; echo(/got ${lib.pkg.C.m()}/)", true));
361+
WorkflowRun b = p.scheduleBuild2(0).waitForStart();
362+
r.waitForMessage("Sleeping for 3 min", b);
363+
b.save();
364+
Thread.sleep(Long.MAX_VALUE);
365+
*/
366+
WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class);
367+
WorkflowRun b = p.getBuildByNumber(1);
368+
r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
369+
r.assertLogContains("got 23", b);
370+
}
371+
348372
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<flow-build>
3+
<actions>
4+
<org.jenkinsci.plugins.workflow.libs.LibrariesAction>
5+
<libraries>
6+
<org.jenkinsci.plugins.workflow.libs.LibraryRecord>
7+
<name>stuff</name>
8+
<version>master</version>
9+
<variables class="sorted-set"/>
10+
<trusted>true</trusted>
11+
<changelog>true</changelog>
12+
<directoryName>eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b</directoryName>
13+
</org.jenkinsci.plugins.workflow.libs.LibraryRecord>
14+
</libraries>
15+
</org.jenkinsci.plugins.workflow.libs.LibrariesAction>
16+
<hudson.plugins.git.util.BuildData>
17+
<buildsByBranchName>
18+
<entry>
19+
<string>master</string>
20+
<hudson.plugins.git.util.Build>
21+
<marked>
22+
<sha1>98c9e41033d994442c0105ba3011fe39a57a69b1</sha1>
23+
<branches class="singleton-set">
24+
<hudson.plugins.git.Branch>
25+
<sha1 reference="../../../sha1"/>
26+
<name>master</name>
27+
</hudson.plugins.git.Branch>
28+
</branches>
29+
</marked>
30+
<revision reference="../marked"/>
31+
<hudsonBuildNumber>1</hudsonBuildNumber>
32+
</hudson.plugins.git.util.Build>
33+
</entry>
34+
</buildsByBranchName>
35+
<lastBuild reference="../buildsByBranchName/entry/hudson.plugins.git.util.Build"/>
36+
<remoteUrls>
37+
<string>…/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657</string>
38+
</remoteUrls>
39+
</hudson.plugins.git.util.BuildData>
40+
<org.jenkinsci.plugins.workflow.steps.scm.MultiSCMRevisionState>
41+
<revisionStates>
42+
<entry>
43+
<string>git …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657</string>
44+
<hudson.scm.SCMRevisionState_-None/>
45+
</entry>
46+
</revisionStates>
47+
</org.jenkinsci.plugins.workflow.steps.scm.MultiSCMRevisionState>
48+
</actions>
49+
<queueId>1</queueId>
50+
<timestamp>1678379139742</timestamp>
51+
<startTime>1678379139771</startTime>
52+
<duration>0</duration>
53+
<charset>UTF-8</charset>
54+
<keepLog>false</keepLog>
55+
<execution class="org.jenkinsci.plugins.workflow.cps.CpsFlowExecution">
56+
<result>SUCCESS</result>
57+
<script>def lib = library(&apos;stuff@master&apos;); sleep 180; echo(/got ${lib.pkg.C.m()}/)</script>
58+
<loadedScripts class="map"/>
59+
<durabilityHint>MAX_SURVIVABILITY</durabilityHint>
60+
<timings class="concurrent-hash-map">
61+
<entry>
62+
<string>flowNode</string>
63+
<long>82579725</long>
64+
</entry>
65+
<entry>
66+
<string>classLoad</string>
67+
<long>406679430</long>
68+
</entry>
69+
<entry>
70+
<string>run</string>
71+
<long>370461094</long>
72+
</entry>
73+
<entry>
74+
<string>parse</string>
75+
<long>387067399</long>
76+
</entry>
77+
<entry>
78+
<string>saveProgram</string>
79+
<long>121174695</long>
80+
</entry>
81+
</timings>
82+
<sandbox>true</sandbox>
83+
<iota>4</iota>
84+
<head>1:4</head>
85+
<start>2</start>
86+
<done>false</done>
87+
<resumeBlocked>false</resumeBlocked>
88+
</execution>
89+
<completed>false</completed>
90+
<checkouts class="hudson.util.PersistedList">
91+
<org.jenkinsci.plugins.workflow.job.WorkflowRun_-SCMCheckout>
92+
<scm class="hudson.plugins.git.GitSCM">
93+
<configVersion>2</configVersion>
94+
<userRemoteConfigs>
95+
<hudson.plugins.git.UserRemoteConfig>
96+
<name>origin</name>
97+
<refspec>+refs/heads/*:refs/remotes/origin/*</refspec>
98+
<url>…/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657</url>
99+
</hudson.plugins.git.UserRemoteConfig>
100+
</userRemoteConfigs>
101+
<branches class="singleton-list">
102+
<hudson.plugins.git.BranchSpec>
103+
<name>master</name>
104+
</hudson.plugins.git.BranchSpec>
105+
</branches>
106+
<doGenerateSubmoduleConfigurations>false</doGenerateSubmoduleConfigurations>
107+
<submoduleCfg class="empty-list"/>
108+
<extensions>
109+
<hudson.plugins.git.extensions.impl.IgnoreNotifyCommit/>
110+
<jenkins.plugins.git.GitSCMSourceDefaults>
111+
<includeTags>false</includeTags>
112+
</jenkins.plugins.git.GitSCMSourceDefaults>
113+
<hudson.plugins.git.extensions.impl.BuildChooserSetting>
114+
<buildChooser class="jenkins.plugins.git.AbstractGitSCMSource$SpecificRevisionBuildChooser">
115+
<revision reference="../../../../../../../actions/hudson.plugins.git.util.BuildData/buildsByBranchName/entry/hudson.plugins.git.util.Build/marked"/>
116+
</buildChooser>
117+
</hudson.plugins.git.extensions.impl.BuildChooserSetting>
118+
</extensions>
119+
</scm>
120+
<node></node>
121+
<workspace>…/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9</workspace>
122+
<changelogFile>…/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/jobs/p/builds/1/changelog5512666869627859761.xml</changelogFile>
123+
<pollingBaseline class="hudson.scm.SCMRevisionState$None" reference="../../../actions/org.jenkinsci.plugins.workflow.steps.scm.MultiSCMRevisionState/revisionStates/entry/hudson.scm.SCMRevisionState_-None"/>
124+
</org.jenkinsci.plugins.workflow.job.WorkflowRun_-SCMCheckout>
125+
</checkouts>
126+
</flow-build>

src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog5512666869627859761.xml

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stuff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package pkg; class C {static void m() {23}}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
Started
2+
ha:////4ESdDHApaBCDSybNFTtB8QH7JYx/td1ExPJr3dgp1IVpAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[Pipeline] Start of Pipeline
3+
ha:////4GmDEP+pf+jq6je4q08twRF2g64HEz1ZmA5RyCc5wqQeAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbaGmsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gfya1RxvAAAAA==[Pipeline] library
4+
Loading library stuff@master
5+
Attempting to resolve master from remote references...
6+
> git --version # timeout=10
7+
> git --version # 'git version 2.34.1'
8+
> git ls-remote -h -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10
9+
Found match: refs/heads/master revision 98c9e41033d994442c0105ba3011fe39a57a69b1
10+
The recommended git tool is: NONE
11+
No credentials specified
12+
Cloning the remote Git repository
13+
Cloning with configured refspecs honoured and without tags
14+
Cloning repository …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
15+
> git init …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9 # timeout=10
16+
Fetching upstream changes from …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
17+
> git --version # timeout=10
18+
> git --version # 'git version 2.34.1'
19+
> git fetch --no-tags --force --progress -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 +refs/heads/*:refs/remotes/origin/* # timeout=10
20+
> git config remote.origin.url …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10
21+
> git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
22+
Avoid second fetch
23+
Checking out Revision 98c9e41033d994442c0105ba3011fe39a57a69b1 (master)
24+
> git config core.sparsecheckout # timeout=10
25+
> git checkout -f 98c9e41033d994442c0105ba3011fe39a57a69b1 # timeout=10
26+
Commit message: "init"
27+
First time build. Skipping changelog.
28+
ha:////4JPM8UkGCuomMvb51Uo5DyzahUcbxePS+FWXyxySXcOwAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbGDtjZUtoPAGyiLDkfxZYdytP5NW8g8RNrJxkknnTvNcb1jnBiZLl3mDvMGvHYxhtXXyi1N8CTdzTlWvCTMFwaSZJnTkvKKkYWMIaWAnYGNSBskNbYCu8eqg2KLTtpaT6HQU0rhvgCUxUc1GpfGFOsLuPXSb8ef4KYI4F2L72ED81/RU+vAAAAA==[Pipeline] sleep
29+
Sleeping for 3 min 0 sec
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
622 3
2+
2385
3+
2685 4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version='1.1' encoding='UTF-8'?>
2+
<Tag>
3+
<node class="org.jenkinsci.plugins.workflow.graph.FlowStartNode">
4+
<parentIds/>
5+
<id>2</id>
6+
</node>
7+
<actions>
8+
<wf.a.TimingAction>
9+
<startTime>1678379140348</startTime>
10+
</wf.a.TimingAction>
11+
</actions>
12+
</Tag>

0 commit comments

Comments
 (0)