diff --git a/pom.xml b/pom.xml
index e15a10ac..43458dbf 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,6 +75,12 @@
import
pom
+
+
+ org.jenkins-ci.plugins.workflow
+ workflow-cps
+ 3637.v63b_c17e0ed5b_
+
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java b/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java
index 7ec37c28..35b1e978 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java
@@ -1,20 +1,21 @@
package org.jenkinsci.plugins.workflow.cps.global;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.NonNull;
import groovy.lang.Binding;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.net.URI;
import java.nio.charset.StandardCharsets;
-import org.apache.commons.io.FileUtils;
+import jenkins.model.Jenkins;
+import org.apache.commons.io.IOUtils;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.jenkinsci.plugins.workflow.cps.CpsCompilationErrorsException;
import org.jenkinsci.plugins.workflow.cps.CpsScript;
import org.jenkinsci.plugins.workflow.cps.CpsThread;
import org.jenkinsci.plugins.workflow.cps.GlobalVariable;
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import edu.umd.cs.findbugs.annotations.NonNull;
-import java.io.File;
-import java.io.IOException;
-import jenkins.model.Jenkins;
-
/**
* Global variable backed by user-supplied script.
*
@@ -22,10 +23,18 @@
*/
// not @Extension because these are instantiated programmatically
public class UserDefinedGlobalVariable extends GlobalVariable {
- private final File help;
+ private final URI help;
private final String name;
+ /**
+ * @deprecated use {@link #UserDefinedGlobalVariable(String, URI)}
+ */
+ @Deprecated
public UserDefinedGlobalVariable(String name, File help) {
+ this(name, help.toURI());
+ }
+
+ public UserDefinedGlobalVariable(String name, URI help) {
this.name = name;
this.help = help;
}
@@ -69,12 +78,14 @@ public Object getValue(@NonNull CpsScript script) throws Exception {
* Loads help from user-defined file, if available.
*/
public @CheckForNull String getHelpHtml() throws IOException {
- if (!help.exists()) return null;
-
- return Jenkins.get().getMarkupFormatter().translate(
- FileUtils.readFileToString(help, StandardCharsets.UTF_8).
- // Util.escape translates \n but not \r, and we do not know what platform the library will be checked out on:
- replace("\r\n", "\n"));
+ try {
+ return Jenkins.get().getMarkupFormatter().translate(
+ IOUtils.toString(help, StandardCharsets.UTF_8).
+ // Util.escape translates \n but not \r, and we do not know what platform the library will be checked out on:
+ replace("\r\n", "\n"));
+ } catch (FileNotFoundException x) {
+ return null;
+ }
}
@Override
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java
index 9ff64777..6a3c0b85 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryAdder.java
@@ -24,7 +24,6 @@
package org.jenkinsci.plugins.workflow.libs;
-import hudson.AbortException;
import hudson.Extension;
import hudson.ExtensionList;
import hudson.FilePath;
@@ -50,6 +49,13 @@
import java.util.logging.Logger;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
@@ -92,16 +98,22 @@
libraryChangelogs.put(parsed[0], changelogs.get(library));
librariesUnparsed.put(parsed[0], library);
}
+ TaskListener listener = execution.getOwner().getListener();
List additions = new ArrayList<>();
LibrariesAction action = build.getAction(LibrariesAction.class);
if (action != null) {
// Resuming a build, so just look up what we loaded before.
for (LibraryRecord record : action.getLibraries()) {
- FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
- for (String root : new String[] {"src", "vars"}) {
- FilePath dir = libDir.child(root);
- if (dir.isDirectory()) {
- additions.add(new Addition(dir.toURI().toURL(), record.trusted));
+ FilePath libJar = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName() + ".jar");
+ if (libJar.exists()) {
+ additions.add(new Addition(libJar.toURI().toURL(), record.trusted));
+ } else {
+ FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
+ if (libDir.isDirectory()) {
+ listener.getLogger().println("Migrating " + libDir + " to " + libJar);
+ LibraryRetriever.dir2Jar(record.getName(), libDir, libJar, listener);
+ libDir.deleteRecursive();
+ additions.add(new Addition(libJar.toURI().toURL(), record.trusted));
}
}
String unparsed = librariesUnparsed.get(record.name);
@@ -114,7 +126,6 @@
// Now we will see which libraries we want to load for this job.
Map librariesAdded = new LinkedHashMap<>();
Map retrievers = new HashMap<>();
- TaskListener listener = execution.getOwner().getListener();
for (LibraryResolver kind : ExtensionList.lookup(LibraryResolver.class)) {
boolean kindTrusted = kind.isTrusted();
for (LibraryConfiguration cfg : kind.forJob(build.getParent(), libraryVersions)) {
@@ -147,9 +158,7 @@
// Now actually try to retrieve the libraries.
for (LibraryRecord record : librariesAdded.values()) {
listener.getLogger().println("Loading library " + record.name + "@" + record.version);
- for (URL u : retrieve(record, retrievers.get(record.name), listener, build, execution)) {
- additions.add(new Addition(u, record.trusted));
- }
+ additions.add(new Addition(retrieve(record, retrievers.get(record.name), listener, build, execution), record.trusted));
}
return additions;
}
@@ -169,14 +178,14 @@ private enum CacheStatus {
EXPIRED;
}
- private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheDir)
+ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheJar)
throws IOException, InterruptedException
{
if (cachingConfiguration.isRefreshEnabled()) {
final long cachingMilliseconds = cachingConfiguration.getRefreshTimeMilliseconds();
- if(versionCacheDir.exists()) {
- if ((versionCacheDir.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) {
+ if(versionCacheJar.exists()) {
+ if ((versionCacheJar.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) {
return CacheStatus.VALID;
} else {
return CacheStatus.EXPIRED;
@@ -185,7 +194,7 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c
return CacheStatus.DOES_NOT_EXIST;
}
} else {
- if (versionCacheDir.exists()) {
+ if (versionCacheJar.exists()) {
return CacheStatus.VALID;
} else {
return CacheStatus.DOES_NOT_EXIST;
@@ -194,16 +203,16 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c
}
/** Retrieve library files. */
- static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run,?> run, @NonNull CpsFlowExecution execution) throws Exception {
+ static URL retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run,?> run, @NonNull CpsFlowExecution execution) throws Exception {
String name = record.name;
String version = record.version;
boolean changelog = record.changelog;
LibraryCachingConfiguration cachingConfiguration = record.cachingConfiguration;
- FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
+ FilePath libJar = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName() + ".jar");
Boolean shouldCache = cachingConfiguration != null;
- final FilePath versionCacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName());
+ final FilePath versionCacheJar = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName() + ".jar");
ReentrantReadWriteLock retrieveLock = getReadWriteLockFor(record.getDirectoryName());
- final FilePath lastReadFile = new FilePath(versionCacheDir, LibraryCachingConfiguration.LAST_READ_FILE);
+ final FilePath lastReadFile = versionCacheJar.sibling(record.getDirectoryName() + "." + LibraryCachingConfiguration.LAST_READ_FILE);
if(shouldCache && cachingConfiguration.isExcluded(version)) {
listener.getLogger().println("Library " + name + "@" + version + " is excluded from caching.");
@@ -213,13 +222,13 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
if(shouldCache) {
retrieveLock.readLock().lockInterruptibly();
try {
- CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheDir);
+ CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheJar);
if (cacheStatus == CacheStatus.DOES_NOT_EXIST || cacheStatus == CacheStatus.EXPIRED) {
retrieveLock.readLock().unlock();
retrieveLock.writeLock().lockInterruptibly();
try {
boolean retrieve = false;
- switch (getCacheStatus(cachingConfiguration, versionCacheDir)) {
+ switch (getCacheStatus(cachingConfiguration, versionCacheJar)) {
case VALID:
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
break;
@@ -229,9 +238,8 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
case EXPIRED:
long cachingMinutes = cachingConfiguration.getRefreshTimeMinutes();
listener.getLogger().println("Library " + name + "@" + version + " is due for a refresh after " + cachingMinutes + " minutes, clearing.");
- if (versionCacheDir.exists()) {
- versionCacheDir.deleteRecursive();
- versionCacheDir.withSuffix("-name.txt").delete();
+ if (versionCacheJar.exists()) {
+ versionCacheJar.delete();
}
retrieve = true;
break;
@@ -239,8 +247,7 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
if (retrieve) {
listener.getLogger().println("Caching library " + name + "@" + version);
- versionCacheDir.mkdirs();
- retriever.retrieve(name, version, changelog, versionCacheDir, run, listener);
+ retriever.retrieveJar(name, version, changelog, versionCacheJar, run, listener);
}
retrieveLock.readLock().lock();
} finally {
@@ -251,50 +258,52 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
}
lastReadFile.touch(System.currentTimeMillis());
- versionCacheDir.withSuffix("-name.txt").write(name, "UTF-8");
- versionCacheDir.copyRecursiveTo(libDir);
+ versionCacheJar.copyTo(libJar);
} finally {
retrieveLock.readLock().unlock();
}
} else {
- retriever.retrieve(name, version, changelog, libDir, run, listener);
+ retriever.retrieveJar(name, version, changelog, libJar, run, listener);
}
- // Write the user-provided name to a file as a debugging aid.
- libDir.withSuffix("-name.txt").write(name, "UTF-8");
// Replace any classes requested for replay:
if (!record.trusted) {
- for (String clazz : ReplayAction.replacementsIn(execution)) {
- for (String root : new String[] {"src", "vars"}) {
- String rel = root + "/" + clazz.replace('.', '/') + ".groovy";
- FilePath f = libDir.child(rel);
- if (f.exists()) {
- String replacement = ReplayAction.replace(execution, clazz);
- if (replacement != null) {
- listener.getLogger().println("Replacing contents of " + rel);
- f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler
+ Set clazzes = ReplayAction.replacementsIn(execution);
+ if (!clazzes.isEmpty()) {
+ FilePath tmp = libJar.withSuffix(".tmp");
+ try {
+ libJar.unzip(tmp);
+ for (String clazz : clazzes) {
+ String rel = clazz.replace('.', '/') + ".groovy";
+ FilePath f = tmp.child(rel);
+ if (f.exists()) {
+ String replacement = ReplayAction.replace(execution, clazz);
+ if (replacement != null) {
+ listener.getLogger().println("Replacing contents of " + rel);
+ f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler
+ }
}
}
+ libJar.delete();
+ try (OutputStream os = libJar.write()) {
+ tmp.zip(os, "**");
+ }
+ } finally {
+ tmp.deleteRecursive();
}
}
}
- List urls = new ArrayList<>();
- FilePath srcDir = libDir.child("src");
- if (srcDir.isDirectory()) {
- urls.add(srcDir.toURI().toURL());
- }
- FilePath varsDir = libDir.child("vars");
- if (varsDir.isDirectory()) {
- urls.add(varsDir.toURI().toURL());
- for (FilePath var : varsDir.list("*.groovy")) {
- record.variables.add(var.getBaseName());
- }
- }
- if (urls.isEmpty()) {
- throw new AbortException("Library " + name + " expected to contain at least one of src or vars directories");
+ try (JarFile jf = new JarFile(libJar.getRemote())) {
+ jf.stream().forEach(entry -> {
+ Matcher m = ROOT_GROOVY_SOURCE.matcher(entry.getName());
+ if (m.matches()) {
+ record.variables.add(m.group(1));
+ }
+ });
}
- return urls;
+ return libJar.toURI().toURL();
}
+ private static final Pattern ROOT_GROOVY_SOURCE = Pattern.compile("([^/]+)[.]groovy");
/**
* Loads resources for {@link ResourceStep}.
@@ -311,12 +320,18 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
if (action != null) {
FilePath libs = new FilePath(run.getRootDir()).child("libs");
for (LibraryRecord library : action.getLibraries()) {
- FilePath libResources = libs.child(library.getDirectoryName() + "/resources/");
- FilePath f = libResources.child(name);
- if (!new File(f.getRemote()).getCanonicalFile().toPath().startsWith(new File(libResources.getRemote()).getCanonicalPath())) {
- throw new AbortException(name + " references a file that is not contained within the library: " + library.name);
- } else if (f.exists()) {
- resources.put(library.name, readResource(f, encoding));
+ FilePath libJar = libs.child(library.getDirectoryName() + ".jar");
+ try (JarFile jf = new JarFile(libJar.getRemote())) {
+ JarEntry je = jf.getJarEntry("resources/" + name);
+ if (je != null) {
+ try (InputStream in = jf.getInputStream(je)) {
+ if ("Base64".equals(encoding)) {
+ resources.put(library.name, Base64.getEncoder().encodeToString(IOUtils.toByteArray(in)));
+ } else {
+ resources.put(library.name, IOUtils.toString(in, encoding)); // The platform default is used if encoding is null.
+ }
+ }
+ }
}
}
}
@@ -324,16 +339,6 @@ static List retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
return resources;
}
- private static String readResource(FilePath file, @CheckForNull String encoding) throws IOException, InterruptedException {
- try (InputStream in = file.read()) {
- if ("Base64".equals(encoding)) {
- return Base64.getEncoder().encodeToString(IOUtils.toByteArray(in));
- } else {
- return IOUtils.toString(in, encoding); // The platform default is used if encoding is null.
- }
- }
- }
-
@Extension public static class GlobalVars extends GlobalVariableSet {
@Override public Collection forRun(Run,?> run) {
@@ -347,7 +352,7 @@ private static String readResource(FilePath file, @CheckForNull String encoding)
List vars = new ArrayList<>();
for (LibraryRecord library : action.getLibraries()) {
for (String variable : library.variables) {
- vars.add(new UserDefinedGlobalVariable(variable, new File(run.getRootDir(), "libs/" + library.getDirectoryName() + "/vars/" + variable + ".txt")));
+ vars.add(new UserDefinedGlobalVariable(variable, URI.create("jar:" + new File(run.getRootDir(), "libs/" + library.getDirectoryName() + ".jar").toURI() + "!/" + variable + ".txt")));
}
}
return vars;
@@ -372,14 +377,19 @@ private static String readResource(FilePath file, @CheckForNull String encoding)
if (library.trusted) {
continue; // TODO JENKINS-41157 allow replay of trusted libraries if you have ADMINISTER
}
- for (String rootName : new String[] {"src", "vars"}) {
- FilePath root = libs.child(library.getDirectoryName() + "/" + rootName);
- if (!root.isDirectory()) {
- continue;
- }
- for (FilePath groovy : root.list("**/*.groovy")) {
- String clazz = className(groovy.getRemote(), root.getRemote());
- scripts.put(clazz, groovy.readToString()); // TODO no idea what encoding the Groovy compiler uses
+ FilePath jar = libs.child(library.getDirectoryName() + ".jar");
+ if (!jar.exists()) {
+ continue;
+ }
+ try (JarFile jf = new JarFile(jar.getRemote())) {
+ for (JarEntry je : (Iterable) jf.stream()::iterator) {
+ if (je.getName().endsWith(".groovy")) {
+ String text;
+ try (InputStream is = jf.getInputStream(je)) {
+ text = IOUtils.toString(is, StandardCharsets.UTF_8); // TODO no idea what encoding the Groovy compiler uses
+ }
+ scripts.put(je.getName().replaceFirst("[.]groovy$", "").replace('/', '.'), text);
+ }
}
}
}
@@ -391,10 +401,6 @@ private static String readResource(FilePath file, @CheckForNull String encoding)
return scripts;
}
- static String className(String groovy, String root) {
- return groovy.replaceFirst("^" + Pattern.quote(root) + "[/\\\\](.+)[.]groovy", "$1").replace('/', '.').replace('\\', '.');
- }
-
}
@Extension public static class Copier extends FlowCopier.ByRun {
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java
index 591dcc0a..8946f394 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanup.java
@@ -9,6 +9,7 @@
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import jenkins.model.Jenkins;
import jenkins.util.SystemProperties;
@@ -27,41 +28,37 @@ public LibraryCachingCleanup() {
@Override protected void execute(TaskListener listener) throws IOException, InterruptedException {
FilePath globalCacheDir = LibraryCachingConfiguration.getGlobalLibrariesCacheDir();
- for (FilePath library : globalCacheDir.list()) {
- if (!removeIfExpiredCacheDirectory(library)) {
- // Prior to the SECURITY-2586 fix, library caches had a two-level directory structure.
- // These caches will never be used again, so we delete any that we find.
- for (FilePath version: library.list()) {
- if (version.child(LibraryCachingConfiguration.LAST_READ_FILE).exists()) {
- library.deleteRecursive();
- break;
- }
- }
- }
+ for (FilePath libJar : globalCacheDir.list("*.jar")) {
+ removeIfExpiredCacheJar(libJar, listener);
}
+ // Old cache directory; format has changed, so just delete it:
+ Jenkins.get().getRootPath().child("global-libraries-cache").deleteRecursive();
}
/**
- * Delete the specified cache directory if it is outdated.
- * @return true if specified directory is a cache directory, regardless of whether it was outdated. Used to detect
- * whether the cache was created before or after the fix for SECURITY-2586.
+ * Delete the specified cache JAR if it is outdated.
*/
- private boolean removeIfExpiredCacheDirectory(FilePath library) throws IOException, InterruptedException {
- final FilePath lastReadFile = new FilePath(library, LibraryCachingConfiguration.LAST_READ_FILE);
- if (lastReadFile.exists()) {
- ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(library.getName());
+ private void removeIfExpiredCacheJar(FilePath libJar, TaskListener listener) throws IOException, InterruptedException {
+ final FilePath lastReadFile = libJar.sibling(libJar.getBaseName() + "." + LibraryCachingConfiguration.LAST_READ_FILE);
+ if (lastReadFile != null && lastReadFile.exists()) {
+ ReentrantReadWriteLock retrieveLock = LibraryAdder.getReadWriteLockFor(libJar.getBaseName());
retrieveLock.writeLock().lockInterruptibly();
try {
if (System.currentTimeMillis() - lastReadFile.lastModified() > TimeUnit.DAYS.toMillis(EXPIRE_AFTER_READ_DAYS)) {
-
- library.deleteRecursive();
- library.withSuffix("-name.txt").delete();
+ listener.getLogger().println("Deleting " + libJar);
+ try {
+ libJar.delete();
+ } finally {
+ lastReadFile.delete();
+ }
+ } else {
+ listener.getLogger().println(lastReadFile + " is sufficiently recent");
}
} finally {
retrieveLock.writeLock().unlock();
}
- return true;
+ } else {
+ listener.getLogger().println("No such file " + lastReadFile);
}
- return false;
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java
index afc3dfad..8cf77fa8 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfiguration.java
@@ -10,17 +10,15 @@
import org.kohsuke.stapler.QueryParameter;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
-import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
public final class LibraryCachingConfiguration extends AbstractDescribableImpl {
@@ -31,7 +29,7 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl implements ExtensionPoint {
+ /**
+ * JAR manifest attribute giving original library name.
+ */
+ static final String ATTR_LIBRARY_NAME = "Jenkins-Library-Name";
+
+ @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Non-final for write access via the Script Console")
+ public static boolean INCLUDE_SRC_TEST_IN_LIBRARIES = Boolean.getBoolean(SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES");
+
+ /**
+ * Obtains library sources.
+ * @param name the {@link LibraryConfiguration#getName}
+ * @param version the version of the library, such as from {@link LibraryConfiguration#getDefaultVersion} or an override
+ * @param changelog whether to include changesets in the library in jobs using it from {@link LibraryConfiguration#getIncludeInChangesets}
+ * @param target a JAR file in which to stash sources; should contain {@code **}{@code /*.groovy} (sources at top level will be considered vars), and optionally also {@code resources/}
+ * @param run a build which will use the library
+ * @param listener a way to report progress
+ * @throws Exception if there is any problem (use {@link AbortException} for user errors)
+ */
+ public void retrieveJar(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run,?> run, @NonNull TaskListener listener) throws Exception {
+ if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, boolean.class, FilePath.class, Run.class, TaskListener.class)) {
+ FilePath tmp = target.sibling(target.getBaseName() + "-checkout");
+ if (tmp == null) {
+ throw new IOException();
+ }
+ try {
+ retrieve(name, version, changelog, tmp, run, listener);
+ dir2Jar(name, tmp, target, listener);
+ } finally {
+ tmp.deleteRecursive();
+ FilePath tmp2 = WorkspaceList.tempDir(tmp);
+ if (tmp2 != null) {
+ tmp2.deleteRecursive();
+ }
+ }
+ } else {
+ throw new AbstractMethodError("Implement retrieveJar");
+ }
+ }
+
+ /**
+ * Translates a historical directory with {@code src/} and/or {@code vars/} and/or {@code resources/} subdirectories
+ * into a JAR file with Groovy in classpath orientation and {@code resources/} as a ZIP folder.
+ */
+ static void dir2Jar(@NonNull String name, @NonNull FilePath dir, @NonNull FilePath jar, @NonNull TaskListener listener) throws IOException, InterruptedException {
+ lookForBadSymlinks(dir, dir);
+ FilePath mf = jar.withSuffix(".mf");
+ try {
+ try (OutputStream os = mf.write()) {
+ Manifest m = new Manifest();
+ m.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
+ // Informational debugging aid, since the hex JAR basename will be meaningless:
+ m.getMainAttributes().putValue(ATTR_LIBRARY_NAME, name);
+ m.write(os);
+ }
+ try (OutputStream os = jar.write()) {
+ dir.archive(ArchiverFactory.ZIP, os, new DirScanner() {
+ @Override public void scan(File dir, FileVisitor visitor) throws IOException {
+ scanSingle(new File(mf.getRemote()), JarFile.MANIFEST_NAME, visitor);
+ String excludes;
+ if (!INCLUDE_SRC_TEST_IN_LIBRARIES && new File(dir, "src/test").isDirectory()) {
+ excludes = "test/";
+ listener.getLogger().println("Excluding src/test/ so that library test code cannot be accessed by Pipelines.");
+ listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins.");
+ } else {
+ excludes = null;
+ }
+ AtomicBoolean found = new AtomicBoolean();
+ FileVisitor verifyingVisitor = visitor.with(pathname -> {
+ if (pathname.getName().endsWith(".groovy")) {
+ found.set(true);
+ }
+ return true;
+ });
+ new DirScanner.Glob("**/*.groovy", excludes).scan(new File(dir, "src"), verifyingVisitor);
+ new DirScanner.Glob("*.groovy,*.txt", null).scan(new File(dir, "vars"), verifyingVisitor);
+ if (!found.get()) {
+ throw new AbortException("Library " + name + " expected to contain at least one of src or vars directories");
+ }
+ new DirScanner.Glob("resources/", null).scan(dir, visitor);
+ }
+ });
+ }
+ } finally {
+ mf.delete();
+ }
+ }
+
+ private static void lookForBadSymlinks(FilePath root, FilePath dir) throws IOException, InterruptedException {
+ for (FilePath child : dir.list()) {
+ if (child.isDirectory()) {
+ lookForBadSymlinks(root, child);
+ } else {
+ String link = child.readLink();
+ if (link != null) {
+ Path target = Path.of(dir.getRemote(), link).toRealPath();
+ if (!target.startsWith(Path.of(root.getRemote()))) {
+ throw new SecurityException(child + " → " + target + " is not inside " + root);
+ }
+ }
+ }
+ }
+ }
+
/**
* Obtains library sources.
* @param name the {@link LibraryConfiguration#getName}
@@ -51,7 +167,14 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl run, @NonNull TaskListener listener) throws Exception;
+ @Deprecated
+ public void retrieve(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run,?> run, @NonNull TaskListener listener) throws Exception {
+ if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, FilePath.class, Run.class, TaskListener.class)) {
+ retrieve(name, version, target, run, listener);
+ } else {
+ throw new AbstractMethodError("Implement retrieveJar");
+ }
+ }
/**
* Obtains library sources.
@@ -62,8 +185,10 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl run, @NonNull TaskListener listener) throws Exception;
+ @Deprecated
+ public void retrieve(@NonNull String name, @NonNull String version, @NonNull FilePath target, @NonNull Run,?> run, @NonNull TaskListener listener) throws Exception {
+ throw new AbstractMethodError("Implement retrieveJar");
+ }
@Deprecated
public FormValidation validateVersion(@NonNull String name, @NonNull String version) {
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java
index 69f709aa..19a2e6e7 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/LibraryStep.java
@@ -39,27 +39,24 @@
import hudson.model.TaskListener;
import hudson.scm.SCM;
import hudson.security.AccessControlled;
-import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.net.URI;
-import java.net.URISyntaxException;
import java.net.URL;
-import java.nio.file.Paths;
-import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
-import java.util.logging.Level;
import java.util.logging.Logger;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
+import edu.umd.cs.findbugs.annotations.Nullable;
import groovy.lang.MissingPropertyException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.inject.Inject;
import jenkins.model.Jenkins;
import jenkins.scm.impl.SingleSCMSource;
@@ -229,9 +226,7 @@ else if (retriever instanceof SCMRetriever) {
listener.getLogger().println("Loading library " + record.name + "@" + record.version);
CpsFlowExecution exec = (CpsFlowExecution) getContext().get(FlowExecution.class);
GroovyClassLoader loader = (trusted ? exec.getTrustedShell() : exec.getShell()).getClassLoader();
- for (URL u : LibraryAdder.retrieve(record, retriever, listener, run, (CpsFlowExecution) getContext().get(FlowExecution.class))) {
- loader.addURL(u);
- }
+ loader.addURL(LibraryAdder.retrieve(record, retriever, listener, run, (CpsFlowExecution) getContext().get(FlowExecution.class)));
run.save(); // persist changes to LibrariesAction.libraries*.variables
return new LoadedClasses(name, record.getDirectoryName(), trusted, changelog, run);
}
@@ -253,20 +248,40 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
private final @NonNull String prefix;
/** {@link Class#getName} minus package prefix */
private final @CheckForNull String clazz;
- /** {@code file:/…/libs/NAME/src/} */
- private final @NonNull String srcUrl;
+ /** {@code file:/…/libs/NAME/src/}, migrated to {@link #directoryName} */
+ @Deprecated
+ private @CheckForNull String srcUrl;
+ /** {@link LibraryRecord#getDirectoryName}, or null if resuming a pre-dir2Jar build */
+ private final @Nullable String directoryName;
LoadedClasses(String library, String libraryDirectoryName, boolean trusted, Boolean changelog, Run,?> run) {
- this(library, trusted, changelog, "", null, /* cf. LibraryAdder.retrieve */ new File(run.getRootDir(), "libs/" + libraryDirectoryName + "/src").toURI().toString());
+ this(library, trusted, changelog, "", null, libraryDirectoryName);
}
- LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String srcUrl) {
+ LoadedClasses(String library, boolean trusted, Boolean changelog, String prefix, String clazz, String directoryName) {
this.library = library;
this.trusted = trusted;
this.changelog = changelog;
this.prefix = prefix;
this.clazz = clazz;
- this.srcUrl = srcUrl;
+ this.directoryName = directoryName;
+ }
+
+ private static final Pattern SRC_URL = Pattern.compile("file:/.+/([0-9a-f]{64})/src/");
+
+ private Object readResolve() throws IllegalAccessException {
+ if (srcUrl != null) {
+ Matcher m = SRC_URL.matcher(srcUrl);
+ if (!m.matches()) {
+ // Perhaps predating hash-based naming (ace0de3, Feb 2022):
+ throw new IllegalAccessException("Unexpected form of library source URL: " + srcUrl);
+ }
+ String inferredDirectoryName = m.group(1);
+ LOGGER.fine(() -> "deserializing to " + inferredDirectoryName);
+ return new LoadedClasses(library, trusted, changelog, prefix, clazz, inferredDirectoryName);
+ } else {
+ return this;
+ }
}
@Override public Object getProperty(String property) {
@@ -290,12 +305,12 @@ public static final class LoadedClasses extends GroovyObjectSupport implements S
String fullClazz = clazz != null ? clazz + '$' + property : property;
loadClass(prefix + fullClazz);
// OK, class really exists, stash it and await methods
- return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, srcUrl);
+ return new LoadedClasses(library, trusted, changelog, prefix, fullClazz, directoryName);
} else if (clazz != null) {
throw new MissingPropertyException(property, loadClass(prefix + clazz));
} else {
// Still selecting package components.
- return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, srcUrl);
+ return new LoadedClasses(library, trusted, changelog, prefix + property + '.', null, directoryName);
}
}
@@ -341,6 +356,8 @@ private static boolean isSandboxed() {
// TODO putProperty for static field set
+ private static final Pattern JAR_URL = Pattern.compile("jar:file:/.+/([0-9a-f]{64})[.]jar!/.+");
+
private Class> loadClass(String name) {
CpsFlowExecution exec = CpsThread.current().getExecution();
GroovyClassLoader loader = (trusted ? exec.getTrustedShell() : exec.getShell()).getClassLoader();
@@ -353,16 +370,19 @@ private Class> loadClass(String name) {
if (definingLoader != loader) {
throw new IllegalAccessException("cannot access " + c + " via library handle: " + definingLoader + " is not " + loader);
}
- // Note that this goes through GroovyCodeSource.(File, String), which unlike (say) URLClassLoader set the “location” to the actual file, *not* the root.
- CodeSource codeSource = c.getProtectionDomain().getCodeSource();
- if (codeSource == null) {
- throw new IllegalAccessException(name + " had no defined code source");
+ URL res = loader.getResource(name.replaceFirst("[$][^.]+$", "").replace('.', '/') + ".groovy");
+ if (res == null) {
+ throw new IllegalAccessException("Unknown where " + name + " (" + c.getProtectionDomain().getCodeSource().getLocation() + ") was loaded from");
+ }
+ Matcher m = JAR_URL.matcher(res.toString());
+ if (!m.matches()) {
+ throw new IllegalAccessException("Unexpected URL " + res);
}
- String actual = canonicalize(codeSource.getLocation().toString());
- String srcUrlC = canonicalize(srcUrl); // do not do this in constructor: path might not actually exist
- if (!actual.startsWith(srcUrlC)) {
- throw new IllegalAccessException(name + " was defined in " + actual + " which was not inside " + srcUrlC);
+ String actual = m.group(1);
+ if (!actual.equals(directoryName)) {
+ throw new IllegalAccessException(name + " was defined in " + res + " rather than the expected " + directoryName);
}
+ LOGGER.fine(() -> "loaded " + name + " from " + res + " ~ " + actual + " as expected");
if (!Modifier.isPublic(c.getModifiers())) { // unlikely since Groovy makes classes implicitly public
throw new IllegalAccessException(c + " is not public");
}
@@ -374,16 +394,6 @@ private Class> loadClass(String name) {
}
}
- private static String canonicalize(String uri) {
- if (uri.startsWith("file:/")) {
- try {
- return Paths.get(new URI(uri)).toRealPath().toUri().toString();
- } catch (IOException | URISyntaxException x) {
- LOGGER.log(Level.WARNING, "could not canonicalize " + uri, x);
- }
- }
- return uri;
- }
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java
index d9f3a680..bf870409 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMBasedRetriever.java
@@ -26,7 +26,6 @@
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
@@ -48,7 +47,6 @@
import java.io.InterruptedIOException;
import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.logging.Level;
@@ -70,9 +68,6 @@ public abstract class SCMBasedRetriever extends LibraryRetriever {
private static final Logger LOGGER = Logger.getLogger(SCMBasedRetriever.class.getName());
- @SuppressFBWarnings(value = "MS_SHOULD_BE_FINAL", justification = "Non-final for write access via the Script Console")
- public static boolean INCLUDE_SRC_TEST_IN_LIBRARIES = Boolean.getBoolean(SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES");
-
/**
* Matches ".." in positions where it would be treated as the parent directory.
*
@@ -126,55 +121,21 @@ protected final void doRetrieve(String name, boolean changelog, @NonNull SCM scm
delegate.setChangelog(changelog);
Node node = Jenkins.get();
if (clone) {
- if (libraryPath == null) {
- retrySCMOperation(listener, () -> {
- delegate.checkout(run, target, listener, Jenkins.get().createLauncher(listener));
- WorkspaceList.tempDir(target).deleteRecursive();
- return null;
- });
- } else {
- FilePath root = target.child("root");
+ FilePath tmp = target.sibling(target.getBaseName() + "-checkout");
+ if (tmp == null) {
+ throw new IOException();
+ }
+ try {
retrySCMOperation(listener, () -> {
- delegate.checkout(run, root, listener, Jenkins.get().createLauncher(listener));
- WorkspaceList.tempDir(root).deleteRecursive();
+ delegate.checkout(run, tmp, listener, node.createLauncher(listener));
return null;
});
- FilePath subdir = root.child(libraryPath);
- if (!subdir.isDirectory()) {
- throw new AbortException("Did not find " + libraryPath + " in checkout");
- }
- for (String content : List.of("src", "vars", "resources")) {
- FilePath contentDir = subdir.child(content);
- if (contentDir.isDirectory()) {
- LOGGER.fine(() -> "Moving " + content + " to top level in " + target);
- contentDir.renameTo(target.child(content));
- }
- }
- // root itself will be deleted below
- }
- if (!INCLUDE_SRC_TEST_IN_LIBRARIES) {
- FilePath srcTest = target.child("src/test");
- if (srcTest.isDirectory()) {
- listener.getLogger().println("Excluding src/test/ from checkout of " + scm.getKey() + " so that library test code cannot be accessed by Pipelines.");
- listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins.");
- srcTest.deleteRecursive();
- }
- }
- for (FilePath child : target.list()) {
- String subdir = child.getName();
- switch (subdir) {
- case "src":
- // TODO delete everything that is not *.groovy
- break;
- case "vars":
- // TODO delete everything that is not *.groovy or *.txt, incl. subdirs
- break;
- case "resources":
- // OK, leave it all
- break;
- default:
- child.deleteRecursive();
- LOGGER.fine(() -> "Deleted " + child);
+ LibraryRetriever.dir2Jar(name, libraryPath != null ? tmp.child(libraryPath) : tmp, target, listener);
+ } finally {
+ tmp.deleteRecursive();
+ FilePath tmp2 = WorkspaceList.tempDir(tmp);
+ if (tmp2 != null) {
+ tmp2.deleteRecursive();
}
}
} else { // !clone
@@ -200,17 +161,9 @@ protected final void doRetrieve(String name, boolean changelog, @NonNull SCM scm
delegate.checkout(run, lease.path, listener, node.createLauncher(listener));
return null;
});
- if (libraryPath == null) {
- libraryPath = ".";
- }
- String excludes = INCLUDE_SRC_TEST_IN_LIBRARIES ? null : "src/test/";
- if (lease.path.child(libraryPath).child("src/test").exists()) {
- listener.getLogger().println("Excluding src/test/ from checkout of " + scm.getKey() + " so that library test code cannot be accessed by Pipelines.");
- listener.getLogger().println("To remove this log message, move the test code outside of src/. To restore the previous behavior that allowed access to files in src/test/, pass -D" + SCMSourceRetriever.class.getName() + ".INCLUDE_SRC_TEST_IN_LIBRARIES=true to the java command used to start Jenkins.");
- }
// Cannot add WorkspaceActionImpl to private CpsFlowExecution.flowStartNodeActions; do we care?
// Copy sources with relevant files from the checkout:
- lease.path.child(libraryPath).copyRecursiveTo("src/**/*.groovy,vars/*.groovy,vars/*.txt,resources/", excludes, target);
+ LibraryRetriever.dir2Jar(name, libraryPath != null ? lease.path.child(libraryPath) : lease.path, target, listener);
}
}
}
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java
index 2b01b819..8a9f1a19 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMRetriever.java
@@ -61,14 +61,10 @@ public SCM getScm() {
return scm;
}
- @Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
+ @Override public void retrieveJar(String name, String version, boolean changelog, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
doRetrieve(name, changelog, scm, target, run, listener);
}
- @Override public void retrieve(String name, String version, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
- retrieve(name, version, true, target, run, listener);
- }
-
@Override public FormValidation validateVersion(String name, String version, Item context) {
if (!Items.XSTREAM2.toXML(scm).contains("${library." + name + ".version}")) {
return FormValidation.warningWithMarkup("When using " + getDescriptor().getDisplayName() + ", you will need to include ${library." + Util.escape(name) + ".version}
in the SCM configuration somewhere.");
diff --git a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java
index 4ba9c5a6..fe0a10c3 100644
--- a/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java
+++ b/src/main/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetriever.java
@@ -71,7 +71,7 @@ public SCMSource getScm() {
return scm;
}
- @Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
+ @Override public void retrieveJar(String name, String version, boolean changelog, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
SCMRevision revision = retrySCMOperation(listener, () -> scm.fetch(version, listener, run.getParent()));
if (revision == null) {
throw new AbortException("No version " + version + " found for library " + name);
@@ -79,10 +79,6 @@ public SCMSource getScm() {
doRetrieve(name, changelog, scm.build(revision.getHead(), revision), target, run, listener);
}
- @Override public void retrieve(String name, String version, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
- retrieve(name, version, true, target, run, listener);
- }
-
@Override public FormValidation validateVersion(String name, String version, Item context) {
StringWriter w = new StringWriter();
try {
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java b/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java
index f84d8422..5fd678ef 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/cps/global/GrapeTest.java
@@ -197,9 +197,6 @@ private static final class LocalRetriever extends LibraryRetriever {
@Override public void retrieve(String name, String version, boolean changelog, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
new FilePath(lib).copyRecursiveTo(target);
}
- @Override public void retrieve(String name, String version, FilePath target, Run, ?> run, TaskListener listener) throws Exception {
- retrieve(name, version, false, target, run, listener);
- }
}
@Test public void outsideLibrary() throws Exception {
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java
index 2cd590b8..495a9399 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest.java
@@ -46,7 +46,6 @@
import jenkins.scm.impl.subversion.SubversionSCMSource;
import jenkins.scm.impl.subversion.SubversionSampleRepoRule;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.cps.GlobalVariable;
@@ -64,7 +63,6 @@
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.TestExtension;
-import org.jvnet.hudson.test.WithoutJenkins;
import org.jvnet.hudson.test.recipes.LocalData;
public class LibraryAdderTest {
@@ -374,7 +372,7 @@ public class LibraryAdderTest {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('lib@master') import test.Foo", true));
WorkflowRun b = r.assertBuildStatus(Result.FAILURE, p.scheduleBuild2(0));
- r.assertLogContains("Excluding src/test/ from checkout", b);
+ r.assertLogContains("Excluding src/test/", b);
r.assertLogContains("expected to contain at least one of src or vars directories", b);
}
@@ -475,6 +473,34 @@ public void correctLibraryDirectoryUsedWhenResumingOldBuild() throws Exception {
r.assertLogContains("called Foo", b);
}
+ @LocalData
+ @Test
+ public void correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild() throws Exception {
+ // LocalData captured as of 1091aea7fa252acae11389588addf603a505e195:
+ /*
+ sampleRepo.init();
+ sampleRepo.write("vars/foo.groovy", "def call() { echo('called Foo') }");
+ sampleRepo.git("add", "vars");
+ sampleRepo.git("commit", "--message=init");
+ GlobalLibraries.get().setLibraries(Collections.singletonList(
+ new LibraryConfiguration("lib",
+ new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)))));
+ WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
+ p.setDefinition(new CpsFlowDefinition(
+ "@Library('lib@master') _\n" +
+ "sleep 180\n" +
+ "foo()", true));
+ WorkflowRun b = p.scheduleBuild2(0).waitForStart();
+ r.waitForMessage("Sleeping for 3 min", b);
+ b.save();
+ Thread.sleep(Long.MAX_VALUE);
+ */
+ WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class);
+ WorkflowRun b = p.getBuildByNumber(1);
+ r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
+ r.assertLogContains("called Foo", b);
+ }
+
@Issue("JENKINS-66898")
@Test
public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable {
@@ -502,7 +528,7 @@ public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable {
WorkflowRun b1 = r.buildAndAssertSuccess(p1);
LibrariesAction action = b1.getAction(LibrariesAction.class);
LibraryRecord record = action.getLibraries().get(0);
- FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName());
+ FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar");
//Expire the cache
long oldMillis = ZonedDateTime.now().minusMinutes(35).toInstant().toEpochMilli();
cache.touch(oldMillis);
@@ -516,12 +542,4 @@ public void parallelBuildsDontInterfereWithExpiredCache() throws Throwable {
// r.assertLogContains("Library library@master is cached. Copying from home.", f2.get());
}
- @Issue("JENKINS-68544")
- @WithoutJenkins
- @Test public void className() {
- assertThat(LibraryAdder.LoadedLibraries.className("/path/to/lib/src/some/pkg/Type.groovy", "/path/to/lib/src"), is("some.pkg.Type"));
- assertThat(LibraryAdder.LoadedLibraries.className("C:\\path\\to\\lib\\src\\some\\pkg\\Type.groovy", "C:\\path\\to\\lib\\src"), is("some.pkg.Type"));
- assertThat(LibraryAdder.LoadedLibraries.className("C:\\path\\to\\Extra\\lib\\src\\some\\pkg\\Type.groovy", "C:\\path\\to\\Extra\\lib\\src"), is("some.pkg.Type"));
- }
-
}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java
index ff30c86e..a212588c 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingCleanupTest.java
@@ -27,7 +27,7 @@
import hudson.FilePath;
import hudson.util.StreamTaskListener;
import java.io.File;
-import java.time.ZonedDateTime;
+import java.util.concurrent.TimeUnit;
import jenkins.plugins.git.GitSCMSource;
import jenkins.plugins.git.GitSampleRepoRule;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
@@ -39,7 +39,6 @@
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.not;
-import static org.hamcrest.io.FileMatchers.anExistingDirectory;
import static org.hamcrest.io.FileMatchers.anExistingFile;
public class LibraryCachingCleanupTest {
@@ -67,28 +66,16 @@ public void smokes() throws Throwable {
WorkflowRun b = r.buildAndAssertSuccess(p);
LibrariesAction action = b.getAction(LibrariesAction.class);
LibraryRecord record = action.getLibraries().get(0);
- FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName());
- assertThat(new File(cache.getRemote()), anExistingDirectory());
+ FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar");
+ assertThat(new File(cache.getRemote()), anExistingFile());
// Run LibraryCachingCleanup and show that cache is not deleted.
ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr());
- assertThat(new File(cache.getRemote()), anExistingDirectory());
- assertThat(new File(cache.withSuffix("-name.txt").getRemote()), anExistingFile());
+ assertThat(new File(cache.getRemote()), anExistingFile());
// Run LibraryCachingCleanup after modifying LAST_READ_FILE to be an old date and and show that cache is deleted.
- long oldMillis = ZonedDateTime.now().minusDays(LibraryCachingCleanup.EXPIRE_AFTER_READ_DAYS + 1).toInstant().toEpochMilli();
- cache.child(LibraryCachingConfiguration.LAST_READ_FILE).touch(oldMillis);
+ long oldMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(LibraryCachingCleanup.EXPIRE_AFTER_READ_DAYS + 1);
+ cache.sibling(cache.getBaseName() + "." + LibraryCachingConfiguration.LAST_READ_FILE).touch(oldMillis);
ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr());
- assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
- assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingDirectory()));
- }
-
- @Test
- public void preSecurity2586() throws Throwable {
- FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child("name").child("version");
- cache.mkdirs();
- cache.child(LibraryCachingConfiguration.LAST_READ_FILE).touch(System.currentTimeMillis());
- ExtensionList.lookupSingleton(LibraryCachingCleanup.class).execute(StreamTaskListener.fromStderr());
- assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
- assertThat(new File(cache.getParent().getRemote()), not(anExistingDirectory()));
+ assertThat(new File(cache.getRemote()), not(anExistingFile()));
}
}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java
index b2a0a55f..67c47068 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryCachingConfigurationTest.java
@@ -173,13 +173,11 @@ public void clearCache() throws Exception {
WorkflowRun b = r.buildAndAssertSuccess(p);
LibrariesAction action = b.getAction(LibrariesAction.class);
LibraryRecord record = action.getLibraries().get(0);
- FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName());
- assertThat(new File(cache.getRemote()), anExistingDirectory());
- assertThat(new File(cache.withSuffix("-name.txt").getRemote()), anExistingFile());
+ FilePath cache = LibraryCachingConfiguration.getGlobalLibrariesCacheDir().child(record.getDirectoryName() + ".jar");
+ assertThat(new File(cache.getRemote()), anExistingFile());
// Clear the cache. TODO: Would be more realistic to set up security and use WebClient.
ExtensionList.lookupSingleton(LibraryCachingConfiguration.DescriptorImpl.class).doClearCache("library", false);
- assertThat(new File(cache.getRemote()), not(anExistingDirectory()));
- assertThat(new File(cache.withSuffix("-name.txt").getRemote()), not(anExistingFile()));
+ assertThat(new File(cache.getRemote()), not(anExistingFile()));
}
}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java
new file mode 100644
index 00000000..6958ec6b
--- /dev/null
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryRetrieverTest.java
@@ -0,0 +1,116 @@
+/*
+ * The MIT License
+ *
+ * Copyright 2023 CloudBees, Inc.
+ *
+ * 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.
+ */
+
+package org.jenkinsci.plugins.workflow.libs;
+
+import hudson.FilePath;
+import hudson.Functions;
+import hudson.util.StreamTaskListener;
+import java.nio.charset.StandardCharsets;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.jar.JarFile;
+import org.apache.commons.io.IOUtils;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assume.assumeFalse;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+public class LibraryRetrieverTest {
+
+ @Rule public TemporaryFolder tmp = new TemporaryFolder();
+
+ @Test public void justVars() throws Exception {
+ assertDir2Jar(Set.of("vars/xxx.groovy", "vars/yyy.groovy"), Set.of("xxx.groovy", "yyy.groovy"));
+ }
+
+ @Test public void justSrc() throws Exception {
+ assertDir2Jar(Set.of("src/p1/xxx.groovy", "src/p2/yyy.groovy"), Set.of("p1/xxx.groovy", "p2/yyy.groovy"));
+ }
+
+ @Test public void theWorks() throws Exception {
+ assertDir2Jar(Set.of("src/p1/xxx.groovy", "vars/yyy.groovy", "resources/a.txt", "resources/b/c.txt"), Set.of("p1/xxx.groovy", "yyy.groovy", "resources/a.txt", "resources/b/c.txt"));
+ }
+
+ @Test public void otherFiles() throws Exception {
+ assertDir2Jar(Set.of("vars/v.groovy", "vars/v.txt", "vars/README.md", "README.md", "docs/usage.png", "src/p1/C.groovy", "src/p1/README.md"), Set.of("v.groovy", "v.txt", "p1/C.groovy"));
+ }
+
+ @Test public void safeSymlinks() throws Exception {
+ assumeFalse(Functions.isWindows());
+ FilePath work = new FilePath(tmp.newFolder());
+ FilePath dir = work.child("dir");
+ dir.child("vars/x.groovy").write("content", null);
+ dir.child("resources/a.txt").write("content", null);
+ dir.child("resources/b.txt").symlinkTo("a.txt", StreamTaskListener.fromStderr());
+ FilePath jar = work.child("x.jar");
+ LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr());
+ try (JarFile jf = new JarFile(jar.getRemote())) {
+ assertThat(IOUtils.toString(jf.getInputStream(jf.getEntry("resources/a.txt")), StandardCharsets.UTF_8), is("content"));
+ assertThat(IOUtils.toString(jf.getInputStream(jf.getEntry("resources/b.txt")), StandardCharsets.UTF_8), is("content"));
+ }
+ }
+
+ @Test public void unsafeSymlinks() throws Exception {
+ assumeFalse(Functions.isWindows());
+ FilePath work = new FilePath(tmp.newFolder());
+ FilePath dir = work.child("dir");
+ dir.child("vars/x.groovy").write("content", null);
+ dir.child("resources").mkdirs();
+ work.child("secret.txt").write("s3cr3t", null);
+ dir.child("resources/hack.txt").symlinkTo("../../secret.txt", StreamTaskListener.fromStderr());
+ FilePath jar = work.child("x.jar");
+ assertThrows(SecurityException.class, () -> LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr()));
+ }
+
+ private void assertDir2Jar(Set inputs, Set outputs) throws Exception {
+ FilePath work = new FilePath(tmp.newFolder());
+ FilePath dir = work.child("dir");
+ for (String input : inputs) {
+ dir.child(input).write("xxx", null);
+ }
+ FilePath jar = work.child("x.jar");
+ var before = dir.list("**");
+ LibraryRetriever.dir2Jar("mylib", dir, jar, StreamTaskListener.fromStderr());
+ assertThat(dir.list("**"), arrayContainingInAnyOrder(before));
+ Set actualOutputs = new TreeSet<>();
+ try (JarFile jf = new JarFile(jar.getRemote())) {
+ assertThat(jf.getManifest().getMainAttributes().getValue(LibraryRetriever.ATTR_LIBRARY_NAME), is("mylib"));
+ jf.stream().forEach(e -> {
+ String name = e.getName();
+ if (!name.endsWith("/") && !name.startsWith("META-INF/")) {
+ actualOutputs.add(name);
+ }
+ });
+ }
+ assertThat(actualOutputs, is(outputs));
+ assertThat(work.list(), containsInAnyOrder(dir, jar));
+ }
+
+}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java
index 67f757c9..3eb12763 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/LibraryStepTest.java
@@ -36,6 +36,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.logging.Level;
import jenkins.plugins.git.GitSCMSource;
import jenkins.plugins.git.GitSampleRepoRule;
@@ -56,6 +57,8 @@
import org.jvnet.hudson.test.BuildWatcher;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.JenkinsRule;
+import org.jvnet.hudson.test.LoggerRule;
+import org.jvnet.hudson.test.recipes.LocalData;
@Issue("JENKINS-39450")
public class LibraryStepTest {
@@ -64,6 +67,7 @@ public class LibraryStepTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();
@Rule public GitSampleRepoRule sampleRepo2 = new GitSampleRepoRule();
+ @Rule public LoggerRule logging = new LoggerRule().record(LibraryStep.class, Level.FINE);
@Test public void configRoundtrip() throws Exception {
StepConfigTester stepTester = new StepConfigTester(r);
@@ -342,4 +346,27 @@ public class LibraryStepTest {
r.assertLogContains("/lib/java", b);
r.assertLogContains("/pipeline/java", b);
}
+
+ @LocalData
+ @Test public void classesWhenResumingPreDir2JarBuild() throws Exception {
+ // LocalData captured as of 1091aea7fa252acae11389588addf603a505e195:
+ /*
+ sampleRepo.init();
+ sampleRepo.write("src/pkg/C.groovy", "package pkg; class C {static void m() {23}}");
+ sampleRepo.git("add", "src");
+ sampleRepo.git("commit", "--message=init");
+ GlobalLibraries.get().setLibraries(Collections.singletonList(new LibraryConfiguration("stuff", new SCMSourceRetriever(new GitSCMSource(null, sampleRepo.toString(), "", "*", "", true)))));
+ WorkflowJob p = r.createProject(WorkflowJob.class, "p");
+ p.setDefinition(new CpsFlowDefinition("def lib = library('stuff@master'); sleep 180; echo(/got ${lib.pkg.C.m()}/)", true));
+ WorkflowRun b = p.scheduleBuild2(0).waitForStart();
+ r.waitForMessage("Sleeping for 3 min", b);
+ b.save();
+ Thread.sleep(Long.MAX_VALUE);
+ */
+ WorkflowJob p = r.jenkins.getItemByFullName("p", WorkflowJob.class);
+ WorkflowRun b = p.getBuildByNumber(1);
+ r.assertBuildStatus(Result.SUCCESS, r.waitForCompletion(b));
+ r.assertLogContains("got 23", b);
+ }
+
}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java
index c1faefca..769ccf1b 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/ResourceStepTest.java
@@ -195,7 +195,7 @@ public class ResourceStepTest {
Path resourcesDir = Paths.get(sampleRepo.getRoot().getPath(), "resources");
Files.createDirectories(resourcesDir);
Path symlinkPath = Paths.get(resourcesDir.toString(), "master.key");
- Files.createSymbolicLink(symlinkPath, Paths.get("../../../../../../../secrets/master.key"));
+ Files.createSymbolicLink(symlinkPath, Paths.get("../../../../secrets/master.key"));
sampleRepo.git("add", "src", "resources");
sampleRepo.git("commit", "--message=init");
@@ -204,7 +204,7 @@ public class ResourceStepTest {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('symlink-stuff@master') import Stuff; echo(Stuff.contents(this))", true));
- r.assertLogContains("master.key references a file that is not contained within the library: symlink-stuff", r.buildAndAssertStatus(Result.FAILURE, p));
+ r.assertLogContains("master.key is not inside", r.buildAndAssertStatus(Result.FAILURE, p));
}
@Issue("SECURITY-2476")
@@ -222,7 +222,7 @@ public class ResourceStepTest {
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('libres-stuff@master') import Stuff; echo(Stuff.contents(this))", true));
- r.assertLogContains("../../../../../../../secrets/master.key references a file that is not contained within the library: libres-stuff", r.buildAndAssertStatus(Result.FAILURE, p));
+ r.assertLogContains("No such library resource ../../../../../../../secrets/master.key could be found.", r.buildAndAssertStatus(Result.FAILURE, p));
}
@Test public void findResourcesAttemptsToLoadFromAllIncludedLibraries() throws Exception {
@@ -271,9 +271,9 @@ public void clearCache(String name) throws Exception {
public void modifyCacheTimestamp(String name, String version, long timestamp) throws Exception {
String cacheDirName = LibraryRecord.directoryNameFor(name, version, String.valueOf(true), GlobalLibraries.ForJob.class.getName());
- FilePath cacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), cacheDirName);
- if (cacheDir.exists()) {
- cacheDir.touch(timestamp);
+ FilePath cacheJar = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), cacheDirName + ".jar");
+ if (cacheJar.exists()) {
+ cacheJar.touch(timestamp);
}
}
diff --git a/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java b/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java
index 0b6a1583..1cd90fb6 100644
--- a/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java
+++ b/src/test/java/org/jenkinsci/plugins/workflow/libs/SCMSourceRetrieverTest.java
@@ -43,7 +43,6 @@
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
-import java.util.logging.Level;
import jenkins.plugins.git.GitSCMSource;
import jenkins.plugins.git.GitSampleRepoRule;
import jenkins.scm.api.SCMHead;
@@ -81,15 +80,13 @@
import org.jvnet.hudson.test.WithoutJenkins;
import static org.hamcrest.MatcherAssert.assertThat;
-import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
-import static org.hamcrest.Matchers.arrayWithSize;
+import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.matchesPattern;
import static org.hamcrest.Matchers.nullValue;
import static org.jenkinsci.plugins.workflow.libs.SCMBasedRetriever.PROHIBITED_DOUBLE_DOT;
import static org.junit.Assume.assumeFalse;
import org.jvnet.hudson.test.FlagRule;
-import org.jvnet.hudson.test.LoggerRule;
public class SCMSourceRetrieverTest {
@@ -97,8 +94,7 @@ public class SCMSourceRetrieverTest {
@Rule public JenkinsRule r = new JenkinsRule();
@Rule public GitSampleRepoRule sampleRepo = new GitSampleRepoRule();
@Rule public SubversionSampleRepoRule sampleRepoSvn = new SubversionSampleRepoRule();
- @Rule public FlagRule includeSrcTest = new FlagRule<>(() -> SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES, v -> SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = v);
- @Rule public LoggerRule logging = new LoggerRule().record(SCMBasedRetriever.class, Level.FINE);
+ @Rule public FlagRule includeSrcTest = new FlagRule<>(() -> LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES, v -> LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = v);
@Issue("JENKINS-40408")
@Test public void lease() throws Exception {
@@ -381,7 +377,6 @@ public static class BasicSCMSource extends SCMSource {
sampleRepo.init();
sampleRepo.write("vars/myecho.groovy", "def call() {echo 'something special'}");
sampleRepo.write("README.md", "Summary");
- sampleRepo.git("rm", "file");
sampleRepo.git("add", ".");
sampleRepo.git("commit", "--message=init");
GitSCMSource src = new GitSCMSource(sampleRepo.toString());
@@ -401,10 +396,8 @@ public static class BasicSCMSource extends SCMSource {
r.assertLogContains("Using shallow clone with depth 1", b);
r.assertLogContains("Avoid fetching tags", b);
r.assertLogNotContains("+refs/heads/*:refs/remotes/origin/*", b);
- File[] libDirs = new File(b.getRootDir(), "libs").listFiles(File::isDirectory);
- assertThat(libDirs, arrayWithSize(1));
- String[] entries = libDirs[0].list();
- assertThat(entries, arrayContainingInAnyOrder("vars"));
+ // Fails to reproduce presence of *.jar.tmp@tmp; probably specific to use of GIT_ASKPASS:
+ assertThat(new File(b.getRootDir(), "libs").list(), arrayContaining(matchesPattern("[0-9a-f]{64}[.]jar")));
}
@Test public void cloneModeLibraryPath() throws Exception {
@@ -422,10 +415,6 @@ public static class BasicSCMSource extends SCMSource {
p.setDefinition(new CpsFlowDefinition("@Library('root_sub_path@master') import myecho; myecho()", true));
WorkflowRun b = r.buildAndAssertSuccess(p);
r.assertLogContains("something special", b);
- File[] libDirs = new File(b.getRootDir(), "libs").listFiles(File::isDirectory);
- assertThat(libDirs, arrayWithSize(1));
- String[] entries = libDirs[0].list();
- assertThat(entries, arrayContainingInAnyOrder("vars"));
}
@Test public void cloneModeLibraryPathSecurity() throws Exception {
@@ -459,11 +448,11 @@ public static class BasicSCMSource extends SCMSource {
GlobalLibraries.get().setLibraries(Collections.singletonList(lc));
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('echoing@master') import myecho; myecho()", true));
- SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = false;
+ LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = false;
WorkflowRun b = r.buildAndAssertSuccess(p);
assertFalse(r.jenkins.getWorkspaceFor(p).withSuffix("@libs").isDirectory());
r.assertLogContains("something special", b);
- r.assertLogContains("Excluding src/test/ from checkout", b);
+ r.assertLogContains("Excluding src/test/", b);
}
@Test public void cloneModeIncludeSrcTest() throws Exception {
@@ -480,7 +469,7 @@ public static class BasicSCMSource extends SCMSource {
GlobalLibraries.get().setLibraries(Collections.singletonList(lc));
WorkflowJob p = r.jenkins.createProject(WorkflowJob.class, "p");
p.setDefinition(new CpsFlowDefinition("@Library('echoing@master') import myecho; myecho()", true));
- SCMBasedRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = true;
+ LibraryRetriever.INCLUDE_SRC_TEST_IN_LIBRARIES = true;
WorkflowRun b = r.buildAndAssertSuccess(p);
assertFalse(r.jenkins.getWorkspaceFor(p).withSuffix("@libs").isDirectory());
r.assertLogContains("got something special", b);
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml
new file mode 100644
index 00000000..2e9bc7ba
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml
@@ -0,0 +1,130 @@
+
+
+
+
+
+
+ lib
+ master
+
+ foo
+
+ true
+ true
+ 897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8
+
+
+
+
+
+
+ master
+
+
+ 3ed68a85765cb39641044af8dc4ad436fafe3fdd
+
+
+
+ master
+
+
+
+
+ 1
+
+
+
+
+
+ …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458
+
+
+
+
+
+ git …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458
+
+
+
+
+
+ 1
+ 1678113342893
+ 1678113342911
+ 0
+ UTF-8
+ false
+
+ SUCCESS
+
+
+ MAX_SURVIVABILITY
+
+
+ flowNode
+ 36231997
+
+
+ classLoad
+ 193985514
+
+
+ run
+ 152858131
+
+
+ parse
+ 500490530
+
+
+ saveProgram
+ 38950881
+
+
+ true
+ 3
+ 1:3
+ 2
+ false
+ false
+
+ false
+
+
+
+ 2
+
+
+ origin
+ +refs/heads/*:refs/remotes/origin/*
+ …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458
+
+
+
+
+ master
+
+
+ false
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+ …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/workspace/p@libs/999c1ee22db1f1f266cfcd6f657a4e9044467f77fcf8c8e4a377d3099a1d3a75
+ …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/jobs/p/builds/1/changelog13282840290199774426.xml
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog13282840290199774426.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog13282840290199774426.xml
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt
new file mode 100644
index 00000000..7951405f
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8-name.txt
@@ -0,0 +1 @@
+lib
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy
new file mode 100644
index 00000000..8f75b987
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/897d64f4c3184a6d97a566b1786f0f0f7702b01d5d4b3167434a1b110c3dfbf8/vars/foo.groovy
@@ -0,0 +1 @@
+def call() { echo('called Foo') }
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log
new file mode 100644
index 00000000..423b9c8d
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log
@@ -0,0 +1,28 @@
+Started
+Loading library lib@master
+Attempting to resolve master from remote references...
+ > git --version # timeout=10
+ > git --version # 'git version 2.34.1'
+ > git ls-remote -h -- …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 # timeout=10
+Found match: refs/heads/master revision 3ed68a85765cb39641044af8dc4ad436fafe3fdd
+The recommended git tool is: NONE
+No credentials specified
+Cloning the remote Git repository
+Cloning with configured refspecs honoured and without tags
+Cloning repository …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458
+ > git init …/pipeline-groovy-lib-plugin/target/tmp/j h17061459720094902767/workspace/p@libs/999c1ee22db1f1f266cfcd6f657a4e9044467f77fcf8c8e4a377d3099a1d3a75 # timeout=10
+Fetching upstream changes from …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458
+ > git --version # timeout=10
+ > git --version # 'git version 2.34.1'
+ > git fetch --no-tags --force --progress -- …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 +refs/heads/*:refs/remotes/origin/* # timeout=10
+ > git config remote.origin.url …/pipeline-groovy-lib-plugin/target/tmp/junit14177916507003849782/junit11215768211970974458 # timeout=10
+ > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
+Avoid second fetch
+Checking out Revision 3ed68a85765cb39641044af8dc4ad436fafe3fdd (master)
+ > git config core.sparsecheckout # timeout=10
+ > git checkout -f 3ed68a85765cb39641044af8dc4ad436fafe3fdd # timeout=10
+Commit message: "init"
+First time build. Skipping changelog.
+[8mha:////4DehqyOFtNH235D4++PGtYgv/4iflo72uInNzFKYe/qgAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[0m[Pipeline] Start of Pipeline
+[8mha:////4KmcPvrxnGvS+11mq0cJwM0MifzD3S6+ZqB6eVBoHivtAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbaGmsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gfya1RxvAAAAA==[0m[Pipeline] sleep
+Sleeping for 3 min 0 sec
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index
new file mode 100644
index 00000000..8a1427c8
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index
@@ -0,0 +1 @@
+2388 3
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat
new file mode 100644
index 00000000..e2bd5c9b
Binary files /dev/null and b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat differ
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml
new file mode 100644
index 00000000..725bc7eb
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ 2
+
+
+
+ 1678113343524
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml
new file mode 100644
index 00000000..3c34d0fe
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ 2
+
+ 3
+ org.jenkinsci.plugins.workflow.steps.SleepStep
+
+
+
+
+
+ time
+ 180
+
+
+
+ true
+
+
+ 1678113343620
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks
new file mode 100644
index 00000000..48ab9e85
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks
@@ -0,0 +1 @@
+lastSuccessfulBuild -1
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml
new file mode 100644
index 00000000..07f78a6f
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/config.xml
@@ -0,0 +1,13 @@
+
+
+ false
+
+
+
+ true
+
+
+ false
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber
new file mode 100644
index 00000000..0cfbf088
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber
@@ -0,0 +1 @@
+2
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml
new file mode 100644
index 00000000..33e6c526
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryAdderTest/correctLibraryDirectoryUsedWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml
@@ -0,0 +1,7 @@
+
+
+
+ p
+ 1
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml
new file mode 100644
index 00000000..d0441445
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/build.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ stuff
+ master
+
+ true
+ true
+ eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b
+
+
+
+
+
+
+ master
+
+
+ 98c9e41033d994442c0105ba3011fe39a57a69b1
+
+
+
+ master
+
+
+
+
+ 1
+
+
+
+
+
+ …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
+
+
+
+
+
+ git …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
+
+
+
+
+
+ 1
+ 1678379139742
+ 1678379139771
+ 0
+ UTF-8
+ false
+
+ SUCCESS
+
+
+ MAX_SURVIVABILITY
+
+
+ flowNode
+ 82579725
+
+
+ classLoad
+ 406679430
+
+
+ run
+ 370461094
+
+
+ parse
+ 387067399
+
+
+ saveProgram
+ 121174695
+
+
+ true
+ 4
+ 1:4
+ 2
+ false
+ false
+
+ false
+
+
+
+ 2
+
+
+ origin
+ +refs/heads/*:refs/remotes/origin/*
+ …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
+
+
+
+
+ master
+
+
+ false
+
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+ …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9
+ …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/jobs/p/builds/1/changelog5512666869627859761.xml
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog5512666869627859761.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/changelog5512666869627859761.xml
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt
new file mode 100644
index 00000000..59c5d2b4
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b-name.txt
@@ -0,0 +1 @@
+stuff
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy
new file mode 100644
index 00000000..6ac9168c
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/libs/eb3ddf43cdf7079385a1295409e0c698810cf8b02b166382c33fab989458805b/src/pkg/C.groovy
@@ -0,0 +1 @@
+package pkg; class C {static void m() {23}}
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log
new file mode 100644
index 00000000..97a7e935
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log
@@ -0,0 +1,29 @@
+Started
+[8mha:////4ESdDHApaBCDSybNFTtB8QH7JYx/td1ExPJr3dgp1IVpAAAAoh+LCAAAAAAAAP9tjTEOwjAQBM8BClpKHuFItIiK1krDC0x8GCfWnbEdkooX8TX+gCESFVvtrLSa5wtWKcKBo5UdUu8otU4GP9jS5Mixv3geZcdn2TIl9igbHBs2eJyx4YwwR1SwULBGaj0nRzbDRnX6rmuvydanHMu2V1A5c4MHCFXMWcf8hSnC9jqYxPTz/BXAFEIGsfuclm8zQVqFvQAAAA==[0m[Pipeline] Start of Pipeline
+[8mha:////4GmDEP+pf+jq6je4q08twRF2g64HEz1ZmA5RyCc5wqQeAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbaGmsbAmNJ0AWEZb8zwLrbuWJvJp3kLiJlZNMMm+a93rDOic4UbLcG+wdZu14DKOti0+U+lugiXu6ck2YKRguzSSpM+cFJRUDS1gDKwEbgzpQdmgLbIVXD9UGhba9lFS/o4DGdQM8gYlqLiqVL8wJdvexy4Q/z18BzLEA29ce4gfya1RxvAAAAA==[0m[Pipeline] library
+Loading library stuff@master
+Attempting to resolve master from remote references...
+ > git --version # timeout=10
+ > git --version # 'git version 2.34.1'
+ > git ls-remote -h -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10
+Found match: refs/heads/master revision 98c9e41033d994442c0105ba3011fe39a57a69b1
+The recommended git tool is: NONE
+No credentials specified
+Cloning the remote Git repository
+Cloning with configured refspecs honoured and without tags
+Cloning repository …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
+ > git init …/pipeline-groovy-lib-plugin/target/tmp/j h744515433002142412/workspace/p@libs/f42b7770bb31e81024ba7b8451a6e63e6a1ce3eac92cd7c91e73094d7d85b2b9 # timeout=10
+Fetching upstream changes from …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657
+ > git --version # timeout=10
+ > git --version # 'git version 2.34.1'
+ > git fetch --no-tags --force --progress -- …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 +refs/heads/*:refs/remotes/origin/* # timeout=10
+ > git config remote.origin.url …/pipeline-groovy-lib-plugin/target/tmp/junit12658522753310550560/junit9582443866860979657 # timeout=10
+ > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
+Avoid second fetch
+Checking out Revision 98c9e41033d994442c0105ba3011fe39a57a69b1 (master)
+ > git config core.sparsecheckout # timeout=10
+ > git checkout -f 98c9e41033d994442c0105ba3011fe39a57a69b1 # timeout=10
+Commit message: "init"
+First time build. Skipping changelog.
+[8mha:////4JPM8UkGCuomMvb51Uo5DyzahUcbxePS+FWXyxySXcOwAAAAoh+LCAAAAAAAAP9tjTEOAiEURD9rLGwtPQSbGDtjZUtoPAGyiLDkfxZYdytP5NW8g8RNrJxkknnTvNcb1jnBiZLl3mDvMGvHYxhtXXyi1N8CTdzTlWvCTMFwaSZJnTkvKKkYWMIaWAnYGNSBskNbYCu8eqg2KLTtpaT6HQU0rhvgCUxUc1GpfGFOsLuPXSb8ef4KYI4F2L72ED81/RU+vAAAAA==[0m[Pipeline] sleep
+Sleeping for 3 min 0 sec
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index
new file mode 100644
index 00000000..45645b44
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/log-index
@@ -0,0 +1,3 @@
+622 3
+2385
+2685 4
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat
new file mode 100644
index 00000000..8a44b773
Binary files /dev/null and b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/program.dat differ
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml
new file mode 100644
index 00000000..bf6dd7fd
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/2.xml
@@ -0,0 +1,12 @@
+
+
+
+
+ 2
+
+
+
+ 1678379140348
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml
new file mode 100644
index 00000000..9cf66a5b
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/3.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ 2
+
+ 3
+ org.jenkinsci.plugins.workflow.libs.LibraryStep
+
+
+
+
+
+ identifier
+ stuff@master
+
+
+
+ true
+
+
+ 1678379140501
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml
new file mode 100644
index 00000000..7cf17cd2
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/1/workflow/4.xml
@@ -0,0 +1,26 @@
+
+
+
+
+ 3
+
+ 4
+ org.jenkinsci.plugins.workflow.steps.SleepStep
+
+
+
+
+
+ time
+ 180
+
+
+
+ true
+
+
+ 1678379141028
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/legacyIds
new file mode 100644
index 00000000..e69de29b
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks
new file mode 100644
index 00000000..48ab9e85
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/builds/permalinks
@@ -0,0 +1 @@
+lastSuccessfulBuild -1
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml
new file mode 100644
index 00000000..69201d5e
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/config.xml
@@ -0,0 +1,11 @@
+
+
+ false
+
+
+
+ true
+
+
+ false
+
\ No newline at end of file
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber
new file mode 100644
index 00000000..0cfbf088
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/jobs/p/nextBuildNumber
@@ -0,0 +1 @@
+2
diff --git a/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml
new file mode 100644
index 00000000..33e6c526
--- /dev/null
+++ b/src/test/resources/org/jenkinsci/plugins/workflow/libs/LibraryStepTest/classesWhenResumingPreDir2JarBuild/org.jenkinsci.plugins.workflow.flow.FlowExecutionList.xml
@@ -0,0 +1,7 @@
+
+
+
+ p
+ 1
+
+
\ No newline at end of file