5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 using System.Collections;
31 using System.Collections.Generic;
32 using System.Collections.Specialized;
36 using Microsoft.Build.Framework;
37 using Microsoft.Build.Utilities;
38 using Mono.XBuild.Utilities;
40 namespace Microsoft.Build.BuildEngine {
41 public class BuildItem {
43 List<BuildItem> child_items;
44 XmlElement itemElement;
49 BuildItemGroup parent_item_group;
50 BuildItem parent_item;
51 //string recursiveDir;
52 IDictionary evaluatedMetadata;
53 IDictionary unevaluatedMetadata;
55 bool keepDuplicates = true;
56 string removeMetadata, keepMetadata;
62 public BuildItem (string itemName, ITaskItem taskItem)
65 throw new ArgumentNullException ("taskItem");
68 this.finalItemSpec = taskItem.ItemSpec;
69 this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
70 this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
71 this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
74 public BuildItem (string itemName, string itemInclude)
76 if (itemInclude == null)
77 throw new ArgumentNullException ("itemInclude");
78 if (itemInclude == String.Empty)
79 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
82 finalItemSpec = itemInclude;
83 this.itemInclude = itemInclude;
84 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
85 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
88 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
90 child_items = new List<BuildItem> ();
91 isImported = parentItemGroup.IsImported;
92 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
93 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
94 this.parent_item_group = parentItemGroup;
96 this.itemElement = itemElement;
97 isDynamic = parentItemGroup.IsDynamic;
100 if (!string.IsNullOrEmpty (Remove)) {
101 if (!string.IsNullOrEmpty (Include) || !string.IsNullOrEmpty (Exclude))
102 throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
103 if (itemElement.HasChildNodes)
104 throw new InvalidProjectFileException ("Children are not allowed below an item remove element.");
106 if (string.IsNullOrEmpty (Include) && !string.IsNullOrEmpty (Exclude))
107 throw new InvalidProjectFileException (string.Format ("The attribute \"Exclude\" in element <{0}> is unrecognized.", Name));
109 if (string.IsNullOrEmpty (Include))
110 throw new InvalidProjectFileException (string.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
111 if (!string.IsNullOrEmpty (Remove))
112 throw new InvalidProjectFileException (string.Format ("The attribute \"Remove\" in element <{0}> is unrecognized.", Name));
115 foreach (XmlAttribute attr in itemElement.Attributes) {
116 if (attr.Name == "Include" || attr.Name == "Exclude" || attr.Name == "Condition")
119 throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
125 case "KeepDuplicates":
126 KeepDuplicates = bool.Parse (attr.Value);
128 case "RemoveMetadata":
129 removeMetadata = attr.Value;
132 keepMetadata = attr.Value;
135 throw new InvalidProjectFileException (string.Format ("The attribute \"{0}\" in element <{1}> is unrecognized.", attr.Name, Name));
140 BuildItem (BuildItem parent)
142 isImported = parent.isImported;
144 parent_item = parent;
145 parent_item.child_items.Add (this);
146 parent_item_group = parent.parent_item_group;
147 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
148 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
151 public void CopyCustomMetadataTo (BuildItem destinationItem)
153 if (destinationItem == null)
154 throw new ArgumentNullException ("destinationItem");
156 foreach (DictionaryEntry de in unevaluatedMetadata)
157 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
161 public BuildItem Clone ()
163 return (BuildItem) this.MemberwiseClone ();
166 public string GetEvaluatedMetadata (string metadataName)
168 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
169 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
170 return MSBuildUtils.Unescape (metadata);
173 if (evaluatedMetadata.Contains (metadataName))
174 return (string) evaluatedMetadata [metadataName];
179 public string GetMetadata (string metadataName)
181 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
182 return ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
183 } else if (unevaluatedMetadata.Contains (metadataName))
184 return (string) unevaluatedMetadata [metadataName];
189 public bool HasMetadata (string metadataName)
191 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
194 return evaluatedMetadata.Contains (metadataName);
197 public void RemoveMetadata (string metadataName)
199 if (metadataName == null)
200 throw new ArgumentNullException ("metadataName");
202 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
203 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
207 if (unevaluatedMetadata.Contains (metadataName)) {
208 XmlNode node = itemElement [metadataName];
209 itemElement.RemoveChild (node);
211 } else if (HasParentItem) {
212 if (parent_item.child_items.Count > 1)
214 parent_item.RemoveMetadata (metadataName);
217 DeleteMetadata (metadataName);
220 public void SetMetadata (string metadataName,
221 string metadataValue)
223 SetMetadata (metadataName, metadataValue, false);
226 public void SetMetadata (string metadataName,
227 string metadataValue,
228 bool treatMetadataValueAsLiteral)
230 if (metadataName == null)
231 throw new ArgumentNullException ("metadataName");
233 if (metadataValue == null)
234 throw new ArgumentNullException ("metadataValue");
236 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
237 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
240 if (treatMetadataValueAsLiteral && !HasParentItem)
241 metadataValue = MSBuildUtils.Escape (metadataValue);
244 XmlElement element = itemElement [metadataName];
245 if (element == null) {
246 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
247 element.InnerText = metadataValue;
248 itemElement.AppendChild (element);
250 element.InnerText = metadataValue;
251 } else if (HasParentItem) {
252 if (parent_item.child_items.Count > 1)
254 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
256 if (FromXml || HasParentItem) {
257 parent_item_group.ParentProject.MarkProjectAsDirty ();
258 parent_item_group.ParentProject.NeedToReevaluate ();
261 DeleteMetadata (metadataName);
262 AddMetadata (metadataName, metadataValue);
265 void AddMetadata (string name, string value)
267 var options = IsDynamic ?
268 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
270 if (parent_item_group != null) {
271 Expression e = new Expression ();
272 e.Parse (value, options);
273 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
274 typeof (string), ExpressionOptions.ExpandItemRefs);
276 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
278 unevaluatedMetadata [name] = value;
281 void DeleteMetadata (string name)
283 if (evaluatedMetadata.Contains (name))
284 evaluatedMetadata.Remove (name);
286 if (unevaluatedMetadata.Contains (name))
287 unevaluatedMetadata.Remove (name);
290 internal void Evaluate (Project project, bool evaluatedTo)
292 // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
293 if (project == null) {
294 this.finalItemSpec = MSBuildUtils.Unescape (Include);
298 foreach (XmlNode xn in itemElement.ChildNodes) {
299 XmlElement xe = xn as XmlElement;
300 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
301 AddMetadata (xe.Name, xe.InnerText);
308 if (!string.IsNullOrEmpty (Remove)) {
309 RemoveItems (project);
313 if (string.IsNullOrEmpty (Include)) {
314 UpdateMetadata (project);
319 DirectoryScanner directoryScanner;
320 Expression includeExpr, excludeExpr;
321 ITaskItem[] includes, excludes;
323 var options = IsDynamic ?
324 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
326 includeExpr = new Expression ();
327 includeExpr.Parse (Include, options);
328 excludeExpr = new Expression ();
329 excludeExpr.Parse (Exclude, options);
331 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
332 ExpressionOptions.ExpandItemRefs);
333 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
334 ExpressionOptions.ExpandItemRefs);
336 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
337 ExpressionOptions.ExpandItemRefs);
339 directoryScanner = new DirectoryScanner ();
341 directoryScanner.Includes = includes;
342 directoryScanner.Excludes = excludes;
344 if (project.FullFileName != String.Empty) {
345 directoryScanner.ProjectFile = project.ThisFileFullPath;
346 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
349 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
351 directoryScanner.Scan ();
353 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
354 AddEvaluatedItem (project, evaluatedTo, matchedItem);
357 bool CheckCondition (Project project)
359 if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
361 if (parent_item != null && !parent_item.CheckCondition (project))
363 return ConditionParser.ParseAndEvaluate (Condition, project);
366 void UpdateMetadata (Project project)
368 BuildItemGroup group;
369 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
372 foreach (BuildItem item in group) {
373 if (!item.CheckCondition (project))
376 foreach (string name in evaluatedMetadata.Keys) {
377 item.SetMetadata (name, (string)evaluatedMetadata [name]);
380 AddAndRemoveMetadata (project, item);
384 void AddAndRemoveMetadata (Project project, BuildItem item)
386 if (!string.IsNullOrEmpty (removeMetadata)) {
387 var removeExpr = new Expression ();
388 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
390 var removeSpec = (string[]) removeExpr.ConvertTo (
391 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
393 foreach (var remove in removeSpec) {
394 item.DeleteMetadata (remove);
398 if (!string.IsNullOrEmpty (keepMetadata)) {
399 var keepExpr = new Expression ();
400 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
402 var keepSpec = (string[]) keepExpr.ConvertTo (
403 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
405 var metadataNames = new string [item.evaluatedMetadata.Count];
406 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
408 foreach (string name in metadataNames) {
409 if (!keepSpec.Contains (name))
410 item.DeleteMetadata (name);
415 void RemoveItems (Project project)
417 BuildItemGroup group;
418 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
421 var removeExpr = new Expression ();
422 removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
424 var removes = (ITaskItem[]) removeExpr.ConvertTo (
425 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
427 var directoryScanner = new DirectoryScanner ();
429 directoryScanner.Includes = removes;
431 if (project.FullFileName != String.Empty)
432 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
434 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
436 directoryScanner.Scan ();
438 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
439 group.RemoveItem (matchedItem);
443 bool ContainsItem (Project project, ITaskItem taskItem)
445 BuildItemGroup group;
446 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
449 var item = group.FindItem (taskItem);
453 foreach (string metadataName in evaluatedMetadata.Keys) {
454 string metadataValue = (string)evaluatedMetadata [metadataName];
455 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
459 foreach (string metadataName in item.evaluatedMetadata.Keys) {
460 string metadataValue = (string)item.evaluatedMetadata [metadataName];
461 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
468 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
470 if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
474 BuildItem bi = new BuildItem (this);
475 bi.finalItemSpec = taskitem.ItemSpec;
477 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
478 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
479 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
482 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
485 project.EvaluatedItems.AddItem (bi);
487 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
488 big = new BuildItemGroup (null, project, null, true);
489 project.EvaluatedItemsByName.Add (bi.Name, big);
491 big = project.EvaluatedItemsByName [bi.Name];
497 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
498 big = new BuildItemGroup (null, project, null, true);
499 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
501 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
507 AddAndRemoveMetadata (project, bi);
510 // during item's eval phase, any item refs in this item, have either
511 // already been expanded or are non-existant, so expand can be _false_
513 // during prop's eval phase, this isn't reached, as it parses expressions
514 // with allowItems=false, so no ItemReferences are created at all
516 // at other times, item refs have already been expanded, so expand: false
517 internal string ConvertToString (Expression transform, ExpressionOptions options)
519 return GetItemSpecFromTransform (transform, options);
522 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
525 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
529 internal void Detach ()
532 itemElement.ParentNode.RemoveChild (itemElement);
533 else if (HasParentItem) {
534 if (parent_item.child_items.Count > 1)
536 parent_item.Detach ();
540 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
544 if (transform == null) {
545 if (options == ExpressionOptions.ExpandItemRefs) {
546 // With usual code paths, this will never execute,
547 // but letting this be here, incase BI.ConvertTo*
548 // is called directly
549 Expression expr = new Expression ();
550 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
552 return (string) expr.ConvertTo (parent_item_group.ParentProject,
553 typeof (string), ExpressionOptions.ExpandItemRefs);
555 return finalItemSpec;
558 // Transform, _DONT_ expand itemrefs
559 sb = new StringBuilder ();
560 foreach (object o in transform.Collection) {
562 sb.Append ((string)o);
563 } else if (o is PropertyReference) {
564 sb.Append (((PropertyReference)o).ConvertToString (
565 parent_item_group.ParentProject,
566 ExpressionOptions.DoNotExpandItemRefs));
567 } else if (o is ItemReference) {
568 sb.Append (((ItemReference)o).ConvertToString (
569 parent_item_group.ParentProject,
570 ExpressionOptions.DoNotExpandItemRefs));
571 } else if (o is MetadataReference) {
572 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
575 return sb.ToString ();
579 void SplitParentItem ()
581 BuildItem parent = parent_item;
582 List <BuildItem> list = new List <BuildItem> ();
583 XmlElement insertAfter = parent.itemElement;
584 foreach (BuildItem bi in parent.child_items) {
585 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
586 insertAfter = added.itemElement;
589 parent.parent_item_group.ReplaceWith (parent, list);
590 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
593 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
597 XmlDocument doc = parent.itemElement.OwnerDocument;
598 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
599 newElement.SetAttribute ("Include", child.FinalItemSpec);
600 if (parent.itemElement.HasAttribute ("Condition"))
601 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
602 foreach (XmlNode xn in parent.itemElement)
603 newElement.AppendChild (xn.Clone ());
604 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
606 newParent = new BuildItem (newElement, parent.parent_item_group);
607 newParent.child_items.Add (child);
608 child.parent_item = newParent;
613 public string Condition {
616 return itemElement.GetAttribute ("Condition");
622 itemElement.SetAttribute ("Condition", value);
623 else if (!HasParentItem)
624 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
628 public string Exclude {
631 return itemElement.GetAttribute ("Exclude");
637 itemElement.SetAttribute ("Exclude", value);
639 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
643 public string FinalItemSpec {
644 get { return finalItemSpec; }
647 public string Include {
650 return itemElement.GetAttribute ("Include");
651 else if (HasParentItem)
652 return parent_item.Include;
658 itemElement.SetAttribute ("Include", value);
659 else if (HasParentItem) {
660 if (parent_item.child_items.Count > 1)
662 parent_item.Include = value;
668 internal bool IsDynamic {
669 get { return isDynamic; }
672 internal string Remove {
677 internal bool KeepDuplicates {
678 get { return keepDuplicates; }
679 private set { keepDuplicates = value; }
682 public bool IsImported {
683 get { return isImported; }
689 return itemElement.Name;
690 else if (HasParentItem)
691 return parent_item.Name;
697 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
698 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
699 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
700 foreach (XmlNode xn in itemElement)
701 newElement.AppendChild (xn.Clone ());
702 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
703 itemElement = newElement;
704 } else if (HasParentItem) {
705 if (parent_item.child_items.Count > 1)
707 parent_item.Name = value;
713 internal bool FromXml {
714 get { return itemElement != null; }
717 internal XmlElement XmlElement {
718 get { return itemElement; }
721 internal bool HasParentItem {
722 get { return parent_item != null; }
725 internal BuildItem ParentItem {
726 get { return parent_item; }
729 internal BuildItemGroup ParentItemGroup {
730 get { return parent_item_group; }
731 set { parent_item_group = value; }