Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions core/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
</dependency>
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
Expand Down Expand Up @@ -215,8 +219,6 @@
<exclude>com.google.code.findbugs:jsr305</exclude>
<!-- com.google.guava:listenablefuture is empty and the ListenableFuture class is available in Guava -->
<exclude>com.google.guava:listenablefuture</exclude>
<!-- let's avoid having commons-io crawling into quarkus-core -->
<exclude>commons-io:commons-io</exclude>
Comment on lines -218 to -219
Copy link
Member Author

Choose a reason for hiding this comment

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

I will ban it through ForbiddenAPIs instead but we have some unrelated code depending on it so I need to clean up this code before doing it.

Will open a follow-up PR once this one is in.

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, this is fine, we already have a local forbidden-apis rule for core/deployment banning usage of Commons IO.

We are using it in a lot of test utils everywhere so it's hard to fully get rid of it. We could get rid of most of them but the IOUtils dependencies to get the content of a URL are handy.

</excludes>
</bannedDependencies>
</rules>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package io.quarkus.deployment.cmd;

import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEFAULT_FAST_JAR_DIRECTORY_NAME;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS_RUN_JAR;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEFAULT_FAST_JAR_DIRECTORY_NAME;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS_RUN_JAR;

import java.io.File;
import java.nio.file.Path;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import io.quarkus.deployment.dev.remote.RemoteDevClient;
import io.quarkus.deployment.dev.remote.RemoteDevClientProvider;
import io.quarkus.deployment.mutability.DevModeTask;
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;
import io.quarkus.deployment.pkg.jar.FastJarFormat;
import io.quarkus.deployment.steps.ClassTransformingBuildStep;
import io.quarkus.dev.spi.DeploymentFailedStartHandler;
import io.quarkus.dev.spi.DevModeType;
Expand Down Expand Up @@ -330,7 +330,7 @@ public Throwable getProblem() {
}

