[msbuild] ongoing ITaskItem input parameter handling improvements.
authorAtsushi Eno <atsushieno@gmail.com>
Wed, 14 May 2014 08:26:13 +0000 (17:26 +0900)
committerAtsushi Eno <atsushieno@gmail.com>
Thu, 15 May 2014 14:55:18 +0000 (23:55 +0900)
Right now all the input parameters are computed based on string, and any
metadata items are simply chopped out when it is passed to a Task.
To fix this, we need to replace ExpandString() use with more complicated
evaluator that takes ITaskItem into consideration.

(It is not complete as of now; evaluation method needs to be replaced and
any regressions need to be fixed.)

mcs/class/Microsoft.Build/Microsoft.Build.Execution/ProjectInstance.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/BuildEngine4.cs
mcs/class/Microsoft.Build/Microsoft.Build.Internal/ExpressionEvaluator.cs
mcs/class/Microsoft.Build/Test/Microsoft.Build.Execution/ProjectInstanceTest.cs

index c3a0e83287d1e7e42ade67f24321e5fde80c6e01..8d6420a72405829973f6596429dba2d8d976ced0 100644 (file)
@@ -446,6 +446,11 @@ namespace Microsoft.Build.Execution
                        return WindowsCompatibilityExtensions.NormalizeFilePath (new ExpressionEvaluator (this).Evaluate (unexpandedValue));
                }
 
+               internal IEnumerable<object> EvaluateAsStringOrItems (string unexpandedValue)
+               {
+                       return new ExpressionEvaluator (this).EvaluateAsStringOrItems (unexpandedValue);
+               }
+
                public ICollection<ProjectItemInstance> GetItems (string itemType)
                {
                        return new CollectionFromEnumerable<ProjectItemInstance> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
index ba0db9e1f6587274852d1a2b4981dc1f8f4f0aa1..f260b3c0982dcdb625944920f6367ec39fa2832a 100644 (file)
@@ -311,7 +311,7 @@ namespace Microsoft.Build.Internal
                        task.BuildEngine = this;
                        
                        // Prepare task parameters.
-                       var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,string> (p.Key, project.ExpandString (p.Value)));
+                       var evaluatedTaskParams = taskInstance.Parameters.Select (p => new KeyValuePair<string,object[]> (p.Key, project.EvaluateAsStringOrItems (p.Value).ToArray ()));
                        
                        var requiredProps = task.GetType ().GetProperties ()
                                .Where (p => p.CanWrite && p.GetCustomAttributes (typeof (RequiredAttribute), true).Any ());
@@ -327,15 +327,22 @@ namespace Microsoft.Build.Internal
                                        continue;
                                }
                                var prop = task.GetType ().GetProperty (p.Key);
+                               object valueInstance = null;
                                if (prop == null)
                                        throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", taskInstance.Name, p.Key));
                                if (!prop.CanWrite)
                                        throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", taskInstance.Name, p.Key));
-                               if (string.IsNullOrEmpty (p.Value) && !requiredProps.Contains (prop))
-                                       continue;
                                try {
-                                       var valueInstance = ConvertTo (p.Value, prop.PropertyType);
-                                       prop.SetValue (task, valueInstance, null);
+                                       if (p.Value.All (o => o is ITaskItem)) {
+                                               valueInstance = ConvertTo (p.Value, prop.PropertyType);
+                                               prop.SetValue (task, valueInstance, null);
+                                       } else if (p.Value.Any ()) {
+                                               string valueString = string.Join (";", p.Value.Where (o => o != null).Select (o => ConvertTo (o, typeof (string))));
+                                               if (string.IsNullOrEmpty (valueString) && !requiredProps.Contains (prop))
+                                                       continue;
+                                               valueInstance = ConvertTo (valueString, prop.PropertyType);
+                                               prop.SetValue (task, valueInstance, null);
+                                       }
                                } catch (Exception ex) {
                                        throw new InvalidOperationException (string.Format ("Failed to convert '{0}' for property '{1}' of type {2}", p.Value, prop.Name, prop.PropertyType), ex);
                                }
@@ -397,14 +404,25 @@ namespace Microsoft.Build.Internal
                        return true;
                }
                
