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 string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
129 ? MSBuildUtils.Escape (metadata)
133 if (evaluatedMetadata.Contains (metadataName))
134 return (string) evaluatedMetadata [metadataName];
139 public string GetMetadata (string metadataName)
141 if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
142 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, unevaluatedMetadata);
143 return string.Equals (metadataName, "fullpath", StringComparison.OrdinalIgnoreCase)
144 ? MSBuildUtils.Escape (metadata)
146 } else if (unevaluatedMetadata.Contains (metadataName))
147 return (string) unevaluatedMetadata [metadataName];
152 public bool HasMetadata (string metadataName)
154 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
157 return evaluatedMetadata.Contains (metadataName);
160 public void RemoveMetadata (string metadataName)
162 if (metadataName == null)
163 throw new ArgumentNullException ("metadataName");
165 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
166 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
170 if (unevaluatedMetadata.Contains (metadataName)) {
171 XmlNode node = itemElement [metadataName];
172 itemElement.RemoveChild (node);
174 } else if (HasParentItem) {
175 if (parent_item.child_items.Count > 1)
177 parent_item.RemoveMetadata (metadataName);
180 DeleteMetadata (metadataName);
183 public void SetMetadata (string metadataName,
184 string metadataValue)
186 SetMetadata (metadataName, metadataValue, false);
189 public void SetMetadata (string metadataName,
190 string metadataValue,
191 bool treatMetadataValueAsLiteral)
193 if (metadataName == null)
194 throw new ArgumentNullException ("metadataName");
196 if (metadataValue == null)
197 throw new ArgumentNullException ("metadataValue");
199 if (ReservedNameUtils.IsReservedMetadataName (metadataName))
200 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
203 if (treatMetadataValueAsLiteral && !HasParentItem)
204 metadataValue = MSBuildUtils.Escape (metadataValue);
207 XmlElement element = itemElement [metadataName];
208 if (element == null) {
209 element = itemElement.OwnerDocument.CreateElement (metadataName, Project.XmlNamespace);
210 element.InnerText = metadataValue;
211 itemElement.AppendChild (element);
213 element.InnerText = metadataValue;
214 } else if (HasParentItem) {
215 if (parent_item.child_items.Count > 1)
217 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
219 if (FromXml || HasParentItem) {
220 parent_item_group.ParentProject.MarkProjectAsDirty ();
221 parent_item_group.ParentProject.NeedToReevaluate ();
224 DeleteMetadata (metadataName);
225 AddMetadata (metadataName, metadataValue);
228 void AddMetadata (string name, string value)
230 if (parent_item_group != null) {
231 Expression e = new Expression ();
232 e.Parse (value, ParseOptions.AllowItemsNoMetadataAndSplit);
233 evaluatedMetadata [name] = (string) e.ConvertTo (parent_item_group.ParentProject,
234 typeof (string), ExpressionOptions.ExpandItemRefs);
236 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
238 unevaluatedMetadata [name] = value;
241 void DeleteMetadata (string name)
243 if (evaluatedMetadata.Contains (name))
244 evaluatedMetadata.Remove (name);
246 if (unevaluatedMetadata.Contains (name))
247 unevaluatedMetadata.Remove (name);
250 internal void Evaluate (Project project, bool evaluatedTo)
252 // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
253 if (project == null) {
254 this.finalItemSpec = MSBuildUtils.Unescape (Include);
258 foreach (XmlNode xn in itemElement.ChildNodes) {
259 XmlElement xe = xn as XmlElement;
260 if (xe != null && ConditionParser.ParseAndEvaluate (xe.GetAttribute ("Condition"), project))
261 AddMetadata (xe.Name, xe.InnerText);
264 DirectoryScanner directoryScanner;
265 Expression includeExpr, excludeExpr;
266 ITaskItem[] includes, excludes;
268 includeExpr = new Expression ();
269 includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
270 excludeExpr = new Expression ();
271 excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
273 includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
274 ExpressionOptions.ExpandItemRefs);
275 excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
276 ExpressionOptions.ExpandItemRefs);
278 this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
279 ExpressionOptions.ExpandItemRefs);
281 directoryScanner = new DirectoryScanner ();
283 directoryScanner.Includes = includes;
284 directoryScanner.Excludes = excludes;
286 if (project.FullFileName != String.Empty)
287 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
289 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
291 directoryScanner.Scan ();
293 foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
294 AddEvaluatedItem (project, evaluatedTo, matchedItem);
297 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
300 BuildItem bi = new BuildItem (this);
301 bi.finalItemSpec = taskitem.ItemSpec;
303 foreach (DictionaryEntry de in taskitem.CloneCustomMetadata ()) {
304 bi.unevaluatedMetadata.Add ((string) de.Key, (string) de.Value);
305 bi.evaluatedMetadata.Add ((string) de.Key, (string) de.Value);
308 project.EvaluatedItemsIgnoringCondition.AddItem (bi);
311 project.EvaluatedItems.AddItem (bi);
313 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
314 big = new BuildItemGroup (null, project, null, true);
315 project.EvaluatedItemsByName.Add (bi.Name, big);
317 big = project.EvaluatedItemsByName [bi.Name];
323 if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
324 big = new BuildItemGroup (null, project, null, true);
325 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
327 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
333 // during item's eval phase, any item refs in this item, have either
334 // already been expanded or are non-existant, so expand can be _false_
336 // during prop's eval phase, this isn't reached, as it parses expressions
337 // with allowItems=false, so no ItemReferences are created at all
339 // at other times, item refs have already been expanded, so expand: false
340 internal string ConvertToString (Expression transform, ExpressionOptions options)
342 return GetItemSpecFromTransform (transform, options);
345 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
348 taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
352 internal void Detach ()
355 itemElement.ParentNode.RemoveChild (itemElement);
356 else if (HasParentItem) {
357 if (parent_item.child_items.Count > 1)
359 parent_item.Detach ();
363 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
367 if (transform == null) {
368 if (options == ExpressionOptions.ExpandItemRefs) {
369 // With usual code paths, this will never execute,
370 // but letting this be here, incase BI.ConvertTo*
371 // is called directly
372 Expression expr = new Expression ();
373 expr.Parse (finalItemSpec, ParseOptions.AllowItemsNoMetadataAndSplit);
375 return (string) expr.ConvertTo (parent_item_group.ParentProject,
376 typeof (string), ExpressionOptions.ExpandItemRefs);
378 return finalItemSpec;
381 // Transform, _DONT_ expand itemrefs
382 sb = new StringBuilder ();
383 foreach (object o in transform.Collection) {
385 sb.Append ((string)o);
386 } else if (o is PropertyReference) {
387 sb.Append (((PropertyReference)o).ConvertToString (
388 parent_item_group.ParentProject,
389 ExpressionOptions.DoNotExpandItemRefs));
390 } else if (o is ItemReference) {
391 sb.Append (((ItemReference)o).ConvertToString (
392 parent_item_group.ParentProject,
393 ExpressionOptions.DoNotExpandItemRefs));
394 } else if (o is MetadataReference) {
395 sb.Append (GetMetadata (((MetadataReference)o).MetadataName));
398 return sb.ToString ();
402 void SplitParentItem ()
404 BuildItem parent = parent_item;
405 List <BuildItem> list = new List <BuildItem> ();
406 XmlElement insertAfter = parent.itemElement;
407 foreach (BuildItem bi in parent.child_items) {
408 BuildItem added = InsertElementAfter (parent, bi, insertAfter);
409 insertAfter = added.itemElement;
412 parent.parent_item_group.ReplaceWith (parent, list);
413 parent.itemElement.ParentNode.RemoveChild (parent.itemElement);
416 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
420 XmlDocument doc = parent.itemElement.OwnerDocument;
421 XmlElement newElement = doc.CreateElement (child.Name, Project.XmlNamespace);
422 newElement.SetAttribute ("Include", child.FinalItemSpec);
423 if (parent.itemElement.HasAttribute ("Condition"))
424 newElement.SetAttribute ("Condition", parent.itemElement.GetAttribute ("Condition"));
425 foreach (XmlNode xn in parent.itemElement)
426 newElement.AppendChild (xn.Clone ());
427 parent.itemElement.ParentNode.InsertAfter (newElement, insertAfter);
429 newParent = new BuildItem (newElement, parent.parent_item_group);
430 newParent.child_items.Add (child);
431 child.parent_item = newParent;
436 public string Condition {
439 return itemElement.GetAttribute ("Condition");
445 itemElement.SetAttribute ("Condition", value);
446 else if (!HasParentItem)
447 throw new InvalidOperationException ("Cannot set a condition on an object not represented by an XML element in the project file.");
451 public string Exclude {
454 return itemElement.GetAttribute ("Exclude");
460 itemElement.SetAttribute ("Exclude", value);
462 throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
466 public string FinalItemSpec {
467 get { return finalItemSpec; }
470 public string Include {
473 return itemElement.GetAttribute ("Include");
474 else if (HasParentItem)
475 return parent_item.Include;
481 itemElement.SetAttribute ("Include", value);
482 else if (HasParentItem) {
483 if (parent_item.child_items.Count > 1)
485 parent_item.Include = value;
491 public bool IsImported {
492 get { return isImported; }
498 return itemElement.Name;
499 else if (HasParentItem)
500 return parent_item.Name;
506 XmlElement newElement = itemElement.OwnerDocument.CreateElement (value, Project.XmlNamespace);
507 newElement.SetAttribute ("Include", itemElement.GetAttribute ("Include"));
508 newElement.SetAttribute ("Condition", itemElement.GetAttribute ("Condition"));
509 foreach (XmlNode xn in itemElement)
510 newElement.AppendChild (xn.Clone ());
511 itemElement.ParentNode.ReplaceChild (newElement, itemElement);
512 itemElement = newElement;
513 } else if (HasParentItem) {
514 if (parent_item.child_items.Count > 1)
516 parent_item.Name = value;
522 internal bool FromXml {
523 get { return itemElement != null; }
526 internal bool HasParentItem {
527 get { return parent_item != null; }
530 internal BuildItem ParentItem {
531 get { return parent_item; }
534 internal BuildItemGroup ParentItemGroup {
535 get { return parent_item_group; }
536 set { parent_item_group = value; }