https://bz.apache.org/bugzilla/show_bug.cgi?id=61079master
@@ -41,10 +41,12 @@ to indicate, for example, the release date. The best place for this task is | |||||
probably in an initialization target.</p> | probably in an initialization target.</p> | ||||
<p><em>Since Ant 1.10.2</em> the magic | <p><em>Since Ant 1.10.2</em> the magic | ||||
property <code>ant.tstamp.now</code> can be used to specify a fixed | |||||
date value in order to create reproducible builds. Its value must be | |||||
a number and is interpreted as seconds since the epoch (midnight | |||||
1970-01-01).</p> | |||||
property <code>ant.tstamp.now</code> can be used to specify a fixed | |||||
date value in order to create reproducible builds. Its value must be | |||||
a number and is interpreted as seconds since the epoch (midnight | |||||
1970-01-01). With <code>ant.tstamp.now.iso</code> you could also specify that | |||||
value in ISO-8601 format (<code>1972-04-17T08:07:00Z</code>). If you specify a value | |||||
in an invalid format an INFO message will be logged and the value will be ignored.</p> | |||||
<h3>Parameters</h3> | <h3>Parameters</h3> | ||||
<table border="1" cellpadding="2" cellspacing="0"> | <table border="1" cellpadding="2" cellspacing="0"> | ||||
@@ -490,6 +490,10 @@ org.apache.tools.ant.Executor implementation specified here. | |||||
<td>number, seconds since the epoch (midnight 1970-01-01)</td> | <td>number, seconds since the epoch (midnight 1970-01-01)</td> | ||||
<td>The value to use as current time and date for <tstamp></td> | <td>The value to use as current time and date for <tstamp></td> | ||||
</tr> | </tr> | ||||
<tr> | |||||
<td><code>ant.tstamp.now.iso</code></td> | |||||
<td>ISO-8601 timestamp string like <code>1972-04-17T08:07:00Z</code></td> | |||||
</tr> | |||||
</table> | </table> | ||||
<p> | <p> | ||||
@@ -308,5 +308,18 @@ public final class MagicNames { | |||||
* @since Ant 1.10.2 | * @since Ant 1.10.2 | ||||
*/ | */ | ||||
public static final String TSTAMP_NOW = "ant.tstamp.now"; | public static final String TSTAMP_NOW = "ant.tstamp.now"; | ||||
/** | |||||
* Magic property that can be set to contain a value for tstamp's | |||||
* "now" in order to make builds that use the task create | |||||
* reproducible results. | |||||
* | |||||
* <p>The value is expected to be in ISO time format | |||||
* (<i>1972-04-17T08:07</i>)</p> | |||||
* | |||||
* Value: {@value} | |||||
* @since Ant 1.10.2 | |||||
*/ | |||||
public static final String TSTAMP_NOW_ISO = "ant.tstamp.now.iso"; | |||||
} | } | ||||
@@ -19,6 +19,7 @@ | |||||
package org.apache.tools.ant.taskdefs; | package org.apache.tools.ant.taskdefs; | ||||
import java.text.SimpleDateFormat; | import java.text.SimpleDateFormat; | ||||
import java.time.Instant; | |||||
import java.util.Calendar; | import java.util.Calendar; | ||||
import java.util.Date; | import java.util.Date; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
@@ -26,9 +27,12 @@ import java.util.List; | |||||
import java.util.Locale; | import java.util.Locale; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.NoSuchElementException; | import java.util.NoSuchElementException; | ||||
import java.util.Optional; | |||||
import java.util.StringTokenizer; | import java.util.StringTokenizer; | ||||
import java.util.TimeZone; | import java.util.TimeZone; | ||||
import java.util.Vector; | import java.util.Vector; | ||||
import java.util.function.BiFunction; | |||||
import java.util.function.Function; | |||||
import org.apache.tools.ant.BuildException; | import org.apache.tools.ant.BuildException; | ||||
import org.apache.tools.ant.Location; | import org.apache.tools.ant.Location; | ||||
@@ -111,16 +115,45 @@ public class Tstamp extends Task { | |||||
* Return the {@link Date} instance to use as base for DSTAMP, TSTAMP and TODAY. | * Return the {@link Date} instance to use as base for DSTAMP, TSTAMP and TODAY. | ||||
*/ | */ | ||||
protected Date getNow() { | protected Date getNow() { | ||||
String magicNow = getProject().getProperty(MagicNames.TSTAMP_NOW); | |||||
if (magicNow != null && magicNow.length() > 0) { | |||||
Optional<Date> now = getNow( | |||||
MagicNames.TSTAMP_NOW_ISO, | |||||
s -> Date.from(Instant.parse(s)), | |||||
(k, v) -> "magic property " + k + " ignored as '" + v + "' is not in valid ISO pattern" | |||||
); | |||||
if (now.isPresent()) { | |||||
return now.get(); | |||||
} | |||||
now = getNow( | |||||
MagicNames.TSTAMP_NOW, | |||||
s -> new Date(1000 * Long.parseLong(s)), | |||||
(k, v) -> "magic property " + k + " ignored as " + v + " is not a valid number" | |||||
); | |||||
if (now.isPresent()) { | |||||
return now.get(); | |||||
} | |||||
return new Date(); | |||||
} | |||||
/** | |||||
* Checks and returns a Date if the specified property is set. | |||||
* @param propertyName name of the property to check | |||||
* @param map convertion of the property value as string to Date | |||||
* @param log supplier of the log message containg the property name and value if | |||||
* the convertion fails | |||||
* @return Optional containing the Date or null | |||||
*/ | |||||
protected Optional<Date> getNow(String propertyName, Function<String, Date> map, BiFunction<String, String, String> log) { | |||||
String property = getProject().getProperty(propertyName); | |||||
if (property != null && property.length() > 0) { | |||||
try { | try { | ||||
return new Date(1000 * Long.parseLong(magicNow)); | |||||
} catch (NumberFormatException ex) { | |||||
log("magic property " + MagicNames.TSTAMP_NOW + " ignored as " | |||||
+ magicNow + " is not a valid number"); | |||||
return Optional.ofNullable(map.apply(property)); | |||||
} catch (Exception e) { | |||||
log(log.apply(propertyName, property)); | |||||
} | } | ||||
} | } | ||||
return new Date(); | |||||
return Optional.empty(); | |||||
} | } | ||||
/** | /** | ||||
@@ -24,4 +24,21 @@ | |||||
<tstamp/> | <tstamp/> | ||||
<au:assertPropertyEquals name="DSTAMP" value="19700102"/> | <au:assertPropertyEquals name="DSTAMP" value="19700102"/> | ||||
</target> | </target> | ||||
<target name="testMagicPropertyIso"> | |||||
<local name="ant.tstamp.now.iso"/> | |||||
<property name="ant.tstamp.now.iso" value="1972-04-17T08:07:00Z"/> | |||||
<tstamp/> | |||||
<au:assertPropertyEquals name="DSTAMP" value="19720417"/> | |||||
</target> | |||||
<target name="testMagicPropertyBoth"> | |||||
<local name="ant.tstamp.now"/> | |||||
<local name="ant.tstamp.now.iso"/> | |||||
<property name="ant.tstamp.now" value="100000"/> | |||||
<property name="ant.tstamp.now.iso" value="1972-04-17T08:07:22Z"/> | |||||
<tstamp/> | |||||
<!-- 'iso' overrides 'simple' --> | |||||
<au:assertPropertyEquals name="DSTAMP" value="19720417"/> | |||||
</target> | |||||
</project> | </project> |