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 SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, false);
233 void SetMetadata (string metadataName,
234 string metadataValue,
235 bool treatMetadataValueAsLiteral,
236 bool fromDynamicItem)
238 if (metadataName == null)
239 throw new ArgumentNullException ("metadataName");
241 if (metadataValue == null)
242 throw new ArgumentNullException ("metadataValue");
244 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
245 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
248 if (treatMetadataValueAsLiteral && !HasParentItem)
249 metadataValue = MSBuildUtils.Escape (metadataValue);
252 XmlElement element = itemElement [metadataName];
253 if (element == null) {
254 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
255 element.InnerText = metadataValue;
256 itemElement.AppendChild (element);
258 element.InnerText = metadataValue;
259 } else if (HasParentItem) {
260 if (parent_item.child_items.Count > 1)
262 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral, fromDynamicItem);
265 // We don't want to reevalute the project for dynamic items
266 if (!fromDynamicItem && !IsDynamic && (FromXml || HasParentItem)) {
267 parent_item_group.ParentProject.MarkProjectAsDirty ();
268 parent_item_group.ParentProject.NeedToReevaluate ();
271 DeleteMetadata (metadataName);
272 AddMetadata (metadataName, metadataValue);
275 void AddMetadata (string name, string value)
277 var options = IsDynamic ?
278 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
280 if (parent_item_group != null) {
281 Expression e = new Expression ();
282 e.Parse (value, options);
283 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
284 typeof (string), ExpressionOptions.ExpandItemRefs);
286 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
288 unevaluatedMetadata [name] = value;
291 void DeleteMetadata (string name)
293 if (evaluatedMetadata.Contains (name))
294 evaluatedMetadata.Remove (name);
296 if (unevaluatedMetadata.Contains (name))
297 unevaluatedMetadata.Remove (name);
300 internal void Evaluate (Project project, bool evaluatedTo)
302 // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
303 if (project == null) {
304 this.finalItemSpec = MSBuildUtils.Unescape (Include);
308 foreach (XmlNode xn in itemElement.ChildNodes) {
309 XmlElement xe = xn as XmlElement;
310 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
311 AddMetadata (xe.Name, xe.InnerText);
318 if (!string.IsNullOrEmpty (Remove)) {
319 RemoveItems (project);
323 if (string.IsNullOrEmpty (Include)) {
324 UpdateMetadata (project);
329 DirectoryScanner directoryScanner;
330 Expression includeExpr, excludeExpr;
331 ITaskItem[] includes, excludes;
333 var options = IsDynamic ?
334 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
336 includeExpr = new Expression ();
337 includeExpr.Parse (Include, options);
338 excludeExpr = new Expression ();
339 excludeExpr.Parse (Exclude, options);
341 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
342 ExpressionOptions.ExpandItemRefs);
343 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
344 ExpressionOptions.ExpandItemRefs);
346 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
347 ExpressionOptions.ExpandItemRefs);
349 directoryScanner = new DirectoryScanner ();
351 directoryScanner.Includes = includes;
352 directoryScanner.Excludes = excludes;
354 if (project.FullFileName != String.Empty) {
355 directoryScanner.ProjectFile = project.ThisFileFullPath;
356 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
359 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
361 directoryScanner.Scan ();
363 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
364 AddEvaluatedItem (project, evaluatedTo, matchedItem);
367 bool CheckCondition (Project project)
369 if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
371 if (parent_item != null && !parent_item.CheckCondition (project))
373 return ConditionParser.ParseAndEvaluate (Condition, project);
376 void UpdateMetadata (Project project)
378 BuildItemGroup group;
379 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
382 foreach (BuildItem item in group) {
383 if (!item.CheckCondition (project))
386 foreach (string name in evaluatedMetadata.Keys) {
387 item.SetMetadata (name, (string)evaluatedMetadata [name], false, IsDynamic);
390 AddAndRemoveMetadata (project, item);
394 void AddAndRemoveMetadata (Project project, BuildItem item)
396 if (!string.IsNullOrEmpty (removeMetadata)) {
397 var removeExpr = new Expression ();
398 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
400 var removeSpec = (string[]) removeExpr.ConvertTo (
401 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
403 foreach (var remove in removeSpec) {
404 item.DeleteMetadata (remove);
408 if (!string.IsNullOrEmpty (keepMetadata)) {
409 var keepExpr = new Expression ();
410 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
412 var keepSpec = (string[]) keepExpr.ConvertTo (
413 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
415 var metadataNames = new string [item.evaluatedMetadata.Count];
416 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
418 foreach (string name in metadataNames) {
419 if (!keepSpec.Contains (name))
420 item.DeleteMetadata (name);
425 void RemoveItems (Project project)
427 BuildItemGroup group;
428 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
431 var removeExpr = new Expression ();
432 removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
434 var removes = (ITaskItem[]) removeExpr.ConvertTo (
435 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
437 var directoryScanner = new DirectoryScanner ();
439 directoryScanner.Includes = removes;
441 if (project.FullFileName != String.Empty)
442 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
444 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
446 directoryScanner.Scan ();
448 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
449 group.RemoveItem (matchedItem);
453 bool ContainsItem (Project project, ITaskItem taskItem)
455 BuildItemGroup group;
456 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
459 var item = group.FindItem (taskItem);
463 foreach (string metadataName in evaluatedMetadata.Keys) {
464 string metadataValue = (string)evaluatedMetadata [metadataName];
465 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
469 foreach (string metadataName in item.evaluatedMetadata.Keys) {
470 string metadataValue = (string)item.evaluatedMetadata [metadataName];
471 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
478 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
480 if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
484 BuildItem bi = new BuildItem (this);
485 bi.finalItemSpec = taskitem.ItemSpec;
487 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
488 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
489 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
492 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
495 project.EvaluatedItems.AddItem (bi);
497 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
498 big = new BuildItemGroup (null, project, null, true);
499 project.EvaluatedItemsByName.Add (bi.Name, big);
501 big = project.EvaluatedItemsByName [bi.Name];
507 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
508 big = new BuildItemGroup (null, project, null, true);
509 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
511 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
517 AddAndRemoveMetadata (project, bi);
520 // during item's eval phase, any item refs in this item, have either
521 // already been expanded or are non-existant, so expand can be _false_
523 // during prop's eval phase, this isn't reached, as it parses expressions
524 // with allowItems=false, so no ItemReferences are created at all
526 // at other times, item refs have already been expanded, so expand: false
527 internal string ConvertToString (Expression transform, ExpressionOptions options)
529 return GetItemSpecFromTransform (transform, options);
532 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
535 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
539 internal void Detach ()
542 itemElement.ParentNode.RemoveChild (itemElement);
543 else if (HasParentItem) {
544 if (parent_item.child_items.Count > 1)
546 parent_item.Detach ();
550 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
554 if (transform == null) {
555 if (options == ExpressionOptions.ExpandItemRefs) {
556 // With usual code paths, this will never execute,
557 // but letting this be here, incase BI.ConvertTo*
558 // is called directly
559 Expression expr = new Expression ();
560 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
562 return (string) expr.ConvertTo (parent_item_group.ParentProject,
563 typeof (string), ExpressionOptions.ExpandItemRefs);
565 return finalItemSpec;
568 // Transform, _DONT_ expand itemrefs
569 sb = new StringBuilder ();
570 foreach (object o in transform.Collection) {
572 sb.Append ((string)o);
573 } else if (o is PropertyReference) {
574 sb.Append (((PropertyReference)o).ConvertToString (
575 parent_item_group.ParentProject,
576 ExpressionOptions.DoNotExpandItemRefs));
577 } else if (o is ItemReference) {
578 sb.Append (((ItemReference)o).ConvertToString (
579 parent_item_group.ParentProject,
580 ExpressionOptions.DoNotExpandItemRefs));
581 } else if (o is MetadataReference) {
582 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
585 return sb.ToString ();
589 void SplitParentItem ()
591 BuildItem parent = parent_item;
592 List <BuildItem> list = new List <BuildItem> ();
593 XmlElement insertAfter = parent.itemElement;
594 foreach (BuildItem bi in parent.child_items) {
595 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
596 insertAfter = added.itemElement;
599 parent.parent_item_group.ReplaceWith (parent, list);
600 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
603 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
607 XmlDocument doc = parent.itemElement.OwnerDocument;
608 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
609 newElement.SetAttribute ("Include", child.FinalItemSpec);
610 if (parent.itemElement.HasAttribute ("Condition"))
611 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
612 foreach (XmlNode xn in parent.itemElement)
613 newElement.AppendChild (xn.Clone ());
614 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
616 newParent = new BuildItem (newElement, parent.parent_item_group);
617 newParent.child_items.Add (child);
618 child.parent_item = newParent;
623 public string Condition {
626 return itemElement.GetAttribute ("Condition");
632 itemElement.SetAttribute ("Condition", value);
633 else if (!HasParentItem)
634 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
638 public string Exclude {
641 return itemElement.GetAttribute ("Exclude");
647 itemElement.SetAttribute ("Exclude", value);
649 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
653 public string FinalItemSpec {
654 get { return finalItemSpec; }
657 public string Include {
660 return itemElement.GetAttribute ("Include");
661 else if (HasParentItem)
662 return parent_item.Include;
668 itemElement.SetAttribute ("Include", value);
669 else if (HasParentItem) {
670 if (parent_item.child_items.Count > 1)
672 parent_item.Include = value;
678 internal bool IsDynamic {
679 get { return isDynamic; }
682 internal string Remove {
687 internal bool KeepDuplicates {
688 get { return keepDuplicates; }
689 private set { keepDuplicates = value; }
692 public bool IsImported {
693 get { return isImported; }
699 return itemElement.Name;
700 else if (HasParentItem)
701 return parent_item.Name;
707 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
708 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
709 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
710 foreach (XmlNode xn in itemElement)
711 newElement.AppendChild (xn.Clone ());
712 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
713 itemElement = newElement;
714 } else if (HasParentItem) {
715 if (parent_item.child_items.Count > 1)
717 parent_item.Name = value;
723 internal bool FromXml {
724 get { return itemElement != null; }
727 internal XmlElement XmlElement {
728 get { return itemElement; }
731 internal bool HasParentItem {
732 get { return parent_item != null; }
735 internal BuildItem ParentItem {
736 get { return parent_item; }
739 internal BuildItemGroup ParentItemGroup {
740 get { return parent_item_group; }
741 set { parent_item_group = value; }