Skip to content

Commit 83438f8

Browse files
authored
Merge pull request #49585 from gsmet/jar-build-step
Rewrite JarResultBuildStep and enable parallel compression of jars
2 parents 9bff1d0 + cfc8d90 commit 83438f8

File tree

27 files changed

+2335
-1548
lines changed

27 files changed

+2335
-1548
lines changed

core/deployment/pom.xml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
<groupId>org.apache.commons</groupId>
3131
<artifactId>commons-lang3</artifactId>
3232
</dependency>
33+
<dependency>
34+
<groupId>org.apache.commons</groupId>
35+
<artifactId>commons-compress</artifactId>
36+
</dependency>
3337
<dependency>
3438
<groupId>org.wildfly.common</groupId>
3539
<artifactId>wildfly-common</artifactId>
@@ -215,8 +219,6 @@
215219
<exclude>com.google.code.findbugs:jsr305</exclude>
216220
<!-- com.google.guava:listenablefuture is empty and the ListenableFuture class is available in Guava -->
217221
<exclude>com.google.guava:listenablefuture</exclude>
218-
<!-- let's avoid having commons-io crawling into quarkus-core -->
219-
<exclude>commons-io:commons-io</exclude>
220222
</excludes>
221223
</bannedDependencies>
222224
</rules>

core/deployment/src/main/java/io/quarkus/deployment/cmd/RunCommandProcessor.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package io.quarkus.deployment.cmd;
22

3-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEFAULT_FAST_JAR_DIRECTORY_NAME;
4-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS_RUN_JAR;
3+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEFAULT_FAST_JAR_DIRECTORY_NAME;
4+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS_RUN_JAR;
55

66
import java.io.File;
77
import java.nio.file.Path;

core/deployment/src/main/java/io/quarkus/deployment/dev/IsolatedRemoteDevModeMain.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
import io.quarkus.deployment.dev.remote.RemoteDevClient;
3939
import io.quarkus.deployment.dev.remote.RemoteDevClientProvider;
4040
import io.quarkus.deployment.mutability.DevModeTask;
41-
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;
41+
import io.quarkus.deployment.pkg.jar.FastJarFormat;
4242
import io.quarkus.deployment.steps.ClassTransformingBuildStep;
4343
import io.quarkus.dev.spi.DeploymentFailedStartHandler;
4444
import io.quarkus.dev.spi.DevModeType;
@@ -330,7 +330,7 @@ public Throwable getProblem() {
330330
}
331331

