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 3323c0824516a..bb61fbcea28ae 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 @@ -1290,7 +1290,13 @@ static void filterJarFile(Path resolvedDep, Path targetPath, Set transfo } else { manifest = new Manifest(); } - try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(targetPath), manifest)) { + try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(targetPath))) { + JarEntry manifestEntry = new JarEntry(JarFile.MANIFEST_NAME); + // Set manifest time to epoch to always make the same jar + manifestEntry.setTime(0); + out.putNextEntry(manifestEntry); + manifest.write(out); + out.closeEntry(); Enumeration entries = in.entries(); while (entries.hasMoreElements()) { JarEntry entry = entries.nextElement(); @@ -1306,6 +1312,8 @@ static void filterJarFile(Path resolvedDep, Path targetPath, Set transfo while ((r = inStream.read(buffer)) > 0) { out.write(buffer, 0, r); } + } finally { + out.closeEntry(); } } else { log.debugf("Removed %s from %s", entryName, resolvedDep); diff --git a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java index 9e50d306377ff..fc5180b34e9a7 100644 --- a/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java +++ b/core/deployment/src/test/java/io/quarkus/deployment/pkg/steps/JarResultBuildStepTest.java @@ -68,6 +68,26 @@ void should_unsign_jar_when_filtered(@TempDir Path tempDir) throws Exception { } } + @Test + void manifestTimeShouldAlwaysBeSetToEpoch(@TempDir Path tempDir) throws Exception { + JavaArchive archive = ShrinkWrap.create(JavaArchive.class, "myarchive.jar") + .addClasses(Integer.class) + .addManifest(); + Path initialJar = tempDir.resolve("initial.jar"); + Path filteredJar = tempDir.resolve("filtered.jar"); + archive.as(ZipExporter.class).exportTo(new File(initialJar.toUri()), true); + JarResultBuildStep.filterJarFile(initialJar, filteredJar, Set.of("java/lang/Integer.class")); + try (JarFile jarFile = new JarFile(filteredJar.toFile())) { + assertThat(jarFile.stream()) + .filteredOn(jarEntry -> jarEntry.getName().equals(JarFile.MANIFEST_NAME)) + .isNotEmpty() + .allMatch(jarEntry -> jarEntry.getTime() == 0); + // Check that the manifest is still has attributes + Manifest manifest = jarFile.getManifest(); + assertThat(manifest.getMainAttributes()).isNotEmpty(); + } + } + private static KeyStore.PrivateKeyEntry createPrivateKeyEntry() throws NoSuchAlgorithmException, CertificateException, OperatorCreationException, CertIOException { KeyPairGenerator ky = KeyPairGenerator.getInstance("RSA");