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