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 string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
171 ? MSBuildUtils.Escape (metadata)
175 if (evaluatedMetadata.Contains (metadataName))
176 return (string) evaluatedMetadata [metadataName];
181 public string GetMetadata (string metadataName)
183 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
184 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
185 return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
186 ? MSBuildUtils.Escape (metadata)
188 } else if (unevaluatedMetadata.Contains (metadataName))
189 return (string) unevaluatedMetadata [metadataName];
194 public bool HasMetadata (string metadataName)
196 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
199 return evaluatedMetadata.Contains (metadataName);
202 public void RemoveMetadata (string metadataName)
204 if (metadataName == null)
205 throw new ArgumentNullException ("metadataName");
207 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
208 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
212 if (unevaluatedMetadata.Contains (metadataName)) {
213 XmlNode node = itemElement [metadataName];
214 itemElement.RemoveChild (node);
216 } else if (HasParentItem) {
217 if (parent_item.child_items.Count > 1)
219 parent_item.RemoveMetadata (metadataName);
222 DeleteMetadata (metadataName);
225 public void SetMetadata (string metadataName,
226 string metadataValue)
228 SetMetadata (metadataName, metadataValue, false);
231 public void SetMetadata (string metadataName,
232 string metadataValue,
233 bool treatMetadataValueAsLiteral)
235 if (metadataName == null)
236 throw new ArgumentNullException ("metadataName");
238 if (metadataValue == null)
239 throw new ArgumentNullException ("metadataValue");
241 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
242 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
245 if (treatMetadataValueAsLiteral && !HasParentItem)
246 metadataValue = MSBuildUtils.Escape (metadataValue);
249 XmlElement element = itemElement [metadataName];
250 if (element == null) {
251 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
252 element.InnerText = metadataValue;
253 itemElement.AppendChild (element);
255 element.InnerText = metadataValue;
256 } else if (HasParentItem) {
257 if (parent_item.child_items.Count > 1)
259 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
261 if (FromXml || HasParentItem) {
262 parent_item_group.ParentProject.MarkProjectAsDirty ();
263 parent_item_group.ParentProject.NeedToReevaluate ();
266 DeleteMetadata (metadataName);
267 AddMetadata (metadataName, metadataValue);
270 void AddMetadata (string name, string value)
272 var options = IsDynamic ?
273 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
275 if (parent_item_group != null) {
276 Expression e = new Expression ();
277 e.Parse (value, options);
278 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
279 typeof (string), ExpressionOptions.ExpandItemRefs);
281 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
283 unevaluatedMetadata [name] = value;
286 void DeleteMetadata (string name)
288 if (evaluatedMetadata.Contains (name))
289 evaluatedMetadata.Remove (name);
291 if (unevaluatedMetadata.Contains (name))
292 unevaluatedMetadata.Remove (name);
295 internal void Evaluate (Project project, bool evaluatedTo)
297 // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
298 if (project == null) {
299 this.finalItemSpec = MSBuildUtils.Unescape (Include);
303 foreach (XmlNode xn in itemElement.ChildNodes) {
304 XmlElement xe = xn as XmlElement;
305 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
306 AddMetadata (xe.Name, xe.InnerText);
313 if (!string.IsNullOrEmpty (Remove)) {
314 RemoveItems (project);
318 if (string.IsNullOrEmpty (Include)) {
319 UpdateMetadata (project);
324 DirectoryScanner directoryScanner;
325 Expression includeExpr, excludeExpr;
326 ITaskItem[] includes, excludes;
328 var options = IsDynamic ?
329 ParseOptions.AllowItemsMetadataAndSplit : ParseOptions.AllowItemsNoMetadataAndSplit;
331 includeExpr = new Expression ();
332 includeExpr.Parse (Include, options);
333 excludeExpr = new Expression ();
334 excludeExpr.Parse (Exclude, options);
336 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
337 ExpressionOptions.ExpandItemRefs);
338 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
339 ExpressionOptions.ExpandItemRefs);
341 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
342 ExpressionOptions.ExpandItemRefs);
344 directoryScanner = new DirectoryScanner ();
346 directoryScanner.Includes = includes;
347 directoryScanner.Excludes = excludes;
349 if (project.FullFileName != String.Empty)
350 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
352 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
354 directoryScanner.Scan ();
356 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
357 AddEvaluatedItem (project, evaluatedTo, matchedItem);
360 bool CheckCondition (Project project)
362 if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
364 if (parent_item != null && !parent_item.CheckCondition (project))
366 return ConditionParser.ParseAndEvaluate (Condition, project);
369 void UpdateMetadata (Project project)
371 BuildItemGroup group;
372 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
375 foreach (BuildItem item in group) {
376 if (!item.CheckCondition (project))
379 foreach (string name in evaluatedMetadata.Keys) {
380 item.SetMetadata (name, (string)evaluatedMetadata [name]);
383 AddAndRemoveMetadata (project, item);
387 void AddAndRemoveMetadata (Project project, BuildItem item)
389 if (!string.IsNullOrEmpty (removeMetadata)) {
390 var removeExpr = new Expression ();
391 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
393 var removeSpec = (string[]) removeExpr.ConvertTo (
394 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
396 foreach (var remove in removeSpec) {
397 item.DeleteMetadata (remove);
401 if (!string.IsNullOrEmpty (keepMetadata)) {
402 var keepExpr = new Expression ();
403 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
405 var keepSpec = (string[]) keepExpr.ConvertTo (
406 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
408 var metadataNames = new string [item.evaluatedMetadata.Count];
409 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
411 foreach (string name in metadataNames) {
412 if (!keepSpec.Contains (name))
413 item.DeleteMetadata (name);
418 void RemoveItems (Project project)
420 BuildItemGroup group;
421 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
424 var removeExpr = new Expression ();
425 removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
427 var removes = (ITaskItem[]) removeExpr.ConvertTo (
428 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
430 var directoryScanner = new DirectoryScanner ();
432 directoryScanner.Includes = removes;
434 if (project.FullFileName != String.Empty)
435 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
437 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
439 directoryScanner.Scan ();
441 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
442 group.RemoveItem (matchedItem);
446 bool ContainsItem (Project project, ITaskItem taskItem)
448 BuildItemGroup group;
449 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
452 var item = group.FindItem (taskItem);
456 foreach (string metadataName in evaluatedMetadata.Keys) {
457 string metadataValue = (string)evaluatedMetadata [metadataName];
458 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
462 foreach (string metadataName in item.evaluatedMetadata.Keys) {
463 string metadataValue = (string)item.evaluatedMetadata [metadataName];
464 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
471 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
473 if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
477 BuildItem bi = new BuildItem (this);
478 bi.finalItemSpec = taskitem.ItemSpec;
480 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
481 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
482 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
485 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
488 project.EvaluatedItems.AddItem (bi);
490 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
491 big = new BuildItemGroup (null, project, null, true);
492 project.EvaluatedItemsByName.Add (bi.Name, big);
494 big = project.EvaluatedItemsByName [bi.Name];
500 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
501 big = new BuildItemGroup (null, project, null, true);
502 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
504 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
510 AddAndRemoveMetadata (project, bi);
513 // during item's eval phase, any item refs in this item, have either
514 // already been expanded or are non-existant, so expand can be _false_
516 // during prop's eval phase, this isn't reached, as it parses expressions
517 // with allowItems=false, so no ItemReferences are created at all
519 // at other times, item refs have already been expanded, so expand: false
520 internal string ConvertToString (Expression transform, ExpressionOptions options)
522 return GetItemSpecFromTransform (transform, options);
525 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
528 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
532 internal void Detach ()
535 itemElement.ParentNode.RemoveChild (itemElement);
536 else if (HasParentItem) {
537 if (parent_item.child_items.Count > 1)
539 parent_item.Detach ();
543 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
547 if (transform == null) {
548 if (options == ExpressionOptions.ExpandItemRefs) {
549 // With usual code paths, this will never execute,
550 // but letting this be here, incase BI.ConvertTo*
551 // is called directly
552 Expression expr = new Expression ();
553 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
555 return (string) expr.ConvertTo (parent_item_group.ParentProject,
556 typeof (string), ExpressionOptions.ExpandItemRefs);
558 return finalItemSpec;
561 // Transform, _DONT_ expand itemrefs
562 sb = new StringBuilder ();
563 foreach (object o in transform.Collection) {
565 sb.Append ((string)o);
566 } else if (o is PropertyReference) {
567 sb.Append (((PropertyReference)o).ConvertToString (
568 parent_item_group.ParentProject,
569 ExpressionOptions.DoNotExpandItemRefs));
570 } else if (o is ItemReference) {
571 sb.Append (((ItemReference)o).ConvertToString (
572 parent_item_group.ParentProject,
573 ExpressionOptions.DoNotExpandItemRefs));
574 } else if (o is MetadataReference) {
575 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
578 return sb.ToString ();
582 void SplitParentItem ()
584 BuildItem parent = parent_item;
585 List <BuildItem> list = new List <BuildItem> ();
586 XmlElement insertAfter = parent.itemElement;
587 foreach (BuildItem bi in parent.child_items) {
588 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
589 insertAfter = added.itemElement;
592 parent.parent_item_group.ReplaceWith (parent, list);
593 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
596 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
600 XmlDocument doc = parent.itemElement.OwnerDocument;
601 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
602 newElement.SetAttribute ("Include", child.FinalItemSpec);
603 if (parent.itemElement.HasAttribute ("Condition"))
604 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
605 foreach (XmlNode xn in parent.itemElement)
606 newElement.AppendChild (xn.Clone ());
607 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
609 newParent = new BuildItem (newElement, parent.parent_item_group);
610 newParent.child_items.Add (child);
611 child.parent_item = newParent;
616 public string Condition {
619 return itemElement.GetAttribute ("Condition");
625 itemElement.SetAttribute ("Condition", value);
626 else if (!HasParentItem)
627 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
631 public string Exclude {
634 return itemElement.GetAttribute ("Exclude");
640 itemElement.SetAttribute ("Exclude", value);
642 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
646 public string FinalItemSpec {
647 get { return finalItemSpec; }
650 public string Include {
653 return itemElement.GetAttribute ("Include");
654 else if (HasParentItem)
655 return parent_item.Include;
661 itemElement.SetAttribute ("Include", value);
662 else if (HasParentItem) {
663 if (parent_item.child_items.Count > 1)
665 parent_item.Include = value;
671 internal bool IsDynamic {
672 get { return isDynamic; }
675 internal string Remove {
680 internal bool KeepDuplicates {
681 get { return keepDuplicates; }
682 private set { keepDuplicates = value; }
685 public bool IsImported {
686 get { return isImported; }
692 return itemElement.Name;
693 else if (HasParentItem)
694 return parent_item.Name;
700 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
701 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
702 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
703 foreach (XmlNode xn in itemElement)
704 newElement.AppendChild (xn.Clone ());
705 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
706 itemElement = newElement;
707 } else if (HasParentItem) {
708 if (parent_item.child_items.Count > 1)
710 parent_item.Name = value;
716 internal bool FromXml {
717 get { return itemElement != null; }
720 internal XmlElement XmlElement {
721 get { return itemElement; }
724 internal bool HasParentItem {
725 get { return parent_item != null; }
728 internal BuildItem ParentItem {
729 get { return parent_item; }
732 internal BuildItemGroup ParentItemGroup {
733 get { return parent_item_group; }
734 set { parent_item_group = value; }