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/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 ef3af7e585..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
@@ -255,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;
}