From 14ab3390916009f76ce8d0c041fefb5b51e65fb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Sol=C3=B3rzano?= Date: Mon, 6 Jun 2022 14:02:17 +0200 Subject: [PATCH] [MJAR-275] - Fix outputTimestamp not applied to module-info; breaks reproducible builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jorge Solórzano --- pom.xml | 5 -- .../invoker.properties | 21 +++++ .../MJAR-275-reproducible-module-info/pom.xml | 77 +++++++++++++++++++ .../src/main/java/myproject/HelloWorld.java | 37 +++++++++ .../src/main/java9/module-info.java | 22 ++++++ .../verify.groovy | 65 ++++++++++++++++ .../maven/plugins/jar/AbstractJarMojo.java | 9 ++- 7 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 src/it/MJAR-275-reproducible-module-info/invoker.properties create mode 100644 src/it/MJAR-275-reproducible-module-info/pom.xml create mode 100644 src/it/MJAR-275-reproducible-module-info/src/main/java/myproject/HelloWorld.java create mode 100644 src/it/MJAR-275-reproducible-module-info/src/main/java9/module-info.java create mode 100644 src/it/MJAR-275-reproducible-module-info/verify.groovy diff --git a/pom.xml b/pom.xml index 86cb9cc..bcb14f5 100644 --- a/pom.xml +++ b/pom.xml @@ -121,11 +121,6 @@ maven-plugin-annotations provided - - org.codehaus.plexus - plexus-archiver - 4.3.0 - diff --git a/src/it/MJAR-275-reproducible-module-info/invoker.properties b/src/it/MJAR-275-reproducible-module-info/invoker.properties new file mode 100644 index 0000000..71eea45 --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/invoker.properties @@ -0,0 +1,21 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# NOTE: Requires Java 10+ to compile the module declaration for Java 9+, +# this is due that compiling the module declaration generates a +# module descriptor with the JDK version on it, making it unreproducible. +invoker.java.version = 10+ diff --git a/src/it/MJAR-275-reproducible-module-info/pom.xml b/src/it/MJAR-275-reproducible-module-info/pom.xml new file mode 100644 index 0000000..c25061c --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/pom.xml @@ -0,0 +1,77 @@ + + + + 4.0.0 + org.apache.maven.plugins + mjar-275-reproducible-multi-release-modular-jar + mjar-275-reproducible-multi-release-modular-jar + Verifies that the modular descriptor is reproducible (timestamp is set) + jar + 1.0-SNAPSHOT + + + UTF-8 + 2022-06-26T13:25:58Z + + + + + + org.apache.maven.plugins + maven-jar-plugin + @project.version@ + + + + myproject.HelloWorld + + + + + + + + + + maven-compiler-plugin + 3.10.1 + + 8 + + + + java9 + + compile + + + 9 + + ${project.basedir}/src/main/java9 + + true + + + + + + + + diff --git a/src/it/MJAR-275-reproducible-module-info/src/main/java/myproject/HelloWorld.java b/src/it/MJAR-275-reproducible-module-info/src/main/java/myproject/HelloWorld.java new file mode 100644 index 0000000..01f2991 --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/src/main/java/myproject/HelloWorld.java @@ -0,0 +1,37 @@ +package myproject; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * The classic Hello World App. + */ +public class HelloWorld +{ + + /** + * Main method. + * + * @param args Not used + */ + public static void main( String[] args ) + { + System.out.println( "Hi!" ); + } +} \ No newline at end of file diff --git a/src/it/MJAR-275-reproducible-module-info/src/main/java9/module-info.java b/src/it/MJAR-275-reproducible-module-info/src/main/java9/module-info.java new file mode 100644 index 0000000..fa45034 --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/src/main/java9/module-info.java @@ -0,0 +1,22 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module myproject { + exports myproject; +} diff --git a/src/it/MJAR-275-reproducible-module-info/verify.groovy b/src/it/MJAR-275-reproducible-module-info/verify.groovy new file mode 100644 index 0000000..f6bce52 --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/verify.groovy @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import java.io.*; +import java.lang.module.*; +import java.nio.file.attribute.FileTime; +import java.time.Instant; +import java.util.*; +import java.util.jar.*; + +File target = new File( basedir, "target" ) + +assert target.exists() +assert target.isDirectory() + +File artifact = new File( target, "mjar-275-reproducible-multi-release-modular-jar-1.0-SNAPSHOT.jar" ); + +assert artifact.exists() +assert artifact.isFile() + +JarFile jar = new JarFile( artifact ); + +Attributes manifest = jar.getManifest().getMainAttributes(); + +assert "myproject.HelloWorld".equals( manifest.get( Attributes.Name.MAIN_CLASS ) ) + +InputStream moduleDescriptorInputStream = jar.getInputStream( jar.getEntry( "META-INF/versions/9/module-info.class" ) ); +ModuleDescriptor moduleDescriptor = ModuleDescriptor.read( moduleDescriptorInputStream ); + +assert "myproject.HelloWorld".equals( moduleDescriptor.mainClass().orElse( null ) ) + +// Normalize to UTC +long normalizeUTC( String timestamp ) +{ + long millis = Instant.parse( timestamp ).toEpochMilli(); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis( millis ); + return millis - ( cal.get( Calendar.ZONE_OFFSET ) + cal.get( Calendar.DST_OFFSET ) ); +} + +// All entries should have the same timestamp +FileTime expectedTimestamp = FileTime.fromMillis( normalizeUTC( "2022-06-26T13:25:58Z" ) ); +Enumeration entries = jar.entries(); +while ( entries.hasMoreElements() ) +{ + assert expectedTimestamp.equals( entries.nextElement().getLastModifiedTime() ) +} + +jar.close(); diff --git a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java index 5b7ca64..588dad8 100644 --- a/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java +++ b/src/main/java/org/apache/maven/plugins/jar/AbstractJarMojo.java @@ -141,9 +141,10 @@ public abstract class AbstractJarMojo private boolean skipIfEmpty; /** - * Timestamp for reproducible output archive entries, either formatted as ISO 8601 - * yyyy-MM-dd'T'HH:mm:ssXXX or as an int representing seconds since the epoch (like - * SOURCE_DATE_EPOCH). + * 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). * * @since 3.2.0 */ @@ -257,7 +258,7 @@ public File createArchive() archiver.setOutputFile( jarFile ); // configure for Reproducible Builds based on outputTimestamp value - archiver.configureReproducible( outputTimestamp ); + archiver.configureReproducibleBuild( outputTimestamp ); archive.setForced( forceCreation );