Skip to content

Commit 62562ac

Browse files
committed
Sketch of saving libraries as JAR files rather than unpacked
1 parent fb585f4 commit 62562ac

File tree

12 files changed

+220
-125
lines changed

12 files changed

+220
-125
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@
7575
<scope>import</scope>
7676
<type>pom</type>
7777
</dependency>
78+
<dependency>
79+
<groupId>org.jenkins-ci.plugins.workflow</groupId>
80+
<artifactId>workflow-cps</artifactId>
81+
<version>999999-SNAPSHOT</version> <!-- TODO https://github.com/jenkinsci/workflow-cps-plugin/pull/668 -->
82+
</dependency>
7883
</dependencies>
7984
</dependencyManagement>
8085
<dependencies>

src/main/java/org/jenkinsci/plugins/workflow/cps/global/UserDefinedGlobalVariable.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
*/
2323
// not @Extension because these are instantiated programmatically
2424
public class UserDefinedGlobalVariable extends GlobalVariable {
25+
// TODO switch to URL
2526
private final File help;
2627
private final String name;
2728

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

Lines changed: 43 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import java.util.logging.Logger;
5151
import edu.umd.cs.findbugs.annotations.CheckForNull;
5252
import edu.umd.cs.findbugs.annotations.NonNull;
53+
import java.util.jar.JarFile;
54+
import java.util.regex.Matcher;
5355
import java.util.regex.Pattern;
5456
import org.apache.commons.io.IOUtils;
5557
import org.jenkinsci.plugins.workflow.cps.CpsFlowExecution;
@@ -97,6 +99,7 @@
9799
if (action != null) {
98100
// Resuming a build, so just look up what we loaded before.
99101
for (LibraryRecord record : action.getLibraries()) {
102+
// TODO call LibraryRetriever.dir2Jar as needed
100103
FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
101104
for (String root : new String[] {"src", "vars"}) {
102105
FilePath dir = libDir.child(root);
@@ -147,9 +150,7 @@
147150
// Now actually try to retrieve the libraries.
148151
for (LibraryRecord record : librariesAdded.values()) {
149152
listener.getLogger().println("Loading library " + record.name + "@" + record.version);
150-
for (URL u : retrieve(record, retrievers.get(record.name), listener, build, execution)) {
151-
additions.add(new Addition(u, record.trusted));
152-
}
153+
additions.add(new Addition(retrieve(record, retrievers.get(record.name), listener, build, execution), record.trusted));
153154
}
154155
return additions;
155156
}
@@ -169,14 +170,14 @@ private enum CacheStatus {
169170
EXPIRED;
170171
}
171172

172-
private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheDir)
173+
private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration cachingConfiguration, @NonNull final FilePath versionCacheJar)
173174
throws IOException, InterruptedException
174175
{
175176
if (cachingConfiguration.isRefreshEnabled()) {
176177
final long cachingMilliseconds = cachingConfiguration.getRefreshTimeMilliseconds();
177178

178-
if(versionCacheDir.exists()) {
179-
if ((versionCacheDir.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) {
179+
if(versionCacheJar.exists()) {
180+
if ((versionCacheJar.lastModified() + cachingMilliseconds) > System.currentTimeMillis()) {
180181
return CacheStatus.VALID;
181182
} else {
182183
return CacheStatus.EXPIRED;
@@ -185,7 +186,7 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c
185186
return CacheStatus.DOES_NOT_EXIST;
186187
}
187188
} else {
188-
if (versionCacheDir.exists()) {
189+
if (versionCacheJar.exists()) {
189190
return CacheStatus.VALID;
190191
} else {
191192
return CacheStatus.DOES_NOT_EXIST;
@@ -194,16 +195,16 @@ private static CacheStatus getCacheStatus(@NonNull LibraryCachingConfiguration c
194195
}
195196

196197
/** Retrieve library files. */
197-
static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run<?,?> run, @NonNull CpsFlowExecution execution) throws Exception {
198+
static URL retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriever retriever, @NonNull TaskListener listener, @NonNull Run<?,?> run, @NonNull CpsFlowExecution execution) throws Exception {
198199
String name = record.name;
199200
String version = record.version;
200201
boolean changelog = record.changelog;
201202
LibraryCachingConfiguration cachingConfiguration = record.cachingConfiguration;
202-
FilePath libDir = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName());
203+
FilePath libJar = new FilePath(execution.getOwner().getRootDir()).child("libs/" + record.getDirectoryName() + ".jar");
203204
Boolean shouldCache = cachingConfiguration != null;
204-
final FilePath versionCacheDir = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName());
205+
final FilePath versionCacheJar = new FilePath(LibraryCachingConfiguration.getGlobalLibrariesCacheDir(), record.getDirectoryName() + ".jar");
205206
ReentrantReadWriteLock retrieveLock = getReadWriteLockFor(record.getDirectoryName());
206-
final FilePath lastReadFile = new FilePath(versionCacheDir, LibraryCachingConfiguration.LAST_READ_FILE);
207+
final FilePath lastReadFile = versionCacheJar.sibling(record.getDirectoryName() + "." + LibraryCachingConfiguration.LAST_READ_FILE);
207208

208209
if(shouldCache && cachingConfiguration.isExcluded(version)) {
209210
listener.getLogger().println("Library " + name + "@" + version + " is excluded from caching.");
@@ -213,13 +214,13 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
213214
if(shouldCache) {
214215
retrieveLock.readLock().lockInterruptibly();
215216
try {
216-
CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheDir);
217+
CacheStatus cacheStatus = getCacheStatus(cachingConfiguration, versionCacheJar);
217218
if (cacheStatus == CacheStatus.DOES_NOT_EXIST || cacheStatus == CacheStatus.EXPIRED) {
218219
retrieveLock.readLock().unlock();
219220
retrieveLock.writeLock().lockInterruptibly();
220221
try {
221222
boolean retrieve = false;
222-
switch (getCacheStatus(cachingConfiguration, versionCacheDir)) {
223+
switch (getCacheStatus(cachingConfiguration, versionCacheJar)) {
223224
case VALID:
224225
listener.getLogger().println("Library " + name + "@" + version + " is cached. Copying from home.");
225226
break;
@@ -229,18 +230,17 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
229230
case EXPIRED:
230231
long cachingMinutes = cachingConfiguration.getRefreshTimeMinutes();
231232
listener.getLogger().println("Library " + name + "@" + version + " is due for a refresh after " + cachingMinutes + " minutes, clearing.");
232-
if (versionCacheDir.exists()) {
233-
versionCacheDir.deleteRecursive();
234-
versionCacheDir.withSuffix("-name.txt").delete();
233+
if (versionCacheJar.exists()) {
234+
versionCacheJar.delete();
235+
versionCacheJar.sibling(record.getDirectoryName() + "-name.txt").delete();
235236
}
236237
retrieve = true;
237238
break;
238239
}
239240

240241
if (retrieve) {
241242
listener.getLogger().println("Caching library " + name + "@" + version);
242-
versionCacheDir.mkdirs();
243-
retriever.retrieve(name, version, changelog, versionCacheDir, run, listener);
243+
retriever.retrieveJar(name, version, changelog, versionCacheJar, run, listener);
244244
}
245245
retrieveLock.readLock().lock();
246246
} finally {
@@ -251,50 +251,44 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
251251
}
252252

253253
lastReadFile.touch(System.currentTimeMillis());
254-
versionCacheDir.withSuffix("-name.txt").write(name, "UTF-8");
255-
versionCacheDir.copyRecursiveTo(libDir);
254+
versionCacheJar.sibling(record.getDirectoryName() + "-name.txt").write(name, "UTF-8");
255+
versionCacheJar.copyTo(libJar);
256256
} finally {
257257
retrieveLock.readLock().unlock();
258258
}
259259
} else {
260-
retriever.retrieve(name, version, changelog, libDir, run, listener);
260+
retriever.retrieveJar(name, version, changelog, libJar, run, listener);
261261
}
262262
// Write the user-provided name to a file as a debugging aid.
263-
libDir.withSuffix("-name.txt").write(name, "UTF-8");
263+
libJar.sibling(record.getDirectoryName() + "-name.txt").write(name, "UTF-8");
264264

265265
// Replace any classes requested for replay:
266266
if (!record.trusted) {
267267
for (String clazz : ReplayAction.replacementsIn(execution)) {
268-
for (String root : new String[] {"src", "vars"}) {
269-
String rel = root + "/" + clazz.replace('.', '/') + ".groovy";
270-
FilePath f = libDir.child(rel);
271-
if (f.exists()) {
272-
String replacement = ReplayAction.replace(execution, clazz);
273-
if (replacement != null) {
274-
listener.getLogger().println("Replacing contents of " + rel);
275-
f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler
276-
}
268+
String rel = clazz.replace('.', '/') + ".groovy";
269+
/* TODO need to unpack & repack I guess
270+
FilePath f = libDir.child(rel);
271+
if (f.exists()) {
272+
String replacement = ReplayAction.replace(execution, clazz);
273+
if (replacement != null) {
274+
listener.getLogger().println("Replacing contents of " + rel);
275+
f.write(replacement, null); // TODO as below, unsure of encoding used by Groovy compiler
277276
}
278277
}
278+
*/
279279
}
280280
}
281-
List<URL> urls = new ArrayList<>();
282-
FilePath srcDir = libDir.child("src");
283-
if (srcDir.isDirectory()) {
284-
urls.add(srcDir.toURI().toURL());
285-
}
286-
FilePath varsDir = libDir.child("vars");
287-
if (varsDir.isDirectory()) {
288-
urls.add(varsDir.toURI().toURL());
289-
for (FilePath var : varsDir.list("*.groovy")) {
290-
record.variables.add(var.getBaseName());
291-
}
292-
}
293-
if (urls.isEmpty()) {
294-
throw new AbortException("Library " + name + " expected to contain at least one of src or vars directories");
281+
try (JarFile jf = new JarFile(libJar.getRemote())) {
282+
jf.stream().forEach(entry -> {
283+
Matcher m = ROOT_GROOVY_SOURCE.matcher(entry.getName());
284+
if (m.matches()) {
285+
record.variables.add(m.group(1));
286+
}
287+
});
295288
}
296-
return urls;
289+
return libJar.toURI().toURL();
297290
}
291+
private static final Pattern ROOT_GROOVY_SOURCE = Pattern.compile("([^/]+)[.]groovy");
298292

299293
/**
300294
* Loads resources for {@link ResourceStep}.
@@ -309,6 +303,7 @@ static List<URL> retrieve(@NonNull LibraryRecord record, @NonNull LibraryRetriev
309303
Run<?,?> run = (Run) executable;
310304
LibrariesAction action = run.getAction(LibrariesAction.class);
311305
if (action != null) {
306+
// TODO handle *.jar
312307
FilePath libs = new FilePath(run.getRootDir()).child("libs");
313308
for (LibraryRecord library : action.getLibraries()) {
314309
FilePath libResources = libs.child(library.getDirectoryName() + "/resources/");
@@ -347,6 +342,7 @@ private static String readResource(FilePath file, @CheckForNull String encoding)
347342
List<GlobalVariable> vars = new ArrayList<>();
348343
for (LibraryRecord library : action.getLibraries()) {
349344
for (String variable : library.variables) {
345+
// TODO pass URL of *.jar!/$variable.txt
350346
vars.add(new UserDefinedGlobalVariable(variable, new File(run.getRootDir(), "libs/" + library.getDirectoryName() + "/vars/" + variable + ".txt")));
351347
}
352348
}
@@ -367,6 +363,7 @@ private static String readResource(FilePath file, @CheckForNull String encoding)
367363
Run<?,?> run = (Run) executable;
368364
LibrariesAction action = run.getAction(LibrariesAction.class);
369365
if (action != null) {
366+
// TODO handle *.jar
370367
FilePath libs = new FilePath(run.getRootDir()).child("libs");
371368
for (LibraryRecord library : action.getLibraries()) {
372369
if (library.trusted) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public final class LibraryCachingConfiguration extends AbstractDescribableImpl<L
3131
private String excludedVersionsStr;
3232

3333
private static final String VERSIONS_SEPARATOR = " ";
34-
public static final String GLOBAL_LIBRARIES_DIR = "global-libraries-cache";
34+
public static final String GLOBAL_LIBRARIES_DIR = "global-libraries-jar-cache";
3535
public static final String LAST_READ_FILE = "last_read";
3636

3737
@DataBoundConstructor public LibraryCachingConfiguration(int refreshTimeMinutes, String excludedVersionsStr) {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public String getName() {
7171
}
7272

7373
/**
74-
* Returns a partially unique name that can be safely used as a directory name.
74+
* Returns a partially unique name that can be safely used as a directory or JAR base name.
7575
*
7676
* Uniqueness is based on the library name, version, whether it is trusted, and the source of the library.
7777
* {@link LibraryRetriever}-specific information such as the SCM is not used to produce this name.

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

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,68 @@
3535
import hudson.util.FormValidation;
3636
import edu.umd.cs.findbugs.annotations.CheckForNull;
3737
import edu.umd.cs.findbugs.annotations.NonNull;
38+
import hudson.util.io.ArchiverFactory;
39+
import java.io.IOException;
40+
import java.io.OutputStream;
3841

3942
/**
4043
* A way in which a library can be physically obtained for use in a build.
4144
*/
4245
public abstract class LibraryRetriever extends AbstractDescribableImpl<LibraryRetriever> implements ExtensionPoint {
4346

47+
/**
48+
* Obtains library sources.
49+
* @param name the {@link LibraryConfiguration#getName}
50+
* @param version the version of the library, such as from {@link LibraryConfiguration#getDefaultVersion} or an override
51+
* @param changelog whether to include changesets in the library in jobs using it from {@link LibraryConfiguration#getIncludeInChangesets}
52+
* @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/}
53+
* @param run a build which will use the library
54+
* @param listener a way to report progress
55+
* @throws Exception if there is any problem (use {@link AbortException} for user errors)
56+
*/
57+
public void retrieveJar(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run<?,?> run, @NonNull TaskListener listener) throws Exception {
58+
if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, boolean.class, FilePath.class, Run.class, TaskListener.class)) {
59+
FilePath tmp = target.withSuffix(".checkout");
60+
try {
61+
retrieve(name, version, changelog, tmp, run, listener);
62+
dir2Jar(tmp, target);
63+
} finally {
64+
tmp.deleteRecursive();
65+
}
66+
} else {
67+
throw new AbstractMethodError("Implement retrieveJar");
68+
}
69+
}
70+
71+
/**
72+
* Translates a historical directory with {@code src/} and/or {@code vars/} and/or {@code resources/} subdirectories
73+
* into a JAR file with Groovy in classpath orientation and {@code resources/} as a ZIP folder.
74+
*/
75+
static void dir2Jar(@NonNull FilePath dir, @NonNull FilePath jar) throws IOException, InterruptedException {
76+
// TODO do this more efficiently by packing JAR directly
77+
FilePath tmp2 = jar.withSuffix(".tmp2");
78+
tmp2.mkdirs();
79+
try {
80+
FilePath src = dir.child("src");
81+
if (src.isDirectory()) {
82+
src.moveAllChildrenTo(tmp2);
83+
}
84+
FilePath vars = dir.child("vars");
85+
if (vars.isDirectory()) {
86+
vars.moveAllChildrenTo(tmp2);
87+
}
88+
FilePath resources = dir.child("resources");
89+
if (resources.isDirectory()) {
90+
resources.renameTo(tmp2.child("resources"));
91+
}
92+
try (OutputStream os = jar.write()) {
93+
tmp2.archive(ArchiverFactory.ZIP, os, "**");
94+
}
95+
} finally {
96+
tmp2.deleteRecursive();
97+
}
98+
}
99+
44100
/**
45101
* Obtains library sources.
46102
* @param name the {@link LibraryConfiguration#getName}
@@ -51,7 +107,14 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl<LibraryRe
51107
* @param listener a way to report progress
52108
* @throws Exception if there is any problem (use {@link AbortException} for user errors)
53109
*/
54-
public abstract void retrieve(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run<?,?> run, @NonNull TaskListener listener) throws Exception;
110+
@Deprecated
111+
public void retrieve(@NonNull String name, @NonNull String version, boolean changelog, @NonNull FilePath target, @NonNull Run<?,?> run, @NonNull TaskListener listener) throws Exception {
112+
if (Util.isOverridden(LibraryRetriever.class, getClass(), "retrieve", String.class, String.class, FilePath.class, Run.class, TaskListener.class)) {
113+
retrieve(name, version, target, run, listener);
114+
} else {
115+
throw new AbstractMethodError("Implement retrieveJar");
116+
}
117+
}
55118

56119
/**
57120
* Obtains library sources.
@@ -62,8 +125,10 @@ public abstract class LibraryRetriever extends AbstractDescribableImpl<LibraryRe
62125
* @param listener a way to report progress
63126
* @throws Exception if there is any problem (use {@link AbortException} for user errors)
64127
*/
65-
// TODO this should have been made nonabstract and deprecated and delegated to the new version; may be able to use access-modifier to help
66-
public abstract void retrieve(@NonNull String name, @NonNull String version, @NonNull FilePath target, @NonNull Run<?,?> run, @NonNull TaskListener listener) throws Exception;
128+
@Deprecated
129+
public void retrieve(@NonNull String name, @NonNull String version, @NonNull FilePath target, @NonNull Run<?,?> run, @NonNull TaskListener listener) throws Exception {
130+
throw new AbstractMethodError("Implement retrieveJar");
131+
}
67132

68133
@Deprecated
69134
public FormValidation validateVersion(@NonNull String name, @NonNull String version) {

0 commit comments

Comments
 (0)