[xbuild] Don't reevaluate project when setting metadata in a dynamic ..
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
index feac4e6833338792855b42edaebe4e10f98c8850..82af83b15232988b1fd1d50b1b18e80d2bc09536 100644 (file)
@@ -26,6 +26,7 @@
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 
 using System;
+using System.Linq;
 using System.Collections;
 using System.Collections.Generic;
 using System.Collections.Specialized;
@@ -50,6 +51,9 @@ namespace Microsoft.Build.BuildEngine {
                //string                recursiveDir;
                IDictionary     evaluatedMetadata;
                IDictionary     unevaluatedMetadata;
+               bool            isDynamic;
+               bool            keepDuplicates = true;
+               string          removeMetadata, keepMetadata;
 
                BuildItem ()
                {
@@ -90,11 +94,49 @@ namespace Microsoft.Build.BuildEngine {
                        this.parent_item_group = parentItemGroup;
                        
                        this.itemElement = itemElement;
-                       
-                       if (Include == String.Empty)
-                               throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
+                       isDynamic = parentItemGroup.IsDynamic;
+
+                       if (IsDynamic) {
+                               if (!string.IsNullOrEmpty (Remove)) {
+                                       if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
+                                               throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
+                                       if (itemElement.HasChildNodes)
+                                               throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
+                               }
+                               if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
+                       } else {
+                               if (string.IsNullOrEmpty (Include))
+                                       throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
+                               if (!string.IsNullOrEmpty (Remove))
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
+                       }
+
+                       foreach (XmlAttribute attr in itemElement.Attributes) {
+                               if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
+                                       continue;
+                               if (!IsDynamic)
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
+
+                               switch (attr.Name) {
+                               case "Remove":
+                                       Remove = attr.Value;
+                                       break;
+                               case "KeepDuplicates":
+                                       KeepDuplicates = bool.Parse (attr.Value);
+                                       break;
+                               case "RemoveMetadata":
+                                       removeMetadata = attr.Value;
+                                       break;
+                               case "KeepMetadata":
+                                       keepMetadata = attr.Value;
+                                       break;
+                               default:
+                                       throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
+                               }
+                       }
                }
-               
+
                BuildItem (BuildItem parent)
                {
                        isImported = parent.isImported;
@@ -125,7 +167,7 @@ namespace Microsoft.Build.BuildEngine {
                {
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
                                string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
-                               return (metadataName.ToLower () == "fullpath") ? MSBuildUtils.Escape (metadata) : metadata;
+                               return MSBuildUtils.Unescape (metadata);
                        }
 
                        if (evaluatedMetadata.Contains (metadataName))
@@ -137,8 +179,7 @@ namespace Microsoft.Build.BuildEngine {
                public string GetMetadata (string metadataName)
                {
                        if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
-                               string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
-                               return (metadataName.ToLower () == "fullpath") ? MSBuildUtils.Escape (metadata) : metadata;
+                               return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
                        } else if (unevaluatedMetadata.Contains (metadataName))
                                return (string) unevaluatedMetadata [metadataName];
                        else
@@ -185,6 +226,14 @@ namespace Microsoft.Build.BuildEngine {
                public void SetMetadata (string metadataName,
                                         string metadataValue,
                                         bool treatMetadataValueAsLiteral)
+               {
+                       SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, false);
+               }
+
+               void SetMetadata (string metadataName,
+                                        string metadataValue,
+                                        bool treatMetadataValueAsLiteral,
+                                        bool fromDynamicItem)
                {
                        if (metadataName == null)
                                throw new ArgumentNullException ("metadataName");
@@ -210,9 +259,11 @@ namespace Microsoft.Build.BuildEngine {
                        } else if (HasParentItem) {
                                if (parent_item.child_items.Count > 1)
                                        SplitParentItem ();
-                               parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
+                               parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, fromDynamicItem);
                        }
-                       if (FromXml || HasParentItem) {
+
+                       // We don't want to reevalute the project for dynamic items
+                       if (!fromDynamicItem && !IsDynamic && (FromXml || HasParentItem)) {
                                parent_item_group.ParentProject.MarkProjectAsDirty ();
                                parent_item_group.ParentProject.NeedToReevaluate ();
                        }
@@ -223,9 +274,12 @@ namespace Microsoft.Build.BuildEngine {
 
                void AddMetadata (string name, string value)
                {
+                       var options = IsDynamic ?
+                                     ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
+
                        if (parent_item_group != null) {
                                Expression e = new Expression ();
-                               e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
+                               e.Parse (value, options);
                                evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
                                                typeof (string), ExpressionOptions.ExpandItemRefs);
                        } else
@@ -250,21 +304,39 @@ namespace Microsoft.Build.BuildEngine {
                                this.finalItemSpec = MSBuildUtils.Unescape (Include);
                                return;
                        }
-                       
+
                        foreach (XmlNode xn in itemElement.ChildNodes) {
                                XmlElement xe = xn as XmlElement;
                                if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
                                        AddMetadata (xe.Name, xe.InnerText);
                        }
 
+                       if (IsDynamic) {
+                               if (!evaluatedTo)
+                                       return;
+
+                               if (!string.IsNullOrEmpty (Remove)) {
+                                       RemoveItems (project);
+                                       return;
+                               }
+
+                               if (string.IsNullOrEmpty (Include)) {
+                                       UpdateMetadata (project);
+                                       return;
+                               }
+                       }
+
                        DirectoryScanner directoryScanner;
                        Expression includeExpr, excludeExpr;
                        ITaskItem[] includes, excludes;
 
+                       var options = IsDynamic ?
+                               ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
+
                        includeExpr = new Expression ();
-                       includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
+                       includeExpr.Parse (Include, options);
                        excludeExpr = new Expression ();
-                       excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
+                       excludeExpr.Parse (Exclude, options);
                        
                        includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
                                                                ExpressionOptions.ExpandItemRefs);
@@ -279,8 +351,10 @@ namespace Microsoft.Build.BuildEngine {
                        directoryScanner.Includes = includes;
                        directoryScanner.Excludes = excludes;
 
-                       if (project.FullFileName != String.Empty)
+                       if (project.FullFileName != String.Empty) {
+                               directoryScanner.ProjectFile = project.ThisFileFullPath;
                                directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
+                       }
                        else
                                directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
                        
@@ -289,9 +363,123 @@ namespace Microsoft.Build.BuildEngine {
                        foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
                                AddEvaluatedItem (project, evaluatedTo, matchedItem);
                }
-               
+
+               bool CheckCondition (Project project)
+               {
+                       if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
+                               return false;
+                       if (parent_item != null && !parent_item.CheckCondition (project))
+                               return false;
+                       return ConditionParser.ParseAndEvaluate (Condition, project);
+               }
+
+               void UpdateMetadata (Project project)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return;
+
+                       foreach (BuildItem item in group) {
+                               if (!item.CheckCondition (project))
+                                       continue;
+                               
+                               foreach (string name in evaluatedMetadata.Keys) {
+                                       item.SetMetadata (name, (string)evaluatedMetadata [name], false, IsDynamic);
+                               }
+
+                               AddAndRemoveMetadata (project, item);
+                       }
+               }
+
+               void AddAndRemoveMetadata (Project project, BuildItem item)
+               {
+                       if (!string.IsNullOrEmpty (removeMetadata)) {
+                               var removeExpr = new Expression ();
+                               removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                               var removeSpec = (string[]) removeExpr.ConvertTo (
+                                       project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
+
+                               foreach (var remove in removeSpec) {
+                                       item.DeleteMetadata (remove);
+                               }
+                       }
+
+                       if (!string.IsNullOrEmpty (keepMetadata)) {
+                               var keepExpr = new Expression ();
+                               keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                               var keepSpec = (string[]) keepExpr.ConvertTo (
+                                       project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
+
+                               var metadataNames = new string [item.evaluatedMetadata.Count];
+                               item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
+
+                               foreach (string name in metadataNames) {
+                                       if (!keepSpec.Contains (name))
+                                               item.DeleteMetadata (name);
+                               }
+                       }
+               }
+
+               void RemoveItems (Project project)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return;
+
+                       var removeExpr = new Expression ();
+                       removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
+
+                       var removes = (ITaskItem[]) removeExpr.ConvertTo (
+                               project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
+
+                       var directoryScanner = new DirectoryScanner ();
+                       
+                       directoryScanner.Includes = removes;
+
+                       if (project.FullFileName != String.Empty)
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
+                       else
+                               directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
+                       
+                       directoryScanner.Scan ();
+
+                       foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
+                               group.RemoveItem (matchedItem);
+                       }
+               }
+
+               bool ContainsItem (Project project, ITaskItem taskItem)
+               {
+                       BuildItemGroup group;
+                       if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
+                               return false;
+
+                       var item = group.FindItem (taskItem);
+                       if (item == null)
+                               return false;
+
+                       foreach (string metadataName in evaluatedMetadata.Keys) {
+                               string metadataValue = (string)evaluatedMetadata [metadataName];
+                               if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
+                                       return false;
+                       }
+                       
+                       foreach (string metadataName in item.evaluatedMetadata.Keys) {
+                               string metadataValue = (string)item.evaluatedMetadata [metadataName];
+                               if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
+                                       return false;
+                       }
+
+                       return true;
+               }
+
                void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
                {
+                       if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
+                               return;
+
                        BuildItemGroup big;                     
                        BuildItem bi = new BuildItem (this);
                        bi.finalItemSpec = taskitem.ItemSpec;
@@ -324,6 +512,9 @@ namespace Microsoft.Build.BuildEngine {
                        }
 
                        big.AddItem (bi);
+
+                       if (IsDynamic)
+                               AddAndRemoveMetadata (project, bi);
                }
                
                // during item's eval phase, any item refs in this item, have either
@@ -484,6 +675,20 @@ namespace Microsoft.Build.BuildEngine {
                        }
                }
 
+               internal bool IsDynamic {
+                       get { return isDynamic; }
+               }
+
+               internal string Remove {
+                       get;
+                       private set;
+               }
+
+               internal bool KeepDuplicates {
+                       get { return keepDuplicates; }
+                       private set { keepDuplicates = value; }
+               }
+
                public bool IsImported {
                        get { return isImported; }
                }
@@ -518,6 +723,10 @@ namespace Microsoft.Build.BuildEngine {
                internal bool FromXml {
                        get { return itemElement != null; }
                }
+
+               internal XmlElement XmlElement {
+                       get { return itemElement; }
+               }
                
                internal bool HasParentItem {
                        get { return parent_item != null; }