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);