-               object ConvertTo (string source, Type targetType)
+               object ConvertTo (object sourceObject, Type targetType)
                {
-                       if (targetType == typeof(ITaskItem) || targetType.IsSubclassOf (typeof(ITaskItem)))
+                       if (sourceObject == null)
+                               return null;
+                       if (sourceObject.GetType () == targetType)
+                               return sourceObject;
+                       var arr = sourceObject is IEnumerable<object> ? ((IEnumerable<object>) sourceObject).ToArray ()
+                               : sourceObject is ICollection
+                               ? (object []) new ArrayList ((ICollection) sourceObject).ToArray (typeof(object)) : null;
+                       if (targetType.IsArray && sourceObject.GetType ().IsArray)
+                               return new ArrayList (arr.Select (o => ConvertTo (o, targetType.GetElementType ())).Where (o => o != null).ToArray ())
+                                               .ToArray (targetType.GetElementType ());
+                       var source = sourceObject is string ? (string) sourceObject : arr == null || !arr.Any () ? string.Empty : string.Join (";", arr.Select (o => ConvertTo (o, typeof (string))).Where (s => s != null));
+                       if (targetType == typeof (ITaskItem) || targetType.IsSubclassOf (typeof (ITaskItem)))
                                return new TargetOutputTaskItem () { ItemSpec = WindowsCompatibilityExtensions.FindMatchingPath (source.Trim ()) };
                        if (targetType.IsArray)
                                return new ArrayList (source.Split (';').Select (s => s.Trim ()).Where (s => !string.IsNullOrEmpty (s)).Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
                                                .ToArray (targetType.GetElementType ());
-                       if (targetType == typeof(bool)) {
+                       if (targetType == typeof (bool)) {
                                switch (source != null ? source.ToLower (CultureInfo.InvariantCulture) : string.Empty) {
                                case "true":
                                case "yes":
index 921003d5230a88c732ea8ae2384d7d469448bb8c..febbd850c5a10e46d9275e00542a381b7bd6b6d9 100644 (file)
@@ -96,6 +96,14 @@ namespace Microsoft.Build.Internal.Expressions
                                throw new InvalidProjectFileException (string.Format ("failed to evaluate expression as boolean: '{0}': {1}", source, ex.Message), ex);
                        }
                }
+
+               public IEnumerable<object> EvaluateAsStringOrItems (string unexpandedValue)
+               {
+                       var exprList = new ExpressionParserManual (unexpandedValue ?? string.Empty, ExpressionValidationType.LaxString).Parse ();
+                       if (exprList == null)
+                               throw new ArgumentNullException ("exprList");
+                       return exprList.Select (e => e.EvaluateAsStringOrItems (CreateContext (unexpandedValue)));
+               }
        }
        
        class EvaluationContext
@@ -113,6 +121,12 @@ namespace Microsoft.Build.Internal.Expressions
                
                Stack<object> evaluating_items = new Stack<object> ();
                Stack<object> evaluating_props = new Stack<object> ();
+
+               List<ITaskItem> evaluated_task_items = new List<ITaskItem> ();
+
+               public IList<ITaskItem> EvaluatedTaskItems {
+                       get { return evaluated_task_items; }
+               }
                
                public IEnumerable<object> GetItems (string name)
                {
@@ -139,8 +153,12 @@ namespace Microsoft.Build.Internal.Expressions
                                var eval = item as ProjectItem;
                                if (eval != null)
                                        return Evaluator.Evaluate (eval.EvaluatedInclude);
-                               else
-                                       return Evaluator.Evaluate (((ProjectItemInstance) item).EvaluatedInclude);
+                               else {
+                                       var inst = (ProjectItemInstance) item;
+                                       if (!evaluated_task_items.Contains (inst))
+                                               evaluated_task_items.Add (inst);
+                                       return Evaluator.Evaluate (inst.EvaluatedInclude);
+                               }
                        } finally {
                                evaluating_items.Pop ();
                        }
@@ -191,6 +209,11 @@ namespace Microsoft.Build.Internal.Expressions
                        }
                        throw new InvalidProjectFileException (this.Location, string.Format ("Condition '{0}' is evaluated as '{1}' and cannot be converted to boolean", context.Source, ret));
                }
+
+               public virtual object EvaluateAsStringOrItems (EvaluationContext context)
+               {
+                       return EvaluateAsString (context);
+               }
        }
        
        partial class BinaryExpression : Expression
@@ -405,6 +428,16 @@ namespace Microsoft.Build.Internal.Expressions
                
                public override string EvaluateAsString (EvaluationContext context)
                {
+                       // FIXME: enable this and fix regressions.
+                       /*
+                       var ret = EvaluateAsStringOrItems (context);
+                       if (ret == null)
+                               return null;
+                       if (ret is string)
+                               return (string) ret;
+                       string itemType = Application.Name.Name;
+                       return string.Join (";", ((IEnumerable<object>) ret).Select (item => context.EvaluateItem (itemType, item)));
+                       */
                        string itemType = Application.Name.Name;
                        var items = context.GetItems (itemType);
                        if (!items.Any ())
@@ -417,13 +450,33 @@ namespace Microsoft.Build.Internal.Expressions
                                        var ret = string.Concat (Application.Expressions.Select (e => e.EvaluateAsString (context)));
                                        context.ContextItem = null;
                                        return ret;
-                                       }));
+                               }));
                }
                
                public override object EvaluateAsObject (EvaluationContext context)
                {
                        return EvaluateAsString (context);
                }
+
+               // FIXME: enable this and fix regressions.
+               /*
+               public override object EvaluateAsStringOrItems (EvaluationContext context)
+               {
+                       string itemType = Application.Name.Name;
+                       var items = context.GetItems (itemType);
+                       if (!items.Any ())
+                               return null;
+                       if (Application.Expressions == null)
+                               return items;
+                       else
+                               return string.Join (";", items.Select (item => {
+                                       context.ContextItem = item;
+                                       var ret = string.Concat (Application.Expressions.Select (e => e.EvaluateAsString (context)));
+                                       context.ContextItem = null;
+                                       return ret;
+                               }));
+               }
+               */
        }
 
        partial class MetadataAccessExpression : Expression
index d3a7af4a0354721146289a2efd108cffa0e63668..1f33a64907f1d50bb28d0db228a32a89bde9bc4c 100644 (file)
@@ -223,13 +223,12 @@ namespace MonoTests.Microsoft.Build.Execution
                [Test]
                public void ExpandStringWithMetadata ()
                {
-                       string thisAssembly = new Uri (GetType ().Assembly.CodeBase).LocalPath;
-                       string project_xml = string.Format (@"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+                       string project_xml = @"<Project xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
   <ItemGroup>
     <Foo Include='xxx'><M>x</M></Foo>
     <Foo Include='yyy'><M>y</M></Foo>
   </ItemGroup>
-</Project>", thisAssembly);
+</Project>";
                        var xml = XmlReader.Create (new StringReader (project_xml));
                        var root = ProjectRootElement.Create (xml);
                        root.FullPath = "ProjectInstanceTest.ExpandStringWithMetadata.proj";