diff --git a/pom.xml b/pom.xml index 359c3e8..6a2f9af 100644 --- a/pom.xml +++ b/pom.xml @@ -76,7 +76,7 @@ 3.0.0 - 3.5.2 + 3.6.0 3.1.0 @@ -150,6 +150,11 @@ 3.4.2 runtime + + commons-io + commons-io + 2.11.0 + junit junit 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..5d1b8a8 --- /dev/null +++ b/src/it/MJAR-275-reproducible-module-info/verify.groovy @@ -0,0 +1,97 @@ +/* + * 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.*; + +boolean result = true; +JarFile jar; + +try +{ + File target = new File( basedir, "target" ); + if ( !target.exists() || !target.isDirectory() ) + { + System.err.println( "target file is missing or not a directory." ); + return false; + } + + File artifact = new File( target, "mjar-275-reproducible-multi-release-modular-jar-1.0-SNAPSHOT.jar" ); + if ( !artifact.exists() || !artifact.isFile() ) + { + System.err.println( "Could not find generated JAR: " + artifact ); + return false; + } + + jar = new JarFile( artifact ); + + Attributes manifest = jar.getManifest().getMainAttributes(); + + if ( !"myproject.HelloWorld".equals( manifest.get( Attributes.Name.MAIN_CLASS ) ) ) + { + System.err.println( "Manifest main class attribute not equals myproject.HelloWorld" ); + return false; + } + + InputStream moduleDescriptorInputStream = jar.getInputStream( jar.getEntry( "META-INF/versions/9/module-info.class" ) ); + ModuleDescriptor moduleDescriptor = ModuleDescriptor.read( moduleDescriptorInputStream ); + + if ( !"myproject.HelloWorld".equals( moduleDescriptor.mainClass().orElse( null ) ) ) + { + System.err.println( "Module descriptor main class not equals myproject.HelloWorld" ); + return false; + } + + // Normalize to UTC + long millis = Instant.parse( "2022-06-26T13:25:58Z" ).toEpochMilli(); + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis( millis ); + millis = millis - ( cal.get( Calendar.ZONE_OFFSET ) + cal.get( Calendar.DST_OFFSET ) ); + + FileTime timestamp = FileTime.fromMillis( millis ); + Enumeration entries = jar.entries(); + while ( entries.hasMoreElements() ) + { + JarEntry entry = entries.nextElement(); + FileTime lastModifiedTime = entry.getLastModifiedTime(); + if ( !timestamp.equals( lastModifiedTime ) ) + { + System.err.println( "The last modified time is not expected for the entry: " + entry ); + result = false; + } + } +} +catch( Throwable e ) +{ + e.printStackTrace(); + result = false; +} +finally +{ + if ( jar != null ) + { + jar.close(); + } +} + +return result; 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 );