diff --git a/tycho-extras/tycho-custom-bundle-plugin/src/main/java/org/eclipse/tycho/extras/custombundle/CustomBundleMojo.java b/tycho-extras/tycho-custom-bundle-plugin/src/main/java/org/eclipse/tycho/extras/custombundle/CustomBundleMojo.java index ae75b4bdcc..557851ac9a 100644 --- a/tycho-extras/tycho-custom-bundle-plugin/src/main/java/org/eclipse/tycho/extras/custombundle/CustomBundleMojo.java +++ b/tycho-extras/tycho-custom-bundle-plugin/src/main/java/org/eclipse/tycho/extras/custombundle/CustomBundleMojo.java @@ -86,6 +86,16 @@ public class CustomBundleMojo extends AbstractMojo { @Parameter private MavenArchiveConfiguration archive = new MavenArchiveConfiguration(); + /** + * Timestamp for reproducible output archive entries, either formatted as ISO + * 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or + * with an offset '2019-10-05T20:37:42+06:00'), or as an int representing + * seconds since the epoch (like SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component(role = Archiver.class, hint = "jar") private JarArchiver jarArchiver; @@ -100,6 +110,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { archiver.setArchiver(jarArchiver); archiver.setOutputFile(outputJarFile); + // configure for Reproducible Builds based on outputTimestamp value + archiver.configureReproducibleBuild(outputTimestamp); + try { archiver.getArchiver().setManifest(updateManifest()); diff --git a/tycho-gpg-plugin/pom.xml b/tycho-gpg-plugin/pom.xml index 77b9423d05..91ad67a4f0 100644 --- a/tycho-gpg-plugin/pom.xml +++ b/tycho-gpg-plugin/pom.xml @@ -64,6 +64,11 @@ tycho-core ${project.version} + + + org.apache.maven + maven-archiver + diff --git a/tycho-gpg-plugin/src/main/java/org/eclipse/tycho/gpg/SignRepositoryArtifactsMojo.java b/tycho-gpg-plugin/src/main/java/org/eclipse/tycho/gpg/SignRepositoryArtifactsMojo.java index 0b521c7d12..f9d7851d59 100644 --- a/tycho-gpg-plugin/src/main/java/org/eclipse/tycho/gpg/SignRepositoryArtifactsMojo.java +++ b/tycho-gpg-plugin/src/main/java/org/eclipse/tycho/gpg/SignRepositoryArtifactsMojo.java @@ -11,9 +11,11 @@ import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; import java.util.Arrays; import java.util.List; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; @@ -137,6 +139,15 @@ enum PGPKeyBehavior { @Parameter private List forceSignature; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component(role = UnArchiver.class, hint = "zip") private ZipUnArchiver zipUnArchiver; @@ -208,6 +219,10 @@ public void doExecute() throws MojoExecutionException, MojoFailureException { } } + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> xzArchiver.configureReproducibleBuild(modifiedTime)); + xzArchiver.setDestFile(artifactsXmlXz); xzArchiver.addFile(artifactsXml, artifactsXml.getName()); xzArchiver.createArchive(); diff --git a/tycho-its/projects/reproducible-archive-timestamps/pom.xml b/tycho-its/projects/reproducible-archive-timestamps/pom.xml new file mode 100644 index 0000000000..258f4e8b20 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/pom.xml @@ -0,0 +1,93 @@ + + + 4.0.0 + + reproducible.archive.timestamps + reproducible.archive.timestamps.parent + 1.0.0 + pom + + + reproducible.bundle + reproducible.bundle.feature + reproducible.iu + reproducible.repository + + + + 2023-01-01T00:00:00Z + http://download.eclipse.org/releases/latest + + + + + repo + p2 + ${target-platform} + + + + + + + org.eclipse.tycho + tycho-maven-plugin + ${tycho-version} + true + + + + org.eclipse.tycho + target-platform-configuration + ${tycho-version} + + + + linux + gtk + x86 + + + + + + + org.eclipse.tycho + tycho-packaging-plugin + ${tycho-version} + + + + org.eclipse.tycho + tycho-source-plugin + ${tycho-version} + + + plugin-source + + plugin-source + feature-source + + + + + + + org.eclipse.tycho + tycho-p2-plugin + ${tycho-version} + + + attach-p2-metadata + package + + p2-metadata + + + + + + + + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/build.properties b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/build.properties new file mode 100644 index 0000000000..64f93a9f0b --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/build.properties @@ -0,0 +1 @@ +bin.includes = feature.xml diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/feature.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/feature.xml new file mode 100644 index 0000000000..c6ae9e0a46 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/feature.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/pom.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/pom.xml new file mode 100644 index 0000000000..c56f2de8d5 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle.feature/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + + reproducible.archive.timestamps + reproducible.archive.timestamps.parent + 1.0.0 + + + reproducible.bundle.feature + eclipse-feature + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/META-INF/MANIFEST.MF b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..103b8ed14a --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Reproducible-bundle +Bundle-SymbolicName: reproducible.bundle +Bundle-Version: 1.0.0 diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/build.properties b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/build.properties new file mode 100644 index 0000000000..34d2e4d2da --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/build.properties @@ -0,0 +1,4 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/META-INF/MANIFEST.MF b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..f3d666f1a8 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/META-INF/MANIFEST.MF @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Reproducible-bundle +Bundle-SymbolicName: reproducible.bundle.attached +Bundle-Version: 1.0.0 diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/META-INF/meta-test.txt b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/META-INF/meta-test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/test.txt b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/custom/test.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/pom.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/pom.xml new file mode 100644 index 0000000000..4adfcab7fb --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + reproducible.archive.timestamps + reproducible.archive.timestamps.parent + 1.0.0 + + + reproducible.bundle + eclipse-plugin + + + + + org.eclipse.tycho.extras + tycho-custom-bundle-plugin + ${tycho-version} + + + attached + package + + custom-bundle + + + ${project.basedir}/custom + attached + + + ${project.build.outputDirectory} + + **/*.class + + + + + + + + + + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/src/reproducible/bundle/PublicClass.java b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/src/reproducible/bundle/PublicClass.java new file mode 100644 index 0000000000..72e2ea4820 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.bundle/src/reproducible/bundle/PublicClass.java @@ -0,0 +1,6 @@ +package reproducible.bundle; + +public class PublicClass +{ + +} diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/p2iu.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/p2iu.xml new file mode 100644 index 0000000000..dd2b0d1151 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/p2iu.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + unzip(source:@artifact, target:${installFolder}); + + + cleanupzip(source:@artifact, target:${installFolder}); + + + + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/pom.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/pom.xml new file mode 100644 index 0000000000..bbeb688b88 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + + reproducible.archive.timestamps + reproducible.archive.timestamps.parent + 1.0.0 + + + reproducible.iu + p2-installable-unit + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/src/main/resources/myFile.txt b/tycho-its/projects/reproducible-archive-timestamps/reproducible.iu/src/main/resources/myFile.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.p2.inf b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.p2.inf new file mode 100644 index 0000000000..ca9789476c --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.p2.inf @@ -0,0 +1,3 @@ +requires.0.namespace=org.eclipse.equinox.p2.iu +requires.0.name=reproducible.iu +requires.0.range=[$version$,$version$] diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.product b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.product new file mode 100644 index 0000000000..ac537f33ba --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/main.product @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/pom.xml b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/pom.xml new file mode 100644 index 0000000000..cfcadb0953 --- /dev/null +++ b/tycho-its/projects/reproducible-archive-timestamps/reproducible.repository/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + + reproducible.archive.timestamps + reproducible.archive.timestamps.parent + 1.0.0 + + + reproducible.repository + eclipse-repository + + + + + org.eclipse.tycho + tycho-p2-repository-plugin + ${tycho-version} + + + assemble-maven-repository + verify + + assemble-maven-repository + + + + + + org.eclipse.tycho + tycho-p2-director-plugin + ${tycho-version} + + + materialize-products + + materialize-products + + + + archive-products + + archive-products + + + + + + zip + zip + zip + + + + + + + diff --git a/tycho-its/src/test/java/org/eclipse/tycho/test/reproducible/ReproducibleArchiveTimestampsTest.java b/tycho-its/src/test/java/org/eclipse/tycho/test/reproducible/ReproducibleArchiveTimestampsTest.java new file mode 100644 index 0000000000..e870382c64 --- /dev/null +++ b/tycho-its/src/test/java/org/eclipse/tycho/test/reproducible/ReproducibleArchiveTimestampsTest.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.tycho.test.reproducible; + +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.maven.it.Verifier; +import org.eclipse.tycho.test.AbstractTychoIntegrationTest; +import org.junit.Assert; +import org.junit.Test; + +public class ReproducibleArchiveTimestampsTest extends AbstractTychoIntegrationTest { + // The ZipEntry.getLastModifiedTime() method uses the default timezone to + // convert date and time fields to Instant, so we also use the default timezone + // for the expected timestamp here. + private static final String EXPECTED_TIMESTAMP_STRING = "2023-01-01T00:00:00"; + private static final Instant EXPECTED_TIMESTAMP_INSTANT = LocalDateTime.parse(EXPECTED_TIMESTAMP_STRING) + .toInstant(OffsetDateTime.now().getOffset()); + + /** + * Check that the timestamp of the files inside the produced archives is equal + * to the one specified in the "project.build.outputTimestamp" property of the + * pom file. + */ + @Test + public void test() throws Exception { + Verifier verifier = getVerifier("reproducible-archive-timestamps"); + verifier.executeGoals(List.of("clean", "verify")); + verifier.verifyErrorFreeLog(); + + // Check timestamps of files in archives + checkTimestamps(verifier.getBasedir() + "/reproducible.bundle/target/reproducible.bundle-1.0.0.jar"); + checkTimestamps(verifier.getBasedir() + "/reproducible.bundle/target/reproducible.bundle-1.0.0-attached.jar"); + checkTimestamps(verifier.getBasedir() + "/reproducible.bundle/target/reproducible.bundle-1.0.0-sources.jar"); + checkTimestamps( + verifier.getBasedir() + "/reproducible.bundle.feature/target/reproducible.bundle.feature-1.0.0.jar"); + checkTimestamps(verifier.getBasedir() + + "/reproducible.bundle.feature/target/reproducible.bundle.feature-1.0.0-sources-feature.jar"); + checkTimestamps(verifier.getBasedir() + "/reproducible.iu/target/reproducible.iu-1.0.0.zip"); + checkTimestamps(verifier.getBasedir() + "/reproducible.repository/target/reproducible.repository-1.0.0.zip"); + checkTimestamps(verifier.getBasedir() + "/reproducible.repository/target/p2-site.zip"); + } + + private void checkTimestamps(String file) throws IOException { + try (ZipFile zip = new ZipFile(file)) { + final var entries = zip.entries(); + while (entries.hasMoreElements()) { + final ZipEntry entry = entries.nextElement(); + Assert.assertEquals(EXPECTED_TIMESTAMP_INSTANT, entry.getLastModifiedTime().toInstant()); + } + } + } +} diff --git a/tycho-p2-director-plugin/pom.xml b/tycho-p2-director-plugin/pom.xml index 57be865ef5..1562f389e8 100644 --- a/tycho-p2-director-plugin/pom.xml +++ b/tycho-p2-director-plugin/pom.xml @@ -96,6 +96,11 @@ tycho-p2-plugin ${project.version} + + + org.apache.maven + maven-archiver + diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java index e6e2e16c10..8a77d56d1c 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/p2/director/ProductArchiverMojo.java @@ -16,12 +16,14 @@ import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; @@ -134,6 +136,15 @@ public final class ProductArchiverMojo extends AbstractProductMojo { @Parameter(defaultValue = "false") private boolean storeCreationTime; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private MavenProjectHelper helper; @@ -223,6 +234,9 @@ private void materialize(Product product, TargetEnvironment env) throws MojoExec createCommonsCompressTarGz(productArchive, sourceDir); } else { Archiver archiver = productArchiver.get(); + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> archiver.configureReproducibleBuild(modifiedTime)); archiver.setDestFile(productArchive); DefaultFileSet fileSet = new DefaultFileSet(sourceDir); fileSet.setUsingDefaultExcludes(false); @@ -241,6 +255,9 @@ private void createCommonsCompressTarGz(File productArchive, File sourceDir) thr TarGzArchiver archiver = new TarGzArchiver(); archiver.setStoreCreationTimeAttribute(storeCreationTime); archiver.setLog(getLog()); + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> archiver.configureReproducibleBuild(modifiedTime)); archiver.addDirectory(sourceDir); archiver.setDestFile(productArchive); archiver.createArchive(); diff --git a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/tar/TarGzArchiver.java b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/tar/TarGzArchiver.java index b8054f9616..97b0608fd2 100644 --- a/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/tar/TarGzArchiver.java +++ b/tycho-p2-director-plugin/src/main/java/org/eclipse/tycho/plugins/tar/TarGzArchiver.java @@ -22,6 +22,7 @@ import java.nio.file.Files; import java.nio.file.LinkOption; import java.nio.file.Path; +import java.nio.file.attribute.FileTime; import java.nio.file.attribute.PosixFileAttributeView; import java.nio.file.attribute.PosixFileAttributes; import java.util.ArrayList; @@ -53,6 +54,7 @@ public class TarGzArchiver { private List sourceDirs = new ArrayList<>(); private Log log = new SystemStreamLog(); private boolean storeCreationTime; + private FileTime outputTimestamp = null; public TarGzArchiver() { } @@ -69,6 +71,10 @@ public void setStoreCreationTimeAttribute(boolean storeCreationTime) { this.storeCreationTime = storeCreationTime; } + public void configureReproducibleBuild(FileTime timestamp) { + this.outputTimestamp = timestamp; + } + public void addDirectory(File directory) { this.sourceDirs.add(directory); } @@ -133,9 +139,15 @@ private TarArchiveEntry createTarEntry(File tarRootDir, File source) throws IOEx if (attrs != null) { tarEntry.setMode(FilePermissionHelper.toOctalFileMode(attrs.permissions())); } - tarEntry.setModTime(source.lastModified()); + if (outputTimestamp == null) { + tarEntry.setModTime(source.lastModified()); + } else { + tarEntry.setModTime(outputTimestamp); + } if (!storeCreationTime) { // GNU tar cannot handle 'LIBARCHIVE.creationtime' attributes and emits a lot of warnings on it tarEntry.setCreationTime(null); + } else if (outputTimestamp != null) { + tarEntry.setCreationTime(outputTimestamp); } return tarEntry; } diff --git a/tycho-p2-repository-plugin/pom.xml b/tycho-p2-repository-plugin/pom.xml index b75f097816..6096536301 100644 --- a/tycho-p2-repository-plugin/pom.xml +++ b/tycho-p2-repository-plugin/pom.xml @@ -60,6 +60,11 @@ tycho-core ${project.version} + + + org.apache.maven + maven-archiver + diff --git a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/ArchiveRepositoryMojo.java b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/ArchiveRepositoryMojo.java index 57b577d387..32cd03b63d 100644 --- a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/ArchiveRepositoryMojo.java +++ b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/ArchiveRepositoryMojo.java @@ -15,7 +15,9 @@ import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; @@ -52,6 +54,15 @@ public final class ArchiveRepositoryMojo extends AbstractRepositoryMojo { @Parameter(defaultValue = "false") private boolean skipArchive; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private FileLockService fileLockService; @@ -64,6 +75,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { File destFile = getBuildDirectory().getChild(finalName + ".zip"); try (var repoLock = fileLockService.lockVirtually(repositoryLocation); var destLock = fileLockService.lockVirtually(destFile);) { + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> inflater.configureReproducibleBuild(modifiedTime)); inflater.addFileSet(DefaultFileSet.fileSet(repositoryLocation).prefixed("")); inflater.setDestFile(destFile); inflater.createArchive(); diff --git a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/MavenP2SiteMojo.java b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/MavenP2SiteMojo.java index b5667bc55c..9e71945987 100644 --- a/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/MavenP2SiteMojo.java +++ b/tycho-p2-repository-plugin/src/main/java/org/eclipse/tycho/plugins/p2/repository/MavenP2SiteMojo.java @@ -22,6 +22,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -34,6 +35,7 @@ import java.util.stream.Stream.Builder; import org.apache.commons.io.FileUtils; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.artifact.resolver.ArtifactResolutionRequest; @@ -175,6 +177,15 @@ public class MavenP2SiteMojo extends AbstractMojo { @Parameter(defaultValue = "${project.build.directory}/repository") private File destination; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private Logger logger; @Component @@ -399,6 +410,9 @@ public void execute() throws MojoExecutionException, MojoFailureException { throw new MojoFailureException("P2 publisher return code was " + result); } ZipArchiver archiver = new ZipArchiver(); + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> archiver.configureReproducibleBuild(modifiedTime)); File destFile = new File(buildDirectory, "p2-site.zip"); archiver.setDestFile(destFile); archiver.addFileSet(new DefaultFileSet(destination)); diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java index 933b7f7768..98348a48c5 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageFeatureMojo.java @@ -105,6 +105,16 @@ public class PackageFeatureMojo extends AbstractTychoPackagingMojo { @Parameter(defaultValue = "${project.build.directory}/site") private File target; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO + * 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or + * with an offset '2019-10-05T20:37:42+06:00'), or as an int representing + * seconds since the epoch (like SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private FeatureXmlTransformer featureXmlTransformer; @@ -161,6 +171,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { MavenArchiver archiver = new MavenArchiver(); JarArchiver jarArchiver = getJarArchiver(); archiver.setArchiver(jarArchiver); + // configure for Reproducible Builds based on outputTimestamp value + archiver.configureReproducibleBuild(outputTimestamp); archiver.setOutputFile(outputJar); jarArchiver.setDestFile(outputJar); diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageIUMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageIUMojo.java index 9cd2d65d91..42d279f852 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageIUMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackageIUMojo.java @@ -14,7 +14,9 @@ import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugins.annotations.Component; @@ -50,6 +52,16 @@ public class PackageIUMojo extends AbstractTychoPackagingMojo { @Parameter(property = "project.basedir", required = true, readonly = true) private File basedir; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO + * 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or + * with an offset '2019-10-05T20:37:42+06:00'), or as an int representing + * seconds since the epoch (like SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private IUXmlTransformer iuTransformer; @@ -65,6 +77,11 @@ public void execute() throws MojoExecutionException, MojoFailureException { getLog().info("Skip packaging"); return; } + + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> zipArchiver.configureReproducibleBuild(modifiedTime)); + synchronized (LOCK) { outputDirectory.mkdirs(); diff --git a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java index a4ee87780d..269fc83e4d 100644 --- a/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java +++ b/tycho-packaging-plugin/src/main/java/org/eclipse/tycho/packaging/PackagePluginMojo.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.nio.file.attribute.FileTime; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -163,6 +164,16 @@ public class PackagePluginMojo extends AbstractTychoPackagingMojo { @Parameter(defaultValue = "true") private boolean checkServiceComponentFilesExist = true; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO + * 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or + * with an offset '2019-10-05T20:37:42+06:00'), or as an int representing + * seconds since the epoch (like SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component private SourceReferenceComputer soureReferenceComputer; @@ -211,6 +222,9 @@ private File makeJar(BuildOutputJar jar) throws MojoExecutionException { try { File jarFile = new File(project.getBasedir(), jarName); JarArchiver archiver = new JarArchiver(); + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> archiver.configureReproducibleBuild(modifiedTime)); archiver.setDestFile(jarFile); File outputDirectory = jar.getOutputDirectory(); if (!outputDirectory.mkdirs() && !outputDirectory.exists()) { @@ -238,6 +252,9 @@ private File createPluginJar() throws MojoExecutionException { MavenArchiver archiver = new MavenArchiver(); archiver.setArchiver(jarArchiver); + // configure for Reproducible Builds based on outputTimestamp value + archiver.configureReproducibleBuild(outputTimestamp); + File pluginFile = new File(buildDirectory, finalName + ".jar"); if (pluginFile.exists()) { pluginFile.delete(); diff --git a/tycho-repository-plugin/pom.xml b/tycho-repository-plugin/pom.xml index 59b6b177e2..3f2e1dd558 100644 --- a/tycho-repository-plugin/pom.xml +++ b/tycho-repository-plugin/pom.xml @@ -62,6 +62,11 @@ tycho-spi ${project.version} + + + org.apache.maven + maven-archiver + diff --git a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java index e80894f8ef..dde6df9824 100644 --- a/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java +++ b/tycho-repository-plugin/src/main/java/org/eclipse/tycho/repository/plugin/PackageRepositoryMojo.java @@ -14,11 +14,13 @@ import java.io.File; import java.io.IOException; +import java.nio.file.attribute.FileTime; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import org.apache.commons.io.FilenameUtils; +import org.apache.maven.archiver.MavenArchiver; import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.handler.DefaultArtifactHandler; import org.apache.maven.execution.MavenSession; @@ -98,6 +100,16 @@ public class PackageRepositoryMojo extends AbstractMojo implements RepositoryCon @Parameter private PlexusConfiguration settings; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO + * 8601 extended offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or + * with an offset '2019-10-05T20:37:42+06:00'), or as an int representing + * seconds since the epoch (like SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + @Component(role = Archiver.class, hint = "zip") private ZipArchiver zipArchiver; @@ -109,6 +121,10 @@ public class PackageRepositoryMojo extends AbstractMojo implements RepositoryCon @Override public void execute() throws MojoExecutionException, MojoFailureException { + // configure for Reproducible Builds based on outputTimestamp value + MavenArchiver.parseBuildOutputTimestamp(outputTimestamp).map(FileTime::from) + .ifPresent(modifiedTime -> zipArchiver.configureReproducibleBuild(modifiedTime)); + RepositoryGenerator generator = generators.get(repositoryType); if (generator == null) { throw new MojoFailureException( diff --git a/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/AbstractSourceJarMojo.java b/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/AbstractSourceJarMojo.java index 9e411efb15..9b46380169 100644 --- a/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/AbstractSourceJarMojo.java +++ b/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/AbstractSourceJarMojo.java @@ -194,6 +194,15 @@ public abstract class AbstractSourceJarMojo extends AbstractMojo { @Parameter(property = "source.forceCreation", defaultValue = "false") private boolean forceCreation; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + // ---------------------------------------------------------------------- // Public methods // ---------------------------------------------------------------------- @@ -349,6 +358,9 @@ protected MavenArchiver createArchiver() throws MojoExecutionException { MavenArchiver archiver = new MavenArchiver(); archiver.setArchiver(jarArchiver); + // configure for Reproducible Builds based on outputTimestamp value + archiver.configureReproducibleBuild(outputTimestamp); + if (project.getBuild() != null) { List resources = project.getBuild().getResources(); diff --git a/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/SourceFeatureMojo.java b/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/SourceFeatureMojo.java index 4717031d6a..c4c78a5bc8 100644 --- a/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/SourceFeatureMojo.java +++ b/tycho-source-plugin/src/main/java/org/eclipse/tycho/source/SourceFeatureMojo.java @@ -193,6 +193,15 @@ public enum MissingSourcesAction { @Parameter(property = "session", readonly = true) private MavenSession session; + /** + * Timestamp for reproducible output archive entries, either formatted as ISO 8601 extended + * offset date-time (e.g. in UTC such as '2011-12-03T10:15:30Z' or with an offset + * '2019-10-05T20:37:42+06:00'), or as an int representing seconds since the epoch (like + * SOURCE_DATE_EPOCH). + */ + @Parameter(defaultValue = "${project.build.outputTimestamp}") + private String outputTimestamp; + private final Set excludedPlugins = new HashSet<>(); private final Set excludedFeatures = new HashSet<>(); @@ -243,6 +252,8 @@ public void execute() throws MojoExecutionException, MojoFailureException { writeProperties(mergedSourceFeatureProps, getMergedSourceFeaturePropertiesFile()); MavenArchiver archiver = new MavenArchiver(); archiver.setArchiver(jarArchiver); + // configure for Reproducible Builds based on outputTimestamp value + archiver.configureReproducibleBuild(outputTimestamp); File outputJarFile = getOutputJarFile(); archiver.setOutputFile(outputJarFile); File template = new File(project.getBasedir(), FEATURE_TEMPLATE_DIR);