diff --git a/.github/workflows/default-tests.yml b/.github/workflows/default-tests.yml index 13e9677b..2495ef47 100644 --- a/.github/workflows/default-tests.yml +++ b/.github/workflows/default-tests.yml @@ -31,7 +31,7 @@ jobs: needs: checkstyle strategy: matrix: - java_version: ['11', '12', '13', '14', '15', '16', '17', '18', '19', '20'] + java_version: ['11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21'] steps: - uses: actions/checkout@v4 @@ -59,7 +59,7 @@ jobs: strategy: matrix: java_version: ['11'] - maven_version: ['3.2.5', '3.3.9', '3.5.4', '3.6.3', '3.8.8', '3.9.1', '3.9.2', '4.0.0-alpha-7'] + maven_version: ['3.2.5', '3.3.9', '3.5.4', '3.6.3', '3.8.8', '3.9.6', '4.0.0-alpha-12'] steps: - uses: actions/checkout@v4 diff --git a/pom.xml b/pom.xml index a5b2e98f..c757aca3 100644 --- a/pom.xml +++ b/pom.xml @@ -397,7 +397,7 @@ ${project.basedir}/.git true target/testing.properties - yyyy-MM-dd'T'HH:mm:ssZ + GMT-08:00 false 7 diff --git a/src/main/java/pl/project13/maven/git/GitCommitIdMojo.java b/src/main/java/pl/project13/maven/git/GitCommitIdMojo.java index eae2ffe6..51972739 100644 --- a/src/main/java/pl/project13/maven/git/GitCommitIdMojo.java +++ b/src/main/java/pl/project13/maven/git/GitCommitIdMojo.java @@ -18,12 +18,12 @@ package pl.project13.maven.git; +import com.google.common.annotations.VisibleForTesting; import java.io.File; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.text.DateFormat; -import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.Collections; import java.util.Date; import java.util.List; @@ -44,6 +44,7 @@ import org.apache.maven.plugins.annotations.Parameter; import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Settings; +import org.joda.time.DateTime; import org.sonatype.plexus.build.incremental.BuildContext; import pl.project13.core.CommitIdGenerationMode; import pl.project13.core.CommitIdPropertiesOutputFormat; @@ -497,20 +498,28 @@ public class GitCommitIdMojo extends AbstractMojo { * represents dates or times exported by this plugin (e.g. {@code git.commit.time}, {@code * git.build.time}). It should be a valid {@link SimpleDateFormat} string. * - *

The current dateFormat is set to match maven's default {@code yyyy-MM-dd'T'HH:mm:ssZ}. - * Please note that in previous versions (2.2.0 - 2.2.2) the default dateFormat was set to: {@code - * dd.MM.yyyy '@' HH:mm:ss z}. However the {@code RFC 822 time zone} seems to give a more reliable - * option in parsing the date and it's being used in maven as default. + *

The current dateFormat will be formatted as ISO 8601 + * {@code yyyy-MM-dd'T'HH:mm:ssXXX} and therefore can be used as input to maven's + * + * reproducible build feature. + * + * Please note that in previous versions + * (2.2.2 - 7.0.1) the default format was set to {@code yyyy-MM-dd'T'HH:mm:ssZ} + * which produces a {@code RFC 822 time zone}. While such format gives reliable + * options in parsing the date, it does not comply with the requirements of + * the reproducible build feature. + * (2.2.0 - 2.2.2) the default dateFormat was set to: {@code + * dd.MM.yyyy '@' HH:mm:ss z}. * *

Example: * *

{@code
-   * yyyy-MM-dd'T'HH:mm:ssZ
+   * yyyy-MM-dd'T'HH:mm:ssXXX
    * }
* * @since 2.2.0 */ - @Parameter(defaultValue = "yyyy-MM-dd'T'HH:mm:ssZ") + @Parameter(defaultValue = "yyyy-MM-dd'T'HH:mm:ssXXX") String dateFormat; /** @@ -1454,32 +1463,26 @@ private Properties getContextProperties(MavenProject project) { * href="https://reproducible-builds.org/docs/source-date-epoch/">SOURCE_DATE_EPOCH. * *

Inspired by - * https://github.com/apache/maven-archiver/blob/a3103d99396cd8d3440b907ef932a33563225265/src/main/java/org/apache/maven/archiver/MavenArchiver.java#L765 + * https://github.com/apache/maven-archiver/blob/7acb1db4a9754beacde3f21a69e5523ee901abd5/src/main/java/org/apache/maven/archiver/MavenArchiver.java#L755 * * @param outputTimestamp the value of ${project.build.outputTimestamp} (may be * null) * @return the parsed timestamp, may be null if null input or input * contains only 1 character */ - private Date parseOutputTimestamp(String outputTimestamp) throws GitCommitIdExecutionException { + @VisibleForTesting + protected static Date parseOutputTimestamp(String outputTimestamp) { if (outputTimestamp != null && !outputTimestamp.trim().isEmpty() && outputTimestamp.chars().allMatch(Character::isDigit)) { - return new Date(Long.parseLong(outputTimestamp) * 1000); + return Date.from(Instant.ofEpochSecond(Long.parseLong(outputTimestamp))); } if ((outputTimestamp == null) || (outputTimestamp.length() < 2)) { // no timestamp configured return null; } - - DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); - try { - return df.parse(outputTimestamp); - } catch (ParseException pe) { - throw new GitCommitIdExecutionException( - "Invalid 'project.build.outputTimestamp' value '" + outputTimestamp + "'", pe); - } + return new DateTime(outputTimestamp).toDate(); } private void publishPropertiesInto(Properties propertiesToPublish, Properties propertiesTarget) { diff --git a/src/test/java/pl/project13/maven/git/GitCommitIdMojoTest.java b/src/test/java/pl/project13/maven/git/GitCommitIdMojoTest.java index 52fcc8b8..0686dfad 100644 --- a/src/test/java/pl/project13/maven/git/GitCommitIdMojoTest.java +++ b/src/test/java/pl/project13/maven/git/GitCommitIdMojoTest.java @@ -22,12 +22,19 @@ import java.io.File; import java.io.IOException; +import java.time.Instant; +import java.util.Date; +import junitparams.JUnitParamsRunner; +import junitparams.Parameters; +import org.joda.time.DateTime; import org.junit.Test; +import org.junit.runner.RunWith; import pl.project13.core.PropertiesFileGenerator; /** * Testcases to verify that the git-commit-id works properly. */ +@RunWith(JUnitParamsRunner.class) public class GitCommitIdMojoTest { @Test public void testCraftPropertiesOutputFileWithRelativePath() throws IOException { @@ -66,4 +73,66 @@ public void testCraftPropertiesOutputFileWithFullPath() throws IOException { .toFile() .getCanonicalPath()); } + + /** + * test cases for output timestamp parsing. + * This timestamp is configured for Reproducible Builds' archive entries + * (https://maven.apache.org/guides/mini/guide-reproducible-builds.html). The value from + * ${project.build.outputTimestamp} is 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. + * When using ISO 8601 formatting please note that the entire expression must be entirely either + * in the basic format (20240215T135459+0100) or in the + * extended format (e.g. 2024-02-15T13:54:59+01:00). + * The maven plugin only supports the extended format. + */ + private Object[] parametersParseOutputTimestamp() { + return new Object[] { + // long since epoch + new Object[] { + "1644689403" + }, + // Date and time with timezone: + new Object[] { + "2022-02-12T15:30+00:00" + }, + new Object[] { + "2022-02-12T15:30:45-05:00" + }, + new Object[] { + "2022-02-12T15:30:00+00:00" + }, + new Object[] { + "2023-11-30T09:17:06+05:30" + }, + new Object[] { + "2024-08-15T20:45:30-03:00" + }, + new Object[] { + "2022-02-12T15:30:00Z" + }, + new Object[] { + "2023-11-30T09:17:06+0100" + }, + // Lowercase time designator + new Object[] { + "2019-03-26t14:00Z" + }, + // Lowercase UTC designator + new Object[] { + "2019-03-26T14:00z" + }, + // Hours-only offset + new Object[] { + "2019-03-26T10:00-04" + }, + }; + } + + @Test + @Parameters(method = "parametersParseOutputTimestamp") + public void testParseOutputTimestamp(String input) { + Date actual = GitCommitIdMojo.parseOutputTimestamp(input); + assertThat(actual).isNotNull(); + } } diff --git a/src/test/java/pl/project13/maven/git/NativeAndJGitProviderTest.java b/src/test/java/pl/project13/maven/git/NativeAndJGitProviderTest.java index f0ec603b..215c5439 100644 --- a/src/test/java/pl/project13/maven/git/NativeAndJGitProviderTest.java +++ b/src/test/java/pl/project13/maven/git/NativeAndJGitProviderTest.java @@ -56,8 +56,8 @@ public class NativeAndJGitProviderTest extends GitIntegrationTest { "git.local.branch.behind", }; - private static final String DEFAULT_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZ"; - private static final String ISO8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssZZ"; + private static final String DEFAULT_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssXXX"; + private static final String ISO8601_FORMAT_STRING = "yyyy-MM-dd'T'HH:mm:ssXXX"; @Test public void testCompareBasic() throws Exception {