diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/UberJarResourceMergedBuildItem.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/UberJarResourceMergedBuildItem.java new file mode 100644 index 00000000000000..7942f147c777f7 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/builditem/UberJarResourceMergedBuildItem.java @@ -0,0 +1,19 @@ +package io.quarkus.deployment.pkg.builditem; + +import io.quarkus.builder.item.MultiBuildItem; + +/** + * Merge duplicate resources from multiple JARs when building an Uber Jar + */ +public final class UberJarResourceMergedBuildItem extends MultiBuildItem { + + private final String path; + + public UberJarResourceMergedBuildItem(String path) { + this.path = path; + } + + public String getPath() { + return path; + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java index cba8cad2f3ce72..e09798ebea3c0d 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/JarResultBuildStep.java @@ -78,6 +78,7 @@ import io.quarkus.deployment.pkg.builditem.NativeImageSourceJarBuildItem; import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem; import io.quarkus.deployment.pkg.builditem.UberJarRequiredBuildItem; +import io.quarkus.deployment.pkg.builditem.UberJarResourceMergedBuildItem; import io.quarkus.deployment.util.FileUtil; /** @@ -135,21 +136,37 @@ public boolean test(String path) { }; private static final Logger log = Logger.getLogger(JarResultBuildStep.class); + private static final BiPredicate IS_JSON_FILE_PREDICATE = new IsJsonFilePredicate(); + public static final String DEPLOYMENT_CLASS_PATH_DAT = "deployment-class-path.dat"; + public static final String BUILD_SYSTEM_PROPERTIES = "build-system.properties"; + public static final String DEPLOYMENT_LIB = "deployment"; + public static final String APPMODEL_DAT = "appmodel.dat"; + public static final String QUARKUS_RUN_JAR = "quarkus-run.jar"; + public static final String QUARKUS_APP_DEPS = "quarkus-app-dependencies.txt"; + public static final String BOOT_LIB = "boot"; + public static final String LIB = "lib"; + public static final String MAIN = "main"; + public static final String GENERATED_BYTECODE_JAR = "generated-bytecode.jar"; + public static final String TRANSFORMED_BYTECODE_JAR = "transformed-bytecode.jar"; + public static final String APP = "app"; + public static final String QUARKUS = "quarkus"; + public static final String DEFAULT_FAST_JAR_DIRECTORY_NAME = "quarkus-app"; + public static final String MP_CONFIG_FILE = "META-INF/microprofile-config.properties"; @BuildStep @@ -188,6 +205,7 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem List generatedClasses, List generatedResources, List uberJarRequired, + List uberJarResourceMergedBuildItems, List legacyJarRequired, QuarkusBuildCloseablesBuildItem closeablesBuildItem, List additionalApplicationArchiveBuildItems, @@ -205,7 +223,8 @@ public JarBuildItem buildRunnerJar(CurateOutcomeBuildItem curateOutcomeBuildItem if (legacyJarRequired.isEmpty() && (!uberJarRequired.isEmpty() || packageConfig.type.equalsIgnoreCase(PackageConfig.UBER_JAR))) { return buildUberJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem, - packageConfig, applicationInfo, generatedClasses, generatedResources, mainClassBuildItem); + packageConfig, applicationInfo, generatedClasses, generatedResources, uberJarResourceMergedBuildItems, + mainClassBuildItem); } else if (!legacyJarRequired.isEmpty() || packageConfig.isLegacyJar() || packageConfig.type.equalsIgnoreCase(PackageConfig.LEGACY)) { return buildLegacyThinJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, @@ -252,6 +271,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem, ApplicationInfoBuildItem applicationInfo, List generatedClasses, List generatedResources, + List mergeResources, MainClassBuildItem mainClassBuildItem) throws Exception { //we use the -runner jar name, unless we are building both types @@ -267,6 +287,7 @@ private JarBuildItem buildUberJar(CurateOutcomeBuildItem curateOutcomeBuildItem, applicationInfo, generatedClasses, generatedResources, + mergeResources, mainClassBuildItem, runnerJar); @@ -292,6 +313,7 @@ private void buildUberJar0(CurateOutcomeBuildItem curateOutcomeBuildItem, ApplicationInfoBuildItem applicationInfo, List generatedClasses, List generatedResources, + List mergeResources, MainClassBuildItem mainClassBuildItem, Path runnerJar) throws Exception { try (FileSystem runnerZipFs = ZipUtils.newZip(runnerJar)) { @@ -301,6 +323,9 @@ private void buildUberJar0(CurateOutcomeBuildItem curateOutcomeBuildItem, final Map seen = new HashMap<>(); final Map> duplicateCatcher = new HashMap<>(); final Map> concatenatedEntries = new HashMap<>(); + final Set mergeResourcePaths = mergeResources.stream() + .map(UberJarResourceMergedBuildItem::getPath) + .collect(Collectors.toSet()); Set finalIgnoredEntries = new HashSet<>(IGNORED_ENTRIES); packageConfig.userConfiguredIgnoredEntries.ifPresent(finalIgnoredEntries::addAll); @@ -328,12 +353,13 @@ private void buildUberJar0(CurateOutcomeBuildItem curateOutcomeBuildItem, try (FileSystem artifactFs = ZipUtils.newFileSystem(resolvedDep)) { for (final Path root : artifactFs.getRootDirectories()) { walkFileDependencyForDependency(root, runnerZipFs, seen, duplicateCatcher, concatenatedEntries, - finalIgnoredEntries, appDep, transformedFromThisArchive); + finalIgnoredEntries, appDep, transformedFromThisArchive, mergeResourcePaths); } } } else { walkFileDependencyForDependency(resolvedDep, runnerZipFs, seen, duplicateCatcher, - concatenatedEntries, finalIgnoredEntries, appDep, transformedFromThisArchive); + concatenatedEntries, finalIgnoredEntries, appDep, transformedFromThisArchive, + mergeResourcePaths); } } } @@ -382,7 +408,8 @@ private static boolean includeAppDep(AppDependency appDep, Optional seen, Map> duplicateCatcher, Map> concatenatedEntries, - Set finalIgnoredEntries, AppDependency appDep, Set transformedFromThisArchive) throws IOException { + Set finalIgnoredEntries, AppDependency appDep, Set transformedFromThisArchive, + Set mergeResourcePaths) throws IOException { final Path metaInfDir = root.resolve("META-INF"); Files.walkFileTree(root, EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, new SimpleFileVisitor() { @@ -414,7 +441,8 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) boolean transformed = transformedFromThisArchive != null && transformedFromThisArchive.contains(relativePath); if (!transformed) { - if (CONCATENATED_ENTRIES_PREDICATE.test(relativePath)) { + if (CONCATENATED_ENTRIES_PREDICATE.test(relativePath) + || mergeResourcePaths.contains(relativePath)) { concatenatedEntries.computeIfAbsent(relativePath, (u) -> new ArrayList<>()) .add(Files.readAllBytes(file)); return FileVisitResult.CONTINUE; @@ -845,7 +873,6 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) /** * Native images are built from a specially created jar file. This allows for changes in how the jar file is generated. - * */ @BuildStep public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem curateOutcomeBuildItem, @@ -858,7 +885,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem List nativeImageResources, List generatedResources, MainClassBuildItem mainClassBuildItem, - List uberJarRequired) throws Exception { + List uberJarRequired, + List mergeResources) throws Exception { Path targetDirectory = outputTargetBuildItem.getOutputDirectory() .resolve(outputTargetBuildItem.getBaseName() + "-native-image-source-jar"); IoUtils.createOrEmptyDir(targetDirectory); @@ -877,7 +905,8 @@ public NativeImageSourceJarBuildItem buildNativeImageJar(CurateOutcomeBuildItem final NativeImageSourceJarBuildItem nativeImageSourceJarBuildItem = buildNativeImageUberJar(curateOutcomeBuildItem, outputTargetBuildItem, transformedClasses, applicationArchivesBuildItem, - packageConfig, applicationInfo, allClasses, generatedResources, mainClassBuildItem, targetDirectory); + packageConfig, applicationInfo, allClasses, generatedResources, mergeResources, mainClassBuildItem, + targetDirectory); // additionally copy any json config files to a location accessible by native-image tool during // native-image generation copyJsonConfigFiles(applicationArchivesBuildItem, targetDirectory); @@ -926,6 +955,7 @@ private NativeImageSourceJarBuildItem buildNativeImageUberJar(CurateOutcomeBuild ApplicationInfoBuildItem applicationInfo, List generatedClasses, List generatedResources, + List mergeResources, MainClassBuildItem mainClassBuildItem, Path targetDirectory) throws Exception { //we use the -runner jar name, unless we are building both types @@ -940,7 +970,7 @@ private NativeImageSourceJarBuildItem buildNativeImageUberJar(CurateOutcomeBuild applicationInfo, generatedClasses, generatedResources, - mainClassBuildItem, + mergeResources, mainClassBuildItem, runnerJar); return new NativeImageSourceJarBuildItem(runnerJar, null); @@ -1126,6 +1156,7 @@ private void copyCommonContent(FileSystem runnerZipFs, Map> for (Map.Entry> entry : concatenatedEntries.entrySet()) { try (final OutputStream os = wrapForJDK8232879( Files.newOutputStream(runnerZipFs.getPath(entry.getKey())))) { + // TODO: Handle merging of XMLs for (byte[] i : entry.getValue()) { os.write(i); os.write('\n');