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.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
347 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
349 directoryScanner.Scan ();
351 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
352 AddEvaluatedItem (project, evaluatedTo, matchedItem);
355 bool CheckCondition (Project project)
357 if (parent_item_group != null && !ConditionParser.ParseAndEvaluate (parent_item_group.Condition, project))
359 if (parent_item != null && !parent_item.CheckCondition (project))
361 return ConditionParser.ParseAndEvaluate (Condition, project);
364 void UpdateMetadata (Project project)
366 BuildItemGroup group;
367 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
370 foreach (BuildItem item in group) {
371 if (!item.CheckCondition (project))
374 foreach (string name in evaluatedMetadata.Keys) {
375 item.SetMetadata (name, (string)evaluatedMetadata [name]);
378 AddAndRemoveMetadata (project, item);
382 void AddAndRemoveMetadata (Project project, BuildItem item)
384 if (!string.IsNullOrEmpty (removeMetadata)) {
385 var removeExpr = new Expression ();
386 removeExpr.Parse (removeMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
388 var removeSpec = (string[]) removeExpr.ConvertTo (
389 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
391 foreach (var remove in removeSpec) {
392 item.DeleteMetadata (remove);
396 if (!string.IsNullOrEmpty (keepMetadata)) {
397 var keepExpr = new Expression ();
398 keepExpr.Parse (keepMetadata, ParseOptions.AllowItemsNoMetadataAndSplit);
400 var keepSpec = (string[]) keepExpr.ConvertTo (
401 project, typeof (string[]), ExpressionOptions.ExpandItemRefs);
403 var metadataNames = new string [item.evaluatedMetadata.Count];
404 item.evaluatedMetadata.Keys.CopyTo (metadataNames, 0);
406 foreach (string name in metadataNames) {
407 if (!keepSpec.Contains (name))
408 item.DeleteMetadata (name);
413 void RemoveItems (Project project)
415 BuildItemGroup group;
416 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
419 var removeExpr = new Expression ();
420 removeExpr.Parse (Remove, ParseOptions.AllowItemsNoMetadataAndSplit);
422 var removes = (ITaskItem[]) removeExpr.ConvertTo (
423 project, typeof (ITaskItem[]), ExpressionOptions.ExpandItemRefs);
425 var directoryScanner = new DirectoryScanner ();
427 directoryScanner.Includes = removes;
429 if (project.FullFileName != String.Empty)
430 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
432 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
434 directoryScanner.Scan ();
436 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems) {
437 group.RemoveItem (matchedItem);
441 bool ContainsItem (Project project, ITaskItem taskItem)
443 BuildItemGroup group;
444 if (!project.TryGetEvaluatedItemByNameBatched (Name, out group))
447 var item = group.FindItem (taskItem);
451 foreach (string metadataName in evaluatedMetadata.Keys) {
452 string metadataValue = (string)evaluatedMetadata [metadataName];
453 if (!metadataValue.Equals (item.evaluatedMetadata [metadataName]))
457 foreach (string metadataName in item.evaluatedMetadata.Keys) {
458 string metadataValue = (string)item.evaluatedMetadata [metadataName];
459 if (!metadataValue.Equals (evaluatedMetadata [metadataName]))
466 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
468 if (IsDynamic && evaluatedTo && !KeepDuplicates && ContainsItem (project, taskitem))
472 BuildItem bi = new BuildItem (this);
473 bi.finalItemSpec = taskitem.ItemSpec;
475 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
476 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
477 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
480 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
483 project.EvaluatedItems.AddItem (bi);
485 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
486 big = new BuildItemGroup (null, project, null, true);
487 project.EvaluatedItemsByName.Add (bi.Name, big);
489 big = project.EvaluatedItemsByName [bi.Name];
495 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
496 big = new BuildItemGroup (null, project, null, true);
497 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
499 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
505 AddAndRemoveMetadata (project, bi);
508 // during item's eval phase, any item refs in this item, have either
509 // already been expanded or are non-existant, so expand can be _false_
511 // during prop's eval phase, this isn't reached, as it parses expressions
512 // with allowItems=false, so no ItemReferences are created at all
514 // at other times, item refs have already been expanded, so expand: false
515 internal string ConvertToString (Expression transform, ExpressionOptions options)
517 return GetItemSpecFromTransform (transform, options);
520 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
523 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
527 internal void Detach ()
530 itemElement.ParentNode.RemoveChild (itemElement);
531 else if (HasParentItem) {
532 if (parent_item.child_items.Count > 1)
534 parent_item.Detach ();
538 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
542 if (transform == null) {
543 if (options == ExpressionOptions.ExpandItemRefs) {
544 // With usual code paths, this will never execute,
545 // but letting this be here, incase BI.ConvertTo*
546 // is called directly
547 Expression expr = new Expression ();
548 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
550 return (string) expr.ConvertTo (parent_item_group.ParentProject,
551 typeof (string), ExpressionOptions.ExpandItemRefs);
553 return finalItemSpec;
556 // Transform, _DONT_ expand itemrefs
557 sb = new StringBuilder ();
558 foreach (object o in transform.Collection) {
560 sb.Append ((string)o);
561 } else if (o is PropertyReference) {
562 sb.Append (((PropertyReference)o).ConvertToString (
563 parent_item_group.ParentProject,
564 ExpressionOptions.DoNotExpandItemRefs));
565 } else if (o is ItemReference) {
566 sb.Append (((ItemReference)o).ConvertToString (
567 parent_item_group.ParentProject,
568 ExpressionOptions.DoNotExpandItemRefs));
569 } else if (o is MetadataReference) {
570 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
573 return sb.ToString ();
577 void SplitParentItem ()
579 BuildItem parent = parent_item;
580 List <BuildItem> list = new List <BuildItem> ();
581 XmlElement insertAfter = parent.itemElement;
582 foreach (BuildItem bi in parent.child_items) {
583 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
584 insertAfter = added.itemElement;
587 parent.parent_item_group.ReplaceWith (parent, list);
588 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
591 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
595 XmlDocument doc = parent.itemElement.OwnerDocument;
596 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
597 newElement.SetAttribute ("Include", child.FinalItemSpec);
598 if (parent.itemElement.HasAttribute ("Condition"))
599 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
600 foreach (XmlNode xn in parent.itemElement)
601 newElement.AppendChild (xn.Clone ());
602 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
604 newParent = new BuildItem (newElement, parent.parent_item_group);
605 newParent.child_items.Add (child);
606 child.parent_item = newParent;
611 public string Condition {
614 return itemElement.GetAttribute ("Condition");
620 itemElement.SetAttribute ("Condition", value);
621 else if (!HasParentItem)
622 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
626 public string Exclude {
629 return itemElement.GetAttribute ("Exclude");
635 itemElement.SetAttribute ("Exclude", value);
637 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
641 public string FinalItemSpec {
642 get { return finalItemSpec; }
645 public string Include {
648 return itemElement.GetAttribute ("Include");
649 else if (HasParentItem)
650 return parent_item.Include;
656 itemElement.SetAttribute ("Include", value);
657 else if (HasParentItem) {
658 if (parent_item.child_items.Count > 1)
660 parent_item.Include = value;
666 internal bool IsDynamic {
667 get { return isDynamic; }
670 internal string Remove {
675 internal bool KeepDuplicates {
676 get { return keepDuplicates; }
677 private set { keepDuplicates = value; }
680 public bool IsImported {
681 get { return isImported; }
687 return itemElement.Name;
688 else if (HasParentItem)
689 return parent_item.Name;
695 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
696 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
697 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
698 foreach (XmlNode xn in itemElement)
699 newElement.AppendChild (xn.Clone ());
700 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
701 itemElement = newElement;
702 } else if (HasParentItem) {
703 if (parent_item.child_items.Count > 1)
705 parent_item.Name = value;
711 internal bool FromXml {
712 get { return itemElement != null; }
715 internal XmlElement XmlElement {
716 get { return itemElement; }
719 internal bool HasParentItem {
720 get { return parent_item != null; }
723 internal BuildItem ParentItem {
724 get { return parent_item; }
727 internal BuildItemGroup ParentItemGroup {
728 get { return parent_item_group; }
729 set { parent_item_group = value; }