static Map<String, String> createHashes(Path appRoot) throws IOException {
Path quarkus = appRoot.resolve(JarResultBuildStep.QUARKUS); //we filter this jar, it has no relevance for remote dev
Path quarkus = appRoot.resolve(FastJarFormat.QUARKUS); //we filter this jar, it has no relevance for remote dev
Map<String, String> hashes = new HashMap<>();
Files.walkFileTree(appRoot, new FileVisitor<Path>() {
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.quarkus.deployment.mutability;

import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.APPMODEL_DAT;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.BUILD_SYSTEM_PROPERTIES;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEPLOYMENT_LIB;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.LIB;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS;

import java.io.Closeable;
import java.io.IOException;
Expand All @@ -28,7 +29,6 @@
import io.quarkus.bootstrap.util.IoUtils;
import io.quarkus.deployment.dev.DevModeContext;
import io.quarkus.deployment.dev.IsolatedDevModeMain;
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;
import io.quarkus.dev.spi.DevModeType;
import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.ResolvedArtifactDependency;
Expand All @@ -40,7 +40,7 @@ public class DevModeTask {
public static Closeable main(Path appRoot) throws Exception {

try (ObjectInputStream in = new ObjectInputStream(
Files.newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(JarResultBuildStep.APPMODEL_DAT)))) {
Files.newInputStream(appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB).resolve(APPMODEL_DAT)))) {
Properties buildSystemProperties = new Properties();
try (InputStream buildIn = Files
.newInputStream(appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES))) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package io.quarkus.deployment.mutability;

import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.BUILD_SYSTEM_PROPERTIES;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.DEPLOYMENT_LIB;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.LIB;
import static io.quarkus.deployment.pkg.steps.JarResultBuildStep.QUARKUS;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.APPMODEL_DAT;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.BUILD_SYSTEM_PROPERTIES;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.DEPLOYMENT_LIB;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.LIB;
import static io.quarkus.deployment.pkg.jar.FastJarFormat.QUARKUS;

import java.io.InputStream;
import java.io.ObjectInputStream;
Expand All @@ -20,7 +21,6 @@
import io.quarkus.bootstrap.app.QuarkusBootstrap;
import io.quarkus.bootstrap.model.ApplicationModel;
import io.quarkus.bootstrap.model.MutableJarApplicationModel;
import io.quarkus.deployment.pkg.steps.JarResultBuildStep;

public class ReaugmentTask {

Expand All @@ -29,7 +29,7 @@ public static void main(Path appRoot) throws Exception {
Path deploymentLib = appRoot.resolve(LIB).resolve(DEPLOYMENT_LIB);
Path buildSystemProps = appRoot.resolve(QUARKUS).resolve(BUILD_SYSTEM_PROPERTIES);
try (ObjectInputStream in = new ObjectInputStream(
Files.newInputStream(deploymentLib.resolve(JarResultBuildStep.APPMODEL_DAT)))) {
Files.newInputStream(deploymentLib.resolve(APPMODEL_DAT)))) {
Properties buildSystemProperties = new Properties();
try (InputStream buildIn = Files
.newInputStream(buildSystemProps)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
package io.quarkus.deployment.pkg.jar;

import static io.quarkus.commons.classloading.ClassLoaderHelper.fromClassNameToResourceName;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.jar.Attributes;
import java.util.jar.Manifest;

import org.jboss.logging.Logger;

import io.quarkus.builder.item.BuildItem;
import io.quarkus.deployment.ApplicationArchive;
import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem;
import io.quarkus.deployment.builditem.ApplicationInfoBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.deployment.builditem.GeneratedResourceBuildItem;
import io.quarkus.deployment.builditem.MainClassBuildItem;
import io.quarkus.deployment.builditem.TransformedClassesBuildItem;
import io.quarkus.deployment.pkg.PackageConfig;
import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem;
import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
import io.quarkus.maven.dependency.ArtifactKey;
import io.quarkus.maven.dependency.ResolvedDependency;
import io.quarkus.paths.PathVisit;
import io.quarkus.paths.PathVisitor;

public abstract class AbstractJarBuilder<T extends BuildItem> implements JarBuilder<T> {

private static final Logger LOG = Logger.getLogger(AbstractJarBuilder.class);

protected final CurateOutcomeBuildItem curateOutcome;
protected final OutputTargetBuildItem outputTarget;
protected final ApplicationInfoBuildItem applicationInfo;
protected final PackageConfig packageConfig;
protected final MainClassBuildItem mainClass;
protected final ApplicationArchivesBuildItem applicationArchives;
protected final TransformedClassesBuildItem transformedClasses;
protected final List<GeneratedClassBuildItem> generatedClasses;
protected final List<GeneratedResourceBuildItem> generatedResources;
protected final Set<ArtifactKey> removedArtifactKeys;

public AbstractJarBuilder(CurateOutcomeBuildItem curateOutcome,
OutputTargetBuildItem outputTarget,
ApplicationInfoBuildItem applicationInfo,
PackageConfig packageConfig,
MainClassBuildItem mainClass,
ApplicationArchivesBuildItem applicationArchives,
TransformedClassesBuildItem transformedClasses,
List<GeneratedClassBuildItem> generatedClasses,
List<GeneratedResourceBuildItem> generatedResources,
Set<ArtifactKey> removedArtifactKeys) {
this.curateOutcome = curateOutcome;
this.outputTarget = outputTarget;
this.applicationInfo = applicationInfo;
this.packageConfig = packageConfig;
this.mainClass = mainClass;
this.applicationArchives = applicationArchives;
this.transformedClasses = transformedClasses;
this.generatedClasses = generatedClasses;
this.generatedResources = generatedResources;
this.removedArtifactKeys = removedArtifactKeys;
}

protected static ArchiveCreator newArchiveCreator(Path archivePath, PackageConfig config) throws IOException {
return new ZipFileSystemArchiveCreator(archivePath, config.jar().compress());
}

/**
* Copy files from {@code archive} to {@code fs}, filtering out service providers into the given map.
*
* @param archive the root application archive
* @param archiveCreator the archive creator
* @param services the services map
* @throws IOException if an error occurs
*/
protected static void copyFiles(ApplicationArchive archive, ArchiveCreator archiveCreator,
Map<String, List<byte[]>> services,
Predicate<String> ignoredEntriesPredicate) throws IOException {
try {
archive.accept(tree -> {
tree.walk(new PathVisitor() {
@Override
public void visitPath(PathVisit visit) {
final Path file = visit.getRoot().relativize(visit.getPath());
final String relativePath = toUri(file);
if (relativePath.isEmpty() || ignoredEntriesPredicate.test(relativePath)) {
return;
}
try {
if (Files.isDirectory(visit.getPath())) {
archiveCreator.addDirectory(relativePath);
} else {
if (relativePath.startsWith("META-INF/services/") && relativePath.length() > 18
&& services != null) {
final byte[] content;
try {
content = Files.readAllBytes(visit.getPath());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
services.computeIfAbsent(relativePath, (u) -> new ArrayList<>()).add(content);
} else if (!relativePath.equals("META-INF/INDEX.LIST")) {
//TODO: auto generate INDEX.LIST
//this may have implications for Camel though, as they change the layout
//also this is only really relevant for the thin jar layout
archiveCreator.addFileIfNotExists(visit.getPath(), relativePath);
}
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
});
});
} catch (RuntimeException re) {
final Throwable cause = re.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw re;
}
}

protected void copyCommonContent(ArchiveCreator archiveCreator,
Map<String, List<byte[]>> concatenatedEntries,
Predicate<String> ignoredEntriesPredicate)
throws IOException {

//TODO: this is probably broken in gradle
// if (Files.exists(augmentOutcome.getConfigDir())) {
// copyFiles(augmentOutcome.getConfigDir(), runnerZipFs, services);
// }
for (Set<TransformedClassesBuildItem.TransformedClass> transformed : transformedClasses
.getTransformedClassesByJar().values()) {
for (TransformedClassesBuildItem.TransformedClass i : transformed) {
if (i.getData() != null) {
archiveCreator.addFile(i.getData(), i.getFileName());
}
}
}
for (GeneratedClassBuildItem i : generatedClasses) {
String fileName = fromClassNameToResourceName(i.internalName());
archiveCreator.addFileIfNotExists(i.getClassData(), fileName, ArchiveCreator.CURRENT_APPLICATION);
}

for (GeneratedResourceBuildItem i : generatedResources) {
if (ignoredEntriesPredicate.test(i.getName())) {
continue;
}
if (i.getName().startsWith("META-INF/services/")) {
concatenatedEntries.computeIfAbsent(i.getName(), (u) -> new ArrayList<>()).add(i.getData());
continue;
}
archiveCreator.addFileIfNotExists(i.getData(), i.getName(), ArchiveCreator.CURRENT_APPLICATION);
}

copyFiles(applicationArchives.getRootArchive(), archiveCreator, concatenatedEntries, ignoredEntriesPredicate);

for (Map.Entry<String, List<byte[]>> entry : concatenatedEntries.entrySet()) {
archiveCreator.addFile(entry.getValue(), entry.getKey());
}
}

/**
* Manifest generation is quite simple : we just have to push some attributes in manifest.
* However, it gets a little more complex if the manifest preexists.
* So we first try to see if a manifest exists, and otherwise create a new one.
*
* <b>BEWARE</b> this method should be invoked after file copy from target/classes and so on.
* Otherwise, this manifest manipulation will be useless.
*/
protected static void generateManifest(ArchiveCreator archiveCreator, final String classPath, PackageConfig config,
ResolvedDependency appArtifact,
String mainClassName,
ApplicationInfoBuildItem applicationInfo)
throws IOException {
final Manifest manifest = new Manifest();

Attributes attributes = manifest.getMainAttributes();
attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
// JDK 24+ needs --add-opens=java.base/java.lang=ALL-UNNAMED for org.jboss.JDKSpecific.ThreadAccess.clearThreadLocals()
attributes.put(new Attributes.Name("Add-Opens"), "java.base/java.lang");

for (Map.Entry<String, String> attribute : config.jar().manifest().attributes().entrySet()) {
attributes.putValue(attribute.getKey(), attribute.getValue());
}
if (attributes.containsKey(Attributes.Name.CLASS_PATH)) {
LOG.warn(
"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.");
}
attributes.put(Attributes.Name.CLASS_PATH, classPath);
if (attributes.containsKey(Attributes.Name.MAIN_CLASS)) {
String existingMainClass = attributes.getValue(Attributes.Name.MAIN_CLASS);
if (!mainClassName.equals(existingMainClass)) {
LOG.warn(
"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.");
}
}
attributes.put(Attributes.Name.MAIN_CLASS, mainClassName);
if (config.jar().manifest().addImplementationEntries()
&& !attributes.containsKey(Attributes.Name.IMPLEMENTATION_TITLE)) {
String name = ApplicationInfoBuildItem.UNSET_VALUE.equals(applicationInfo.getName())
? appArtifact.getArtifactId()
: applicationInfo.getName();
attributes.put(Attributes.Name.IMPLEMENTATION_TITLE, name);
}
if (config.jar().manifest().addImplementationEntries()
&& !attributes.containsKey(Attributes.Name.IMPLEMENTATION_VERSION)) {
String version = ApplicationInfoBuildItem.UNSET_VALUE.equals(applicationInfo.getVersion())
? appArtifact.getVersion()
: applicationInfo.getVersion();
attributes.put(Attributes.Name.IMPLEMENTATION_VERSION, version);
}
for (String sectionName : config.jar().manifest().sections().keySet()) {
for (Map.Entry<String, String> entry : config.jar().manifest().sections().get(sectionName).entrySet()) {
Attributes attribs = manifest.getEntries().computeIfAbsent(sectionName, k -> new Attributes());
attribs.putValue(entry.getKey(), entry.getValue());
}
}

archiveCreator.addManifest(manifest);
}

/**
* Indicates whether the given dependency should be included or not.
* <p>
* A dependency should be included if it is a jar file and:
* <p>
* <ul>
* <li>The dependency is not optional or</li>
* <li>The dependency is part of the optional dependencies to include or</li>
* <li>The optional dependencies to include are absent</li>
* </ul>
*
* @param appDep the dependency to test.
* @param optionalDependencies the optional dependencies to include into the final package.
* @return {@code true} if the dependency should be included, {@code false} otherwise.
*/
protected static boolean includeAppDependency(ResolvedDependency appDep, Optional<Set<ArtifactKey>> optionalDependencies,
Set<ArtifactKey> removedArtifacts) {
if (!appDep.isJar()) {
return false;
}
if (appDep.isOptional()) {
return optionalDependencies.map(appArtifactKeys -> appArtifactKeys.contains(appDep.getKey()))
.orElse(true);
}
if (removedArtifacts.contains(appDep.getKey())) {
return false;
}
return true;
}

protected static String suffixToClassifier(String suffix) {
return suffix.startsWith("-") ? suffix.substring(1) : suffix;
}

protected static String toUri(Path path) {
if (path.isAbsolute()) {
return path.toUri().getPath();
}
if (path.getNameCount() == 0) {
return "";
}
return toUri(new StringBuilder(), path, 0).toString();
}

private static StringBuilder toUri(StringBuilder b, Path path, int seg) {
b.append(path.getName(seg));
if (seg < path.getNameCount() - 1) {
b.append('/');
toUri(b, path, seg + 1);
}
return b;
}
}
Loading
Loading