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.
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37 using Mono.XBuild.Utilities;
39 namespace Microsoft.Build.BuildEngine {
40 public class BuildItem {
42 List<BuildItem> child_items;
43 XmlElement itemElement;
48 BuildItemGroup parent_item_group;
49 BuildItem parent_item;
50 //string recursiveDir;
51 IDictionary evaluatedMetadata;
52 IDictionary unevaluatedMetadata;
58 public BuildItem (string itemName, ITaskItem taskItem)
61 throw new ArgumentNullException ("taskItem");
64 this.finalItemSpec = taskItem.ItemSpec;
65 this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
66 this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
67 this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
70 public BuildItem (string itemName, string itemInclude)
72 if (itemInclude == null)
73 throw new ArgumentNullException ("itemInclude");
74 if (itemInclude == String.Empty)
75 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
78 finalItemSpec = itemInclude;
79 this.itemInclude = itemInclude;
80 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
81 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
84 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
86 child_items = new List<BuildItem> ();
87 isImported = parentItemGroup.IsImported;
88 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
89 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
90 this.parent_item_group = parentItemGroup;
92 this.itemElement = itemElement;
94 if (Include == String.Empty)
95 throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
98 BuildItem (BuildItem parent)
100 isImported = parent.isImported;
102 parent_item = parent;
103 parent_item.child_items.Add (this);
104 parent_item_group = parent.parent_item_group;
105 unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.unevaluatedMetadata);
106 evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable (parent.evaluatedMetadata);
109 public void CopyCustomMetadataTo (BuildItem destinationItem)
111 if (destinationItem == null)
112 throw new ArgumentNullException ("destinationItem");
114 foreach (DictionaryEntry de in unevaluatedMetadata)
115 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
119 public BuildItem Clone ()
121 return (BuildItem) this.MemberwiseClone ();
124 public string GetEvaluatedMetadata (string metadataName)
126 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
127 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
128 return (metadataName.ToLower () == "fullpath") ? MSBuildUtils.Escape (metadata) : metadata;
131 if (evaluatedMetadata.Contains (metadataName))
132 return (string) evaluatedMetadata [metadataName];
137 public string GetMetadata (string metadataName)
139 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
140 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
141 return (metadataName.ToLower () == "fullpath") ? MSBuildUtils.Escape (metadata) : metadata;
142 } else if (unevaluatedMetadata.Contains (metadataName))
143 return (string) unevaluatedMetadata [metadataName];
148 public bool HasMetadata (string metadataName)
150 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
153 return evaluatedMetadata.Contains (metadataName);
156 public void RemoveMetadata (string metadataName)
158 if (metadataName == null)
159 throw new ArgumentNullException ("metadataName");
161 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
162 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
166 if (unevaluatedMetadata.Contains (metadataName)) {
167 XmlNode node = itemElement [metadataName];
168 itemElement.RemoveChild (node);
170 } else if (HasParentItem) {
171 if (parent_item.child_items.Count > 1)
173 parent_item.RemoveMetadata (metadataName);
176 DeleteMetadata (metadataName);
179 public void SetMetadata (string metadataName,
180 string metadataValue)
182 SetMetadata (metadataName, metadataValue, false);
185 public void SetMetadata (string metadataName,
186 string metadataValue,
187 bool treatMetadataValueAsLiteral)
189 if (metadataName == null)
190 throw new ArgumentNullException ("metadataName");
192 if (metadataValue == null)
193 throw new ArgumentNullException ("metadataValue");
195 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
196 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
199 if (treatMetadataValueAsLiteral && !HasParentItem)
200 metadataValue = MSBuildUtils.Escape (metadataValue);
203 XmlElement element = itemElement [metadataName];
204 if (element == null) {
205 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
206 element.InnerText = metadataValue;
207 itemElement.AppendChild (element);
209 element.InnerText = metadataValue;
210 } else if (HasParentItem) {
211 if (parent_item.child_items.Count > 1)
213 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
215 if (FromXml || HasParentItem) {
216 parent_item_group.ParentProject.MarkProjectAsDirty ();
217 parent_item_group.ParentProject.NeedToReevaluate ();
220 DeleteMetadata (metadataName);
221 AddMetadata (metadataName, metadataValue);
224 void AddMetadata (string name, string value)
226 if (parent_item_group != null) {
227 Expression e = new Expression ();
228 e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
229 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
230 typeof (string), ExpressionOptions.ExpandItemRefs);
232 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
234 unevaluatedMetadata [name] = value;
237 void DeleteMetadata (string name)
239 if (evaluatedMetadata.Contains (name))
240 evaluatedMetadata.Remove (name);
242 if (unevaluatedMetadata.Contains (name))
243 unevaluatedMetadata.Remove (name);
246 internal void Evaluate (Project project, bool evaluatedTo)
248 // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
249 if (project == null) {
250 this.finalItemSpec = MSBuildUtils.Unescape (Include);
254 foreach (XmlNode xn in itemElement.ChildNodes) {
255 XmlElement xe = xn as XmlElement;
256 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
257 AddMetadata (xe.Name, xe.InnerText);
260 DirectoryScanner directoryScanner;
261 Expression includeExpr, excludeExpr;
262 ITaskItem[] includes, excludes;
264 includeExpr = new Expression ();
265 includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
266 excludeExpr = new Expression ();
267 excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
269 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
270 ExpressionOptions.ExpandItemRefs);
271 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
272 ExpressionOptions.ExpandItemRefs);
274 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
275 ExpressionOptions.ExpandItemRefs);
277 directoryScanner = new DirectoryScanner ();
279 directoryScanner.Includes = includes;
280 directoryScanner.Excludes = excludes;
282 if (project.FullFileName != String.Empty)
283 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
285 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
287 directoryScanner.Scan ();
289 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
290 AddEvaluatedItem (project, evaluatedTo, matchedItem);
293 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
296 BuildItem bi = new BuildItem (this);
297 bi.finalItemSpec = taskitem.ItemSpec;
299 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
300 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
301 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
304 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
307 project.EvaluatedItems.AddItem (bi);
309 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
310 big = new BuildItemGroup (null, project, null, true);
311 project.EvaluatedItemsByName.Add (bi.Name, big);
313 big = project.EvaluatedItemsByName [bi.Name];
319 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
320 big = new BuildItemGroup (null, project, null, true);
321 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
323 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
329 // during item's eval phase, any item refs in this item, have either
330 // already been expanded or are non-existant, so expand can be _false_
332 // during prop's eval phase, this isn't reached, as it parses expressions
333 // with allowItems=false, so no ItemReferences are created at all
335 // at other times, item refs have already been expanded, so expand: false
336 internal string ConvertToString (Expression transform, ExpressionOptions options)
338 return GetItemSpecFromTransform (transform, options);
341 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
344 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
348 internal void Detach ()
351 itemElement.ParentNode.RemoveChild (itemElement);
352 else if (HasParentItem) {
353 if (parent_item.child_items.Count > 1)
355 parent_item.Detach ();
359 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
363 if (transform == null) {
364 if (options == ExpressionOptions.ExpandItemRefs) {
365 // With usual code paths, this will never execute,
366 // but letting this be here, incase BI.ConvertTo*
367 // is called directly
368 Expression expr = new Expression ();
369 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
371 return (string) expr.ConvertTo (parent_item_group.ParentProject,
372 typeof (string), ExpressionOptions.ExpandItemRefs);
374 return finalItemSpec;
377 // Transform, _DONT_ expand itemrefs
378 sb = new StringBuilder ();
379 foreach (object o in transform.Collection) {
381 sb.Append ((string)o);
382 } else if (o is PropertyReference) {
383 sb.Append (((PropertyReference)o).ConvertToString (
384 parent_item_group.ParentProject,
385 ExpressionOptions.DoNotExpandItemRefs));
386 } else if (o is ItemReference) {
387 sb.Append (((ItemReference)o).ConvertToString (
388 parent_item_group.ParentProject,
389 ExpressionOptions.DoNotExpandItemRefs));
390 } else if (o is MetadataReference) {
391 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
394 return sb.ToString ();
398 void SplitParentItem ()
400 BuildItem parent = parent_item;
401 List <BuildItem> list = new List <BuildItem> ();
402 XmlElement insertAfter = parent.itemElement;
403 foreach (BuildItem bi in parent.child_items) {
404 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
405 insertAfter = added.itemElement;
408 parent.parent_item_group.ReplaceWith (parent, list);
409 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
412 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
416 XmlDocument doc = parent.itemElement.OwnerDocument;
417 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
418 newElement.SetAttribute ("Include", child.FinalItemSpec);
419 if (parent.itemElement.HasAttribute ("Condition"))
420 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
421 foreach (XmlNode xn in parent.itemElement)
422 newElement.AppendChild (xn.Clone ());
423 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
425 newParent = new BuildItem (newElement, parent.parent_item_group);
426 newParent.child_items.Add (child);
427 child.parent_item = newParent;
432 public string Condition {
435 return itemElement.GetAttribute ("Condition");
441 itemElement.SetAttribute ("Condition", value);
442 else if (!HasParentItem)
443 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
447 public string Exclude {
450 return itemElement.GetAttribute ("Exclude");
456 itemElement.SetAttribute ("Exclude", value);
458 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
462 public string FinalItemSpec {
463 get { return finalItemSpec; }
466 public string Include {
469 return itemElement.GetAttribute ("Include");
470 else if (HasParentItem)
471 return parent_item.Include;
477 itemElement.SetAttribute ("Include", value);
478 else if (HasParentItem) {
479 if (parent_item.child_items.Count > 1)
481 parent_item.Include = value;
487 public bool IsImported {
488 get { return isImported; }
494 return itemElement.Name;
495 else if (HasParentItem)
496 return parent_item.Name;
502 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
503 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
504 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
505 foreach (XmlNode xn in itemElement)
506 newElement.AppendChild (xn.Clone ());
507 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
508 itemElement = newElement;
509 } else if (HasParentItem) {
510 if (parent_item.child_items.Count > 1)
512 parent_item.Name = value;
518 internal bool FromXml {
519 get { return itemElement != null; }
522 internal bool HasParentItem {
523 get { return parent_item != null; }
526 internal BuildItem ParentItem {
527 get { return parent_item; }
530 internal BuildItemGroup ParentItemGroup {
531 get { return parent_item_group; }
532 set { parent_item_group = value; }