diff --git a/WHATSNEW b/WHATSNEW
index 350789623..c7708d9ab 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -153,6 +153,8 @@ Other changes:
a build if a warning occurs.
Bugzilla Report 41836.
+ * Ant now supports local properties. Bugzilla report 23942.
+
Changes from Ant 1.7.0 TO Ant 1.7.1
=============================================
diff --git a/docs/manual/CoreTasks/local.html b/docs/manual/CoreTasks/local.html
new file mode 100644
index 000000000..5abc87789
--- /dev/null
+++ b/docs/manual/CoreTasks/local.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+Local Task
+
+
+
+
+
Local
+
Description
+
Adds a local property to the current scope. Property scopes exist at Ant's
+various "block" levels. These include targets as well as the
+Parallel and Sequential
+task containers (including Macrodef bodies). A local
+property at a given scope "shadows" properties of the same name at higher scopes,
+including the global scope (declaring a local property at the global level, i.e.
+outside of any scope block, has no effect). Since Ant 1.8
+
+
Parameters
+
+
+
Attribute
+
Description
+
Required
+
+
+
name
+
The property to declare in the current scope
+
Yes
+
+
+
+
+
+
diff --git a/docs/manual/coretasklist.html b/docs/manual/coretasklist.html
index 670e724d4..aa47dd623 100644
--- a/docs/manual/coretasklist.html
+++ b/docs/manual/coretasklist.html
@@ -86,6 +86,7 @@
LoadFile LoadProperties LoadResource
+Local MakeURL Mail MacroDef
diff --git a/src/main/org/apache/tools/ant/MagicNames.java b/src/main/org/apache/tools/ant/MagicNames.java
index 36efb3570..46e2a59aa 100644
--- a/src/main/org/apache/tools/ant/MagicNames.java
+++ b/src/main/org/apache/tools/ant/MagicNames.java
@@ -174,6 +174,12 @@ public final class MagicNames {
*/
public static final String REFID_PROPERTY_HELPER = "ant.PropertyHelper";
+ /**
+ * Reference used to store the local properties.
+ * Value: {@value}
+ */
+ public static final String REFID_LOCAL_PROPERTIES = "ant.LocalProperties";
+
/**
* Name of JVM system property which provides the name of the ProjectHelper class to use.
* Value: {@value}
diff --git a/src/main/org/apache/tools/ant/Target.java b/src/main/org/apache/tools/ant/Target.java
index cf0993442..bf5bcd049 100644
--- a/src/main/org/apache/tools/ant/Target.java
+++ b/src/main/org/apache/tools/ant/Target.java
@@ -25,6 +25,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
+import org.apache.tools.ant.property.LocalProperties;
+
/**
* Class to implement a target object with required parameters.
*
@@ -347,14 +349,22 @@ public class Target implements TaskContainer {
*/
public void execute() throws BuildException {
if (testIfCondition() && testUnlessCondition()) {
- for (int taskPosition = 0; taskPosition < children.size(); ++taskPosition) {
- Object o = children.get(taskPosition);
- if (o instanceof Task) {
- Task task = (Task) o;
- task.perform();
- } else {
- ((RuntimeConfigurable) o).maybeConfigure(project);
+ LocalProperties localProperties
+ = LocalProperties.get(getProject());
+ localProperties.enterScope();
+ try {
+ for (int taskPosition = 0; taskPosition < children.size();
+ ++taskPosition) {
+ Object o = children.get(taskPosition);
+ if (o instanceof Task) {
+ Task task = (Task) o;
+ task.perform();
+ } else {
+ ((RuntimeConfigurable) o).maybeConfigure(project);
+ }
}
+ } finally {
+ localProperties.exitScope();
}
} else if (!testIfCondition()) {
project.log(this, "Skipped because property '" + project.replaceProperties(ifCondition)
diff --git a/src/main/org/apache/tools/ant/property/LocalProperties.java b/src/main/org/apache/tools/ant/property/LocalProperties.java
new file mode 100644
index 000000000..367261637
--- /dev/null
+++ b/src/main/org/apache/tools/ant/property/LocalProperties.java
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.tools.ant.property;
+
+import org.apache.tools.ant.PropertyHelper;
+import org.apache.tools.ant.Project;
+import org.apache.tools.ant.MagicNames;
+
+/**
+ * Thread local class containing local properties.
+ * @since Ant 1.8.0
+ */
+public class LocalProperties
+ extends InheritableThreadLocal
+ implements PropertyHelper.PropertyEvaluator,
+ PropertyHelper.PropertySetter {
+
+ /**
+ * Get a localproperties for the given project.
+ * @param project the project to retieve the localproperties for.
+ * @return the localproperties.
+ */
+ public static synchronized LocalProperties get(Project project) {
+ LocalProperties l = (LocalProperties) project.getReference(
+ MagicNames.REFID_LOCAL_PROPERTIES);
+ if (l == null) {
+ l = new LocalProperties();
+ project.addReference(MagicNames.REFID_LOCAL_PROPERTIES, l);
+ PropertyHelper.getPropertyHelper(project).add(l);
+ }
+ return l;
+ }
+
+ // --------------------------------------------------
+ //
+ // Thread stuff
+ //
+ // --------------------------------------------------
+
+ /**
+ * Construct a new LocalProperties object.
+ */
+ private LocalProperties() {
+ }
+
+ /**
+ * Get the initial value.
+ * @return a new localproperties stack.
+ */
+ protected synchronized Object initialValue() {
+ return new LocalPropertyStack();
+ }
+
+ private LocalPropertyStack current() {
+ return (LocalPropertyStack) get();
+ }
+
+ // --------------------------------------------------
+ //
+ // Local property adding and scoping
+ //
+ // --------------------------------------------------
+
+ /**
+ * Add a local property to the current scope.
+ * @param property the property name to add.
+ */
+ public void addLocal(String property) {
+ current().addLocal(property);
+ }
+
+ /** enter the scope */
+ public void enterScope() {
+ current().enterScope();
+ }
+
+ /** exit the scope */
+ public void exitScope() {
+ current().exitScope();
+ }
+
+ // --------------------------------------------------
+ //
+ // Copy - used in parallel to make a new stack
+ //
+ // --------------------------------------------------
+
+ /**
+ * Copy the stack for a parallel thread.
+ * To be called from the parallel thread itself.
+ */
+ public void copy() {
+ set(current().copy());
+ }
+
+ // --------------------------------------------------
+ //
+ // PropertyHelper delegate methods
+ //
+ // --------------------------------------------------
+
+ /**
+ * Evaluate a property.
+ * @param property the property's String "identifier".
+ * @param helper the invoking PropertyHelper.
+ * @return Object value.
+ */
+ public Object evaluate(String property, PropertyHelper helper) {
+ return current().evaluate(property, helper);
+ }
+
+ /**
+ * Set a *new" property.
+ * @param property the property's String "identifier".
+ * @param value the value to set.
+ * @param propertyHelper the invoking PropertyHelper.
+ * @return true if this entity 'owns' the property.
+ */
+ public boolean setNew(
+ String property, Object value, PropertyHelper propertyHelper) {
+ return current().setNew(property, value, propertyHelper);
+ }
+
+ /**
+ * Set a property.
+ * @param property the property's String "identifier".
+ * @param value the value to set.
+ * @param propertyHelper the invoking PropertyHelper.
+ * @return true if this entity 'owns' the property.
+ */
+ public boolean set(
+ String property, Object value, PropertyHelper propertyHelper) {
+ return current().set(property, value, propertyHelper);
+ }
+}
+
+
diff --git a/src/main/org/apache/tools/ant/property/LocalPropertyStack.java b/src/main/org/apache/tools/ant/property/LocalPropertyStack.java
new file mode 100644
index 000000000..dd6f97adc
--- /dev/null
+++ b/src/main/org/apache/tools/ant/property/LocalPropertyStack.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.tools.ant.property;
+
+
+import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.tools.ant.PropertyHelper;
+
+/**
+ * A stack of local property maps.
+ * There is a map for each scope (target, sequential, macro).
+ * @since Ant 1.8.0
+ */
+public class LocalPropertyStack {
+ private LinkedList stack = new LinkedList();
+
+ // --------------------------------------------------
+ //
+ // Local property adding and scoping
+ //
+ // --------------------------------------------------
+
+ /**
+ * Add a local property.
+ * @param property the name of the local proeprty.
+ */
+ public void addLocal(String property) {
+ if (!stack.isEmpty()) {
+ ((Map) stack.getFirst()).put(property, NullReturn.NULL);
+ }
+ }
+
+ /**
+ * Enter the local scope.
+ */
+ public void enterScope() {
+ stack.addFirst(new HashMap());
+ }
+
+ /**
+ * Exit the local scope.
+ */
+ public void exitScope() {
+ ((HashMap) stack.removeFirst()).clear();
+ }
+
+ // --------------------------------------------------
+ //
+ // Copy - used in parallel to make a new stack
+ //
+ // --------------------------------------------------
+
+ /**
+ * Copy the stack for a parallel thread.
+ * @return a copy.
+ */
+ public LocalPropertyStack copy() {
+ LocalPropertyStack ret = new LocalPropertyStack();
+ ret.stack.addAll(stack);
+ return ret;
+ }
+
+ // --------------------------------------------------
+ //
+ // PropertyHelper delegate methods
+ //
+ // --------------------------------------------------
+
+ /**
+ * Evaluate a property.
+ * @param property the property's String "identifier".
+ * @param helper the invoking PropertyHelper.
+ * @return Object value.
+ */
+ public Object evaluate(String property, PropertyHelper helper) {
+ for (Iterator i = stack.iterator(); i.hasNext();) {
+ Map map = (Map) i.next();
+ Object ret = map.get(property);
+ if (ret != null) {
+ return ret;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Set a *new" property.
+ * @param property the property's String "identifier".
+ * @param value the value to set.
+ * @param propertyHelper the invoking PropertyHelper.
+ * @return true if this entity 'owns' the property.
+ */
+ public boolean setNew(
+ String property, Object value, PropertyHelper propertyHelper) {
+ Map map = getMapForProperty(property);
+ if (map == null) {
+ return false;
+ }
+ Object currValue = map.get(property);
+ if (currValue == NullReturn.NULL) {
+ map.put(property, value);
+ }
+ return true;
+ }
+
+ /**
+ * Set a property.
+ * @param property the property's String "identifier".
+ * @param value the value to set.
+ * @param propertyHelper the invoking PropertyHelper.
+ * @return true if this entity 'owns' the property.
+ */
+ public boolean set(String property, Object value, PropertyHelper propertyHelper) {
+ Map map = getMapForProperty(property);
+ if (map == null) {
+ return false;
+ }
+ map.put(property, value);
+ return true;
+ }
+
+ private Map getMapForProperty(String property) {
+ for (Iterator i = stack.iterator(); i.hasNext();) {
+ Map map = (Map) i.next();
+ if (map.get(property) != null) {
+ return map;
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/src/main/org/apache/tools/ant/taskdefs/Local.java b/src/main/org/apache/tools/ant/taskdefs/Local.java
new file mode 100644
index 000000000..3fdf9b59b
--- /dev/null
+++ b/src/main/org/apache/tools/ant/taskdefs/Local.java
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ *
+ */
+package org.apache.tools.ant.taskdefs;
+
+import org.apache.tools.ant.BuildException;
+import org.apache.tools.ant.Task;
+import org.apache.tools.ant.property.LocalProperties;
+
+/**
+ * Task to create a local property in the current scope.
+ */
+public class Local extends Task {
+ private String name;
+
+ /**
+ * Set the name attribute.
+ * @param name the name of the local property.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Run the task.
+ */
+ public void execute() {
+ if (name == null) {
+ throw new BuildException("Missing attribute name");
+ }
+ LocalProperties.get(getProject()).addLocal(name);
+ }
+}
diff --git a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java
index 3917101ca..8814eba1e 100644
--- a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java
+++ b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java
@@ -37,6 +37,7 @@ import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
import org.apache.tools.ant.UnknownElement;
+import org.apache.tools.ant.property.LocalProperties;
/**
* The class to be placed in the ant type definition.
@@ -390,6 +391,9 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain
// need to set the project on unknown element
UnknownElement c = copy(macroDef.getNestedTask(), false);
c.init();
+ LocalProperties localProperties
+ = LocalProperties.get(getProject());
+ localProperties.enterScope();
try {
c.perform();
} catch (BuildException ex) {
@@ -403,6 +407,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain
} finally {
presentElements = null;
localAttributes = null;
+ localProperties.exitScope();
}
}
}
diff --git a/src/main/org/apache/tools/ant/taskdefs/Parallel.java b/src/main/org/apache/tools/ant/taskdefs/Parallel.java
index 9ebdf9c9d..750071cc9 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Parallel.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Parallel.java
@@ -26,6 +26,7 @@ import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Location;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
+import org.apache.tools.ant.property.LocalProperties;
import org.apache.tools.ant.util.StringUtils;
/**
@@ -451,6 +452,7 @@ public class Parallel extends Task
*/
public void run() {
try {
+ LocalProperties.get(getProject()).copy();
thread = Thread.currentThread();
task.perform();
} catch (Throwable t) {
diff --git a/src/main/org/apache/tools/ant/taskdefs/Sequential.java b/src/main/org/apache/tools/ant/taskdefs/Sequential.java
index 6ef641bf8..2fa9388ab 100644
--- a/src/main/org/apache/tools/ant/taskdefs/Sequential.java
+++ b/src/main/org/apache/tools/ant/taskdefs/Sequential.java
@@ -23,6 +23,8 @@ import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.TaskContainer;
+import org.apache.tools.ant.property.LocalProperties;
+
/**
* Sequential is a container task - it can contain other Ant tasks. The nested
* tasks are simply executed in sequence. Sequential's primary use is to support
@@ -57,9 +59,16 @@ public class Sequential extends Task implements TaskContainer {
* @throws BuildException if one of the nested tasks fails.
*/
public void execute() throws BuildException {
- for (Iterator i = nestedTasks.iterator(); i.hasNext();) {
- Task nestedTask = (Task) i.next();
- nestedTask.perform();
+ LocalProperties localProperties
+ = LocalProperties.get(getProject());
+ localProperties.enterScope();
+ try {
+ for (Iterator i = nestedTasks.iterator(); i.hasNext();) {
+ Task nestedTask = (Task) i.next();
+ nestedTask.perform();
+ }
+ } finally {
+ localProperties.exitScope();
}
}
}
diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties
index f0488dc90..6b923e680 100644
--- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties
+++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties
@@ -61,6 +61,7 @@ length=org.apache.tools.ant.taskdefs.Length
loadfile=org.apache.tools.ant.taskdefs.LoadFile
loadproperties=org.apache.tools.ant.taskdefs.LoadProperties
loadresource=org.apache.tools.ant.taskdefs.LoadResource
+local=org.apache.tools.ant.taskdefs.Local
macrodef=org.apache.tools.ant.taskdefs.MacroDef
mail=org.apache.tools.ant.taskdefs.email.EmailTask
manifest=org.apache.tools.ant.taskdefs.ManifestTask
diff --git a/src/tests/antunit/taskdefs/local-test.xml b/src/tests/antunit/taskdefs/local-test.xml
new file mode 100644
index 000000000..49b01193e
--- /dev/null
+++ b/src/tests/antunit/taskdefs/local-test.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+