332332
static Map<String, String> createHashes(Path appRoot) throws IOException {
333-
Path quarkus = appRoot.resolve(JarResultBuildStep.QUARKUS); //we filter this jar, it has no relevance for remote dev
333+
Path quarkus = appRoot.resolve(FastJarFormat.QUARKUS); //we filter this jar, it has no relevance for remote dev
334334
Map<String, String> hashes = new HashMap<>();
335335
Files.walkFileTree(appRoot, new FileVisitor<Path>() {
336336
@Override

core/deployment/src/main/java/io/quarkus/deployment/mutability/DevModeTask.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.quarkus.deployment.mutability;
22

3-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
4-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
5-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
6-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
3+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.APPMODEL_DAT;
4+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.BUILD_SYSTEM_PROPERTIES;
5+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEPLOYMENT_LIB;
6+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.LIB;
7+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS;
78

89
import java.io.Closeable;
910
import java.io.IOException;
@@ -28,7 +29,6 @@
2829
import io.quarkus.bootstrap.util.IoUtils;
2930
import io.quarkus.deployment.dev.DevModeContext;
3031
import io.quarkus.deployment.dev.IsolatedDevModeMain;
31-
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;
3232
import io.quarkus.dev.spi.DevModeType;
3333
import io.quarkus.maven.dependency.ArtifactKey;
3434
import io.quarkus.maven.dependency.ResolvedArtifactDependency;
@@ -40,7 +40,7 @@ public class DevModeTask {
4040
public static Closeable main(Path appRoot) throws Exception {
4141

4242
try (ObjectInputStream in = new ObjectInputStream(
43-
Files.newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(JarResultBuildStep.APPMODEL_DAT)))) {
43+
Files.newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(APPMODEL_DAT)))) {
4444
Properties buildSystemProperties = new Properties();
4545
try (InputStream buildIn = Files
4646
.newInputStream(appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES))) {

core/deployment/src/main/java/io/quarkus/deployment/mutability/ReaugmentTask.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package io.quarkus.deployment.mutability;
22

3-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
4-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
5-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
6-
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
3+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.APPMODEL_DAT;
4+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.BUILD_SYSTEM_PROPERTIES;
5+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEPLOYMENT_LIB;
6+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.LIB;
7+
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS;
78

89
import java.io.InputStream;
910
import java.io.ObjectInputStream;
@@ -20,7 +21,6 @@
2021
import io.quarkus.bootstrap.app.QuarkusBootstrap;
2122
import io.quarkus.bootstrap.model.ApplicationModel;
2223
import io.quarkus.bootstrap.model.MutableJarApplicationModel;
23-
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;
2424

2525
public class ReaugmentTask {
2626

@@ -29,7 +29,7 @@ public static void main(Path appRoot) throws Exception {
2929
Path deploymentLib = appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB);
3030
Path buildSystemProps = appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES);
3131
try (ObjectInputStream in = new ObjectInputStream(
32-
Files.newInputStream(deploymentLib.resolve(JarResultBuildStep.APPMODEL_DAT)))) {
32+
Files.newInputStream(deploymentLib.resolve(APPMODEL_DAT)))) {
3333
Properties buildSystemProperties = new Properties();
3434
try (InputStream buildIn = Files
3535
.newInputStream(buildSystemProps)) {
Lines changed: 285 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,285 @@
1+
package io.quarkus.deployment.pkg.jar;
2+
3+
import static io.quarkus.commons.classloading.ClassLoaderHelper.fromClassNameToResourceName;
4+
5+
import java.io.IOException;
6+
import java.io.UncheckedIOException;
7+
import java.nio.file.Files;
8+
import java.nio.file.Path;
9+
import java.util.ArrayList;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.Set;
14+
import java.util.function.Predicate;
15+
import java.util.jar.Attributes;
16+
import java.util.jar.Manifest;
17+
18+
import org.jboss.logging.Logger;
19+
20+
import io.quarkus.builder.item.BuildItem;
21+
import io.quarkus.deployment.ApplicationArchive;
22+
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
23+
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
24+
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
25+
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
26+
import io.quarkus.deployment.builditem.MainClassBuildItem;
27+
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
28+
import io.quarkus.deployment.pkg.PackageConfig;
29+
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
30+
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
31+
import io.quarkus.maven.dependency.ArtifactKey;
32+
import io.quarkus.maven.dependency.ResolvedDependency;
33+
import io.quarkus.paths.PathVisit;
34+
import io.quarkus.paths.PathVisitor;
35+
36+
public abstract class AbstractJarBuilder<T extends BuildItem> implements JarBuilder<T> {
37+
38+
private static final Logger LOG = Logger.getLogger(AbstractJarBuilder.class);
39+
40+
protected final CurateOutcomeBuildItem curateOutcome;
41+
protected final OutputTargetBuildItem outputTarget;
42+
protected final ApplicationInfoBuildItem applicationInfo;
43+
protected final PackageConfig packageConfig;
44+
protected final MainClassBuildItem mainClass;
45+
protected final ApplicationArchivesBuildItem applicationArchives;
46+
protected final TransformedClassesBuildItem transformedClasses;
47+
protected final List<GeneratedClassBuildItem> generatedClasses;
48+
protected final List<GeneratedResourceBuildItem> generatedResources;
49+
protected final Set<ArtifactKey> removedArtifactKeys;
50+
51+
public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome,
52+
OutputTargetBuildItem outputTarget,
53+
ApplicationInfoBuildItem applicationInfo,
54+
PackageConfig packageConfig,
55+
MainClassBuildItem mainClass,
56+
ApplicationArchivesBuildItem applicationArchives,
57+
TransformedClassesBuildItem transformedClasses,
58+
List<GeneratedClassBuildItem> generatedClasses,
59+
List<GeneratedResourceBuildItem> generatedResources,
60+
Set<ArtifactKey> removedArtifactKeys) {
61+
this.curateOutcome = curateOutcome;
62+
this.outputTarget = outputTarget;
63+
this.applicationInfo = applicationInfo;
64+
this.packageConfig = packageConfig;
65+
this.mainClass = mainClass;
66+
this.applicationArchives = applicationArchives;
67+
this.transformedClasses = transformedClasses;
68+
this.generatedClasses = generatedClasses;
69+
this.generatedResources = generatedResources;
70+
this.removedArtifactKeys = removedArtifactKeys;
71+
}
72+
73+
protected static ArchiveCreator newArchiveCreator(Path archivePath, PackageConfig config) throws IOException {
74+
return new ZipFileSystemArchiveCreator(archivePath, config.jar().compress());
75+
}
76+
77+
/**
78+
* Copy files from {@code archive} to {@code fs}, filtering out service providers into the given map.
79+
*
80+
* @param archive the root application archive
81+
* @param archiveCreator the archive creator
82+
* @param services the services map
83+
* @throws IOException if an error occurs
84+
*/
85+
protected static void copyFiles(ApplicationArchive archive, ArchiveCreator archiveCreator,
86+
Map<String, List<byte[]>> services,
87+
Predicate<String> ignoredEntriesPredicate) throws IOException {
88+
try {
89+
archive.accept(tree -> {
90+
tree.walk(new PathVisitor() {
91+
@Override
92+
public void visitPath(PathVisit visit) {
93+
final Path file = visit.getRoot().relativize(visit.getPath());
94+
final String relativePath = toUri(file);
95+
if (relativePath.isEmpty() || ignoredEntriesPredicate.test(relativePath)) {
96+
return;
97+
}
98+
try {
99+
if (Files.isDirectory(visit.getPath())) {
100+
archiveCreator.addDirectory(relativePath);
101+
} else {
102+
if (relativePath.startsWith("META-INF/services/") && relativePath.length() > 18
103+
&& services != null) {
104+
final byte[] content;
105+
try {
106+
content = Files.readAllBytes(visit.getPath());
107+
} catch (IOException e) {
108+
throw new UncheckedIOException(e);
109+
}
110+
services.computeIfAbsent(relativePath, (u) -> new ArrayList<>()).add(content);
111+
} else if (!relativePath.equals("META-INF/INDEX.LIST")) {
112+
//TODO: auto generate INDEX.LIST
113+
//this may have implications for Camel though, as they change the layout
114+
//also this is only really relevant for the thin jar layout
115+
archiveCreator.addFileIfNotExists(visit.getPath(), relativePath);
116+
}
117+
}
118+
} catch (IOException e) {
119+
throw new UncheckedIOException(e);
120+
}
121+
}
122+
});
123+
});
124+
} catch (RuntimeException re) {
125+
final Throwable cause = re.getCause();
126+
if (cause instanceof IOException) {
127+
throw (IOException) cause;
128+
}
129+
throw re;
130+
}
131+
}
132+
133+
protected void copyCommonContent(ArchiveCreator archiveCreator,
134+
Map<String, List<byte[]>> concatenatedEntries,
135+
Predicate<String> ignoredEntriesPredicate)
136+
throws IOException {
137+
138+
//TODO: this is probably broken in gradle
139+
// if (Files.exists(augmentOutcome.getConfigDir())) {
140+
// copyFiles(augmentOutcome.getConfigDir(), runnerZipFs, services);
141+
// }
142+
for (Set<TransformedClassesBuildItem.TransformedClass> transformed : transformedClasses
143+
.getTransformedClassesByJar().values()) {
144+
for (TransformedClassesBuildItem.TransformedClass i : transformed) {
145+
if (i.getData() != null) {
146+
archiveCreator.addFile(i.getData(), i.getFileName());
147+
}
148+
}
149+
}
150+
for (GeneratedClassBuildItem i : generatedClasses) {
151+
String fileName = fromClassNameToResourceName(i.internalName());
152+
archiveCreator.addFileIfNotExists(i.getClassData(), fileName, ArchiveCreator.CURRENT_APPLICATION);
153+
}
154+
155+
for (GeneratedResourceBuildItem i : generatedResources) {
156+
if (ignoredEntriesPredicate.test(i.getName())) {
157+
continue;
158+
}
159+
if (i.getName().startsWith("META-INF/services/")) {
160+
concatenatedEntries.computeIfAbsent(i.getName(), (u) -> new ArrayList<>()).add(i.getData());
161+
continue;
162+
}
163+
archiveCreator.addFileIfNotExists(i.getData(), i.getName(), ArchiveCreator.CURRENT_APPLICATION);
164+
}
165+
166+
copyFiles(applicationArchives.getRootArchive(), archiveCreator, concatenatedEntries, ignoredEntriesPredicate);
167+
168+
for (Map.Entry<String, List<byte[]>> entry : concatenatedEntries.entrySet()) {
169+
archiveCreator.addFile(entry.getValue(), entry.getKey());
170+
}
171+
}
172+
173+
/**
174+
* Manifest generation is quite simple : we just have to push some attributes in manifest.
175+
* However, it gets a little more complex if the manifest preexists.
176+
* So we first try to see if a manifest exists, and otherwise create a new one.
177+
*
178+
* <b>BEWARE</b> this method should be invoked after file copy from target/classes and so on.
179+
* Otherwise, this manifest manipulation will be useless.
180+
*/
181+
protected static void generateManifest(ArchiveCreator archiveCreator, final String classPath, PackageConfig config,
182+
ResolvedDependency appArtifact,
183+
String mainClassName,
184+
ApplicationInfoBuildItem applicationInfo)
185+
throws IOException {
186+
final Manifest manifest = new Manifest();
187+
188+
Attributes attributes = manifest.getMainAttributes();
189+
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
190+
// JDK 24+ needs --add-opens=java.base/java.lang=ALL-UNNAMED for org.jboss.JDKSpecific.ThreadAccess.clearThreadLocals()
191+
attributes.put(new Attributes.Name("Add-Opens"), "java.base/java.lang");
192+
193+
for (Map.Entry<String, String> attribute : config.jar().manifest().attributes().entrySet()) {
194+
attributes.putValue(attribute.getKey(), attribute.getValue());
195+
}
196+
if (attributes.containsKey(Attributes.Name.CLASS_PATH)) {
197+
LOG.warn(
198+
"A CLASS_PATH entry was already defined in your MANIFEST.MF or using the property quarkus.package.jar.manifest.attributes.\"Class-Path\". Quarkus has overwritten this existing entry.");
199+
}
200+
attributes.put(Attributes.Name.CLASS_PATH, classPath);
201+
if (attributes.containsKey(Attributes.Name.MAIN_CLASS)) {
202+
String existingMainClass = attributes.getValue(Attributes.Name.MAIN_CLASS);
203+
if (!mainClassName.equals(existingMainClass)) {
204+
LOG.warn(
205+
"A MAIN_CLASS entry was already defined in your MANIFEST.MF or using the property quarkus.package.jar.manifest.attributes.\"Main-Class\". Quarkus has overwritten your existing entry.");
206+
}
207+
}
208+
attributes.put(Attributes.Name.MAIN_CLASS, mainClassName);
209+
if (config.jar().manifest().addImplementationEntries()
210+
&& !attributes.containsKey(Attributes.Name.IMPLEMENTATION_TITLE)) {
211+
String name = ApplicationInfoBuildItem.UNSET_VALUE.equals(applicationInfo.getName())
212+
? appArtifact.getArtifactId()
213+
: applicationInfo.getName();
214+
attributes.put(Attributes.Name.IMPLEMENTATION_TITLE, name);
215+
}
216+
if (config.jar().manifest().addImplementationEntries()
217+
&& !attributes.containsKey(Attributes.Name.IMPLEMENTATION_VERSION)) {
218+
String version = ApplicationInfoBuildItem.UNSET_VALUE.equals(applicationInfo.getVersion())
219+
? appArtifact.getVersion()
220+
: applicationInfo.getVersion();
221+
attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, version);
222+
}
223+
for (String sectionName : config.jar().manifest().sections().keySet()) {
224+
for (Map.Entry<String, String> entry : config.jar().manifest().sections().get(sectionName).entrySet()) {
225+
Attributes attribs = manifest.getEntries().computeIfAbsent(sectionName, k -> new Attributes());
226+
attribs.putValue(entry.getKey(), entry.getValue());
227+
}
228+
}
229+
230+
archiveCreator.addManifest(manifest);
231+
}
232+
233+
/**
234+
* Indicates whether the given dependency should be included or not.
235+
* <p>
236+
* A dependency should be included if it is a jar file and:
237+
* <p>
238+
* <ul>
239+
* <li>The dependency is not optional or</li>
240+
* <li>The dependency is part of the optional dependencies to include or</li>
241+
* <li>The optional dependencies to include are absent</li>
242+
* </ul>
243+
*
244+
* @param appDep the dependency to test.
245+
* @param optionalDependencies the optional dependencies to include into the final package.
246+
* @return {@code true} if the dependency should be included, {@code false} otherwise.
247+
*/
248+
protected static boolean includeAppDependency(ResolvedDependency appDep, Optional<Set<ArtifactKey>> optionalDependencies,
249+
Set<ArtifactKey> removedArtifacts) {
250+
if (!appDep.isJar()) {
251+
return false;
252+
}
253+
if (appDep.isOptional()) {
254+
return optionalDependencies.map(appArtifactKeys -> appArtifactKeys.contains(appDep.getKey()))
255+
.orElse(true);
256+
}
257+
if (removedArtifacts.contains(appDep.getKey())) {
258+
return false;
259+
}
260+
return true;
261+
}
262+
263+
protected static String suffixToClassifier(String suffix) {
264+
return suffix.startsWith("-") ? suffix.substring(1) : suffix;
265+
}
266+
267+
protected static String toUri(Path path) {
268+
if (path.isAbsolute()) {
269+
return path.toUri().getPath();
270+
}
271+
if (path.getNameCount() == 0) {
272+
return "";
273+
}
274+
return toUri(new StringBuilder(), path, 0).toString();
275+
}
276+
277+
private static StringBuilder toUri(StringBuilder b, Path path, int seg) {
278+
b.append(path.getName(seg));
279+
if (seg < path.getNameCount() - 1) {
280+
b.append('/');
281+
toUri(b, path, seg + 1);
282+
}
283+
return b;
284+
}
285+
}

0 commit comments

Comments
 (0)