Skip to content

Commit

Permalink
reproducible: Update -reproducible to specify the timestamp
Browse files Browse the repository at this point in the history
To properly support project.build.outputTimestamp for the new jar
goal in bnd-maven-plugin, we need to support allowing the user to
specify the desired timestamp value. So -reproducible can now
accept the same values that maven-jar-plugin supports for
project.build.outputTimestamp.

This required a small change to Builder so that getBuildJar() sets
the reproducible and compression values on the jar.

Signed-off-by: BJ Hargrave <bj@hargrave.dev>
  • Loading branch information
bjhargrave committed Feb 16, 2022
1 parent 4876531 commit 2e0dc2e
Show file tree
Hide file tree
Showing 17 changed files with 131 additions and 41 deletions.
1 change: 0 additions & 1 deletion biz.aQute.bndlib/src/aQute/bnd/build/ProjectBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,6 @@ protected void startBuild(Builder builder) throws Exception {
&& (builder.getProperty(Constants.INCLUDERESOURCE) == null) && project.getOutput()
.isDirectory()) {
Jar outputDirJar = new Jar(project.getName(), project.getOutput());
outputDirJar.setReproducible(is(REPRODUCIBLE));
outputDirJar.setManifest(new Manifest());
builder.setJar(outputDirJar);
}
Expand Down
11 changes: 6 additions & 5 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Builder.java
Original file line number Diff line number Diff line change
Expand Up @@ -189,13 +189,14 @@ private Jar getBuildJar() {
Jar dot = getJar();
if (dot == null) {
dot = new Jar("dot");

buildInstrs.compression()
.ifPresent(dot::setCompression);

dot.setReproducible(is(REPRODUCIBLE));
setJar(dot);
}

buildInstrs.compression()
.ifPresent(dot::setCompression);

dot.setReproducible(getProperty(REPRODUCIBLE));

try {
long modified = Long.parseLong(getProperty("base.modified"));
dot.updateModified(modified, "Base modified");
Expand Down
57 changes: 41 additions & 16 deletions biz.aQute.bndlib/src/aQute/bnd/osgi/Jar.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -62,6 +63,7 @@
import aQute.bnd.version.Version;
import aQute.lib.base64.Base64;
import aQute.lib.collections.Iterables;
import aQute.lib.date.Dates;
import aQute.lib.io.ByteBufferDataInput;
import aQute.lib.io.ByteBufferOutputStream;
import aQute.lib.io.IO;
Expand All @@ -86,10 +88,9 @@ public class Jar implements Closeable {
* Java 7 doesn't do that. So make sure that your seconds are even.
* Moreover, parsing happens via `new Date(millis)` in
* {@link java.util.zip.ZipUtils}#javaToDosTime() so we must use default
* timezone and locale. The date is 1980 February 1st CET.
* timezone and locale. The date is 1980-02-01T00:00:00Z.
*/
private static final long ZIP_ENTRY_CONSTANT_TIME = new GregorianCalendar(1980, Calendar.FEBRUARY, 1, 0, 0, 0)
.getTimeInMillis();
private static final long ZIP_ENTRY_CONSTANT_TIME = 318211200000L;

public enum Compression {
DEFLATE,
Expand Down Expand Up @@ -121,6 +122,7 @@ public enum Compression {
private SHA256 sha256;
private boolean calculateFileDigest;
private int fileLength = -1;
private long zipEntryConstantTime = ZIP_ENTRY_CONSTANT_TIME;

public Jar(String name) {
this.name = name;
Expand Down Expand Up @@ -699,11 +701,7 @@ private void doManifest(ZipOutputStream jout, Set<String> directories, String ma
check();
createDirectories(directories, jout, manifestName);
JarEntry ze = new JarEntry(manifestName);
if (isReproducible()) {
ze.setTime(ZIP_ENTRY_CONSTANT_TIME);
} else {
ZipUtil.setModifiedTime(ze, lastModified());
}
ZipUtil.setModifiedTime(ze, isReproducible() ? zipEntryConstantTime : lastModified());
Resource r = new WriteResource() {

@Override
Expand Down Expand Up @@ -941,7 +939,7 @@ private void writeResource(ZipOutputStream jout, Set<String> directories, String
ZipEntry ze = new ZipEntry(path);
ze.setMethod(ZipEntry.DEFLATED);
if (isReproducible()) {
ze.setTime(ZIP_ENTRY_CONSTANT_TIME);
ZipUtil.setModifiedTime(ze, zipEntryConstantTime);
} else {
long lastModified = resource.lastModified();
if (lastModified == 0L) {
Expand All @@ -967,11 +965,7 @@ void createDirectories(Set<String> directories, ZipOutputStream zip, String name
return;
createDirectories(directories, zip, path);
ZipEntry ze = new ZipEntry(path + '/');
if (isReproducible()) {
ze.setTime(ZIP_ENTRY_CONSTANT_TIME);
} else {
ZipUtil.setModifiedTime(ze, lastModified());
}
ZipUtil.setModifiedTime(ze, isReproducible() ? zipEntryConstantTime : lastModified());
if (compression == Compression.STORE) {
ze.setCrc(0L);
ze.setSize(0);
Expand Down Expand Up @@ -1230,8 +1224,39 @@ public boolean isReproducible() {
return reproducible;
}

public void setReproducible(String outputTimestamp) {
reproducible = Processor.isTrue(outputTimestamp);
if (!reproducible || Boolean.parseBoolean(outputTimestamp = outputTimestamp.trim())) {
zipEntryConstantTime = ZIP_ENTRY_CONSTANT_TIME;
return; // false or just plain "true"
}
// is it epoch seconds?
// https://reproducible-builds.org/docs/source-date-epoch/
try {
zipEntryConstantTime = Long.parseUnsignedLong(outputTimestamp) * 1000L;
return;
} catch (NumberFormatException e) {
// ignore
}
// is it a date?
try {
ZonedDateTime dateTime = Dates.toZonedDateTime(DateTimeFormatter.ISO_DATE_TIME.parse(outputTimestamp));
zipEntryConstantTime = dateTime.toInstant()
.toEpochMilli();
return;
} catch (DateTimeParseException e) {
// ignore
}
zipEntryConstantTime = ZIP_ENTRY_CONSTANT_TIME;
}

/**
* @deprecated Replaced by {@link #setReproducible(String)}.
*/
@Deprecated
public void setReproducible(boolean reproducible) {
this.reproducible = reproducible;
zipEntryConstantTime = ZIP_ENTRY_CONSTANT_TIME;
}

public void copy(Jar srce, String path, boolean overwrite) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ private Jar getSources(Tool tool, Processor context, String path, Map<String, St
}
jar.ensureManifest();
jar.setName(Archive.SOURCES_CLASSIFIER); // set jar name to classifier
jar.setReproducible(context.is(Constants.REPRODUCIBLE));
jar.setReproducible(context.getProperty(Constants.REPRODUCIBLE));
tool.addClose(jar);
return jar;
}
Expand All @@ -346,7 +346,7 @@ private Jar getJavadoc(Tool tool, Processor context, String path, Map<String, St
}
jar.ensureManifest();
jar.setName(Archive.JAVADOC_CLASSIFIER); // set jar name to classifier
jar.setReproducible(context.is(Constants.REPRODUCIBLE));
jar.setReproducible(context.getProperty(Constants.REPRODUCIBLE));
tool.addClose(jar);
return jar;
}
Expand Down
9 changes: 7 additions & 2 deletions docs/_instructions/reproducible.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
---
layout: default
class: Builder
title: -reproducible BOOLEAN
title: -reproducible BOOLEAN | TIMESTAMP
summary: Ensure the bundle can be built in a reproducible manner.
---

To ensure the bundle can be built in a reproducible manner, the timestamp of the zip entries is set to the fixed time `1980-02-01 00:00Z`. The `Bnd-LastModified` header is also omitted from the manifest. The default value is `false`.
To ensure the bundle can be built in a reproducible manner, the timestamp of the zip entries is set to the fixed time `1980-02-01T00:00:00Z` when the value of this instruction is `true`.
The value can also be set to either an ISO-8601 formatted time or the number of seconds since the epoch which is used as the timestamp for the zip entries.
The `Bnd-LastModified` header is also omitted from the manifest.
The default value is `false`.

For example:

-reproducible: true
-reproducible: 1641127394
-reproducible: 2022-01-02T12:43:14Z
Original file line number Diff line number Diff line change
Expand Up @@ -412,8 +412,22 @@ public void execute(Task t) {
IO.copy(archiveFile, archiveCopyFile);
Jar bundleJar = new Jar(archiveFileName, archiveCopyFile);
String reproducible = builder.getProperty(Constants.REPRODUCIBLE);
bundleJar.setReproducible(Objects.nonNull(reproducible) ? Processor.isTrue(reproducible)
: !getTask().isPreserveFileTimestamps());
if (Objects.isNull(reproducible)) {
if (!getTask().isPreserveFileTimestamps()) {
builder.setProperty(Constants.REPRODUCIBLE, Boolean.TRUE.toString());
}
}
String compression = builder.getProperty(Constants.COMPRESSION);
if (Objects.isNull(compression)) {
switch (getTask().getEntryCompression()) {
case STORED :
builder.setProperty(Constants.COMPRESSION, Jar.Compression.STORE.name());
break;
case DEFLATED :
// default
break;
}
}
bundleJar.updateModified(archiveFile.lastModified(), "time of Jar task generated jar");
bundleJar.setManifest(new Manifest());
builder.setJar(bundleJar);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aQute.bnd.gradle

import static aQute.bnd.gradle.TestHelper.formatTime
import static org.gradle.testkit.runner.TaskOutcome.*

import java.util.jar.Attributes
Expand Down Expand Up @@ -65,6 +66,7 @@ class TestBundlePlugin extends Specification {
jartask_manifest.getValue("Gradle-Missing-Prop") == "\${task.projectprop}"
jartask_manifest.getValue("Here") == testProjectDir.absolutePath.replace(File.separatorChar, (char)'/')
jartask_jar.getEntry("doubler/Doubler.class")
formatTime(jartask_jar.getEntry("doubler/Doubler.class")) == "2022-01-02T12:43:14Z"
jartask_jar.getEntry("doubler/packageinfo")
jartask_jar.getEntry("doubler/impl/DoublerImpl.class")
jartask_jar.getEntry("doubler/impl/packageinfo")
Expand Down Expand Up @@ -100,6 +102,7 @@ class TestBundlePlugin extends Specification {
!bundletask_jar.getEntry("doubler/Doubler.class")
!bundletask_jar.getEntry("doubler/impl/DoublerImpl.class")
bundletask_jar.getEntry("doubler/impl/DoublerImplTest.class")
formatTime(bundletask_jar.getEntry("doubler/impl/DoublerImplTest.class")) != "2022-01-02T12:43:14Z"
bundletask_jar.getEntry("OSGI-OPT/src/")
!bundletask_jar.getEntry("foo.txt")
!bundletask_jar.getEntry("bar.txt")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
package aQute.bnd.gradle

import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
import java.util.zip.ZipEntry

import org.gradle.api.JavaVersion
import org.gradle.testkit.runner.GradleRunner

import aQute.bnd.version.MavenVersion

class TestHelper {
private final static TimeZone DEFAULT_TIME_ZONE = TimeZone.getDefault();
private final static ZoneId UTC_ZONE_ID = ZoneId.of("UTC");

private TestHelper() { }

Expand Down Expand Up @@ -39,4 +47,11 @@ class TestHelper {
}
return "6.7"
}

public static String formatTime(ZipEntry entry) {
long entryTime = entry.getTime()
long offsetTime = entryTime + DEFAULT_TIME_ZONE.getOffset(entryTime)
ZonedDateTime timestamp = ZonedDateTime.ofInstant(Instant.ofEpochMilli(offsetTime), UTC_ZONE_ID)
return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(timestamp)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package aQute.bnd.gradle

import static aQute.bnd.gradle.TestHelper.formatTime
import static org.gradle.testkit.runner.TaskOutcome.*

import java.util.jar.Attributes
Expand Down Expand Up @@ -48,10 +49,12 @@ class TestIndexTask extends Specification {

jartask_manifest.getValue("Bundle-SymbolicName") == "${testProject}"
jartask_manifest.getValue("Bundle-Version") == "1.0.0"
formatTime(jartask_jar.getEntry("doubler/Doubler.class")) == "2022-01-02T12:43:14Z"
jartask_jar.close()

bundletask_manifest.getValue("Bundle-SymbolicName") == "${testProject}_bundle"
bundletask_manifest.getValue("Bundle-Version") == "1.1.0"
formatTime(bundletask_jar.getEntry("doubler/impl/DoublerImplTest.class")) != "2022-01-02T12:43:14Z"
bundletask_jar.close()

when:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ Gradle-Task-Prop: ${task.taskprop}
Gradle-Task-Project-Prop: ${task.project.projectprop}
Gradle-Missing-Prop: ${task.projectprop}
Here: ${.}
-reproducible: true
-reproducible: 2022-01-02T12:43:14Z
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ Gradle-Task-Prop: ${task.taskprop}
Gradle-Task-Project-Prop: ${task.project.projectprop}
Gradle-Missing-Prop: ${task.projectprop}
Here: ${.}
-reproducible: true
-reproducible: 1641127394
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ public abstract class AbstractBndMavenPlugin extends AbstractMojo {
static final String PACKAGING_WAR = "war";
static final String TSTAMP = "${tstamp}";
static final String SNAPSHOT = "SNAPSHOT";
static final String OUTPUT_TIMESTAMP = "project.build.outputTimestamp";

@Parameter(defaultValue = "${project.build.directory}", readonly = true)
File targetDir;
Expand All @@ -114,6 +113,9 @@ public abstract class AbstractBndMavenPlugin extends AbstractMojo {
@Parameter(property = "bnd.skipIfEmpty", defaultValue = "false")
boolean skipIfEmpty;

@Parameter(defaultValue = "${project.build.outputTimestamp}")
private String outputTimestamp;

/**
* File path to a bnd file containing bnd instructions for this project.
* Defaults to {@code bnd.bnd}. The file path can be an absolute or relative
Expand Down Expand Up @@ -327,7 +329,16 @@ public void execute() throws MojoExecutionException, MojoFailureException {
processBuilder(builder);

// https://maven.apache.org/guides/mini/guide-reproducible-builds.html
boolean isReproducible = projectProperties.getProperty(OUTPUT_TIMESTAMP) != null;
boolean isReproducible = Strings.nonNullOrEmpty(outputTimestamp)
// no timestamp configured (1 character configuration is useful
// to override a full value during pom inheritance)
&& ((outputTimestamp.length() > 1) || Character.isDigit(outputTimestamp.charAt(0)));
if (isReproducible) {
builder.setProperty(Constants.REPRODUCIBLE, outputTimestamp);
if (builder.getProperty(Constants.NOEXTRAHEADERS) == null) {
builder.setProperty(Constants.NOEXTRAHEADERS, Boolean.TRUE.toString());
}
}

// Set Bundle-SymbolicName
if (builder.getProperty(Constants.BUNDLE_SYMBOLICNAME) == null) {
Expand Down Expand Up @@ -470,12 +481,6 @@ public void execute() throws MojoExecutionException, MojoFailureException {
}
}

if (isReproducible) {
if (builder.getProperty(Constants.NOEXTRAHEADERS) == null) {
builder.setProperty(Constants.NOEXTRAHEADERS, "true");
}
}

logger.debug("builder properties: {}", builder.getProperties());
logger.debug("builder delta: {}", delta);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<packaging>pom</packaging>

<properties>
<project.build.outputTimestamp>1980-02-01T00:00:00Z</project.build.outputTimestamp>
<project.build.outputTimestamp>2022-01-02T12:43:14Z</project.build.outputTimestamp>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<someParentVar>parentValue</someParentVar>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import java.time.*;
import java.time.format.*;
import java.util.jar.*;

def bsn = 'jar-test-impl-bundle'
Expand All @@ -10,9 +12,14 @@ assert impl_bundle.isFile()

JarFile jar = new JarFile(impl_bundle)

assert jar.getEntry("META-INF/maven/biz.aQute.bnd-test/${bsn}/pom.xml") != null
JarEntry pomXml = jar.getJarEntry("META-INF/maven/biz.aQute.bnd-test/${bsn}/pom.xml")
assert pomXml != null
assert jar.getEntry("META-INF/maven/biz.aQute.bnd-test/${bsn}/pom.properties") != null

long time = pomXml.getTime();
String outputTimestamp = DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.ofInstant(Instant.ofEpochMilli(time + TimeZone.getDefault().getOffset(time)),ZoneId.of("UTC")));
assert outputTimestamp == "2022-01-02T12:43:14Z"

def groupId = 'biz.aQute.bnd-test'

def checkMavenPom(JarFile jar, String entry, String groupId, String artifactId, String version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ Bundle-SymbolicName: test.wrapper.bundle
X-ParentProjectProperty: overridden
-include: other.bnd
-includeresource: lib/osgi.annotation.jar=osgi.annotation-*.jar; lib:=true
-snapshot: SNAPSHOT
-noextraheaders: true
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<artifactId>jar-test-wrapper-bundle</artifactId>
<version>0.0.1-BUILD-SNAPSHOT</version>

<properties>
<project.build.outputTimestamp>-</project.build.outputTimestamp>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
Expand Down
Loading

0 comments on commit 2e0dc2e

Please sign in to comment.