Merge pull request #485 from mtausig/master
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / BuildItem.cs
1 //
2 // BuildItem.cs:
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Collections.Specialized;
32 using System.IO;
33 using System.Text;
34 using System.Xml;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37 using Mono.XBuild.Utilities;
38
39 namespace Microsoft.Build.BuildEngine {
40         public class BuildItem {
41
42                 List<BuildItem> child_items;
43                 XmlElement      itemElement;
44                 string          finalItemSpec;
45                 bool            isImported;
46                 string          itemInclude;
47                 string          name;
48                 BuildItemGroup  parent_item_group;
49                 BuildItem       parent_item;
50                 //string                recursiveDir;
51                 IDictionary     evaluatedMetadata;
52                 IDictionary     unevaluatedMetadata;
53
54                 BuildItem ()
55                 {
56                 }
57                 
58                 public BuildItem (string itemName, ITaskItem taskItem)
59                 {
60                         if (taskItem == null)
61                                 throw new ArgumentNullException ("taskItem");
62
63                         this.name = itemName;
64                         this.finalItemSpec = taskItem.ItemSpec;
65                         this.itemInclude = MSBuildUtils.Escape (taskItem.ItemSpec);
66                         this.evaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
67                         this.unevaluatedMetadata = (Hashtable) taskItem.CloneCustomMetadata ();
68                 }
69
70                 public BuildItem (string itemName, string itemInclude)
71                 {
72                         if (itemInclude == null)
73                                 throw new ArgumentNullException ("itemInclude");
74                         if (itemInclude == String.Empty)
75                                 throw new ArgumentException ("Parameter \"itemInclude\" cannot have zero length.");
76
77                         name = itemName;
78                         finalItemSpec = itemInclude;
79                         this.itemInclude = itemInclude;
80                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
81                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
82                 }
83
84                 internal BuildItem (XmlElement itemElement, BuildItemGroup parentItemGroup)
85                 {
86                         child_items = new List<BuildItem> ();
87                         isImported = parentItemGroup.IsImported;
88                         unevaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
89                         evaluatedMetadata = CollectionsUtil.CreateCaseInsensitiveHashtable ();
90                         this.parent_item_group = parentItemGroup;
91                         
92                         this.itemElement = itemElement;
93                         
94                         if (Include == String.Empty)
95                                 throw new InvalidProjectFileException (String.Format ("The required attribute \"Include\" is missing from element <{0}>.", Name));
96                 }
97                 
98                 BuildItem (BuildItem parent)
99                 {
100                         isImported = parent.isImported;
101                         name = parent.Name;
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);
107                 }
108                 
109                 public void CopyCustomMetadataTo (BuildItem destinationItem)
110                 {
111                         if (destinationItem == null)
112                                 throw new ArgumentNullException ("destinationItem");
113
114                         foreach (DictionaryEntry de in unevaluatedMetadata)
115                                 destinationItem.AddMetadata ((string) de.Key, (string) de.Value);
116                 }
117                 
118                 [MonoTODO]
119                 public BuildItem Clone ()
120                 {
121                         return (BuildItem) this.MemberwiseClone ();
122                 }
123
124                 public string GetEvaluatedMetadata (string metadataName)
125                 {
126                         if (ReservedNameUtils.IsReservedMetadataName (metadataName)) {
127                                 string metadata = ReservedNameUtils.GetReservedMetadata (FinalItemSpec, metadataName, evaluatedMetadata);
128                                 return (metadataName.ToLower () == "fullpath") ? MSBuildUtils.Escape (metadata) : metadata;
129                         }
130
131                         if (evaluatedMetadata.Contains (metadataName))
132                                 return (string) evaluatedMetadata [metadataName];
133                         else
134                                 return String.Empty;
135                 }
136
137                 public string GetMetadata (string metadataName)
138                 {
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];
144                         else
145                                 return String.Empty;
146                 }
147                 
148                 public bool HasMetadata (string metadataName)
149                 {
150                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
151                                 return true;
152                         else
153                                 return evaluatedMetadata.Contains (metadataName);
154                 }
155
156                 public void RemoveMetadata (string metadataName)
157                 {
158                         if (metadataName == null)
159                                 throw new ArgumentNullException ("metadataName");
160                         
161                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
162                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
163                                         metadataName));
164
165                         if (FromXml) {
166                                 if (unevaluatedMetadata.Contains (metadataName)) {
167                                         XmlNode node = itemElement [metadataName];
168                                         itemElement.RemoveChild (node);
169                                 }
170                         } else if (HasParentItem) {
171                                 if (parent_item.child_items.Count > 1)
172                                         SplitParentItem ();
173                                 parent_item.RemoveMetadata (metadataName);
174                         } 
175                         
176                         DeleteMetadata (metadataName);
177                 }
178
179                 public void SetMetadata (string metadataName,
180                                          string metadataValue)
181                 {
182                         SetMetadata (metadataName, metadataValue, false);
183                 }
184                 
185                 public void SetMetadata (string metadataName,
186                                          string metadataValue,
187                                          bool treatMetadataValueAsLiteral)
188                 {
189                         if (metadataName == null)
190                                 throw new ArgumentNullException ("metadataName");
191                         
192                         if (metadataValue == null)
193                                 throw new ArgumentNullException ("metadataValue");
194                         
195                         if (ReservedNameUtils.IsReservedMetadataName (metadataName))
196                                 throw new ArgumentException (String.Format ("\"{0}\" is a reserved item meta-data, and cannot be modified or deleted.",
197                                         metadataName));
198
199                         if (treatMetadataValueAsLiteral && !HasParentItem)
200                                 metadataValue = MSBuildUtils.Escape (metadataValue);
201
202                         if (FromXml) {
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);
208                                 } else
209                                         element.InnerText = metadataValue;
210                         } else if (HasParentItem) {
211                                 if (parent_item.child_items.Count > 1)
212                                         SplitParentItem ();
213                                 parent_item.SetMetadata (metadataName, metadataValue, treatMetadataValueAsLiteral);
214                         }
215                         if (FromXml || HasParentItem) {
216                                 parent_item_group.ParentProject.MarkProjectAsDirty ();
217                                 parent_item_group.ParentProject.NeedToReevaluate ();
218                         }
219                         
220                         DeleteMetadata (metadataName);
221                         AddMetadata (metadataName, metadataValue);
222                 }
223
224                 void AddMetadata (string name, string value)
225                 {
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);
231                         } else
232                                 evaluatedMetadata [name] = MSBuildUtils.Unescape (value);
233                                 
234                         unevaluatedMetadata [name] = value;
235                 }
236
237                 void DeleteMetadata (string name)
238                 {
239                         if (evaluatedMetadata.Contains (name))
240                                 evaluatedMetadata.Remove (name);
241                         
242                         if (unevaluatedMetadata.Contains (name))
243                                 unevaluatedMetadata.Remove (name);
244                 }
245
246                 internal void Evaluate (Project project, bool evaluatedTo)
247                 {
248                         // FIXME: maybe make Expression.ConvertTo (null, ...) work as MSBuildUtils.Unescape ()?
249                         if (project == null) {
250                                 this.finalItemSpec = MSBuildUtils.Unescape (Include);
251                                 return;
252                         }
253                         
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);
258                         }
259
260                         DirectoryScanner directoryScanner;
261                         Expression includeExpr, excludeExpr;
262                         ITaskItem[] includes, excludes;
263
264                         includeExpr = new Expression ();
265                         includeExpr.Parse (Include, ParseOptions.AllowItemsNoMetadataAndSplit);
266                         excludeExpr = new Expression ();
267                         excludeExpr.Parse (Exclude, ParseOptions.AllowItemsNoMetadataAndSplit);
268                         
269                         includes = (ITaskItem[]) includeExpr.ConvertTo (project, typeof (ITaskItem[]),
270                                                                 ExpressionOptions.ExpandItemRefs);
271                         excludes = (ITaskItem[]) excludeExpr.ConvertTo (project, typeof (ITaskItem[]),
272                                                                 ExpressionOptions.ExpandItemRefs);
273
274                         this.finalItemSpec = (string) includeExpr.ConvertTo (project, typeof (string),
275                                                         ExpressionOptions.ExpandItemRefs);
276
277                         directoryScanner = new DirectoryScanner ();
278                         
279                         directoryScanner.Includes = includes;
280                         directoryScanner.Excludes = excludes;
281
282                         if (project.FullFileName != String.Empty)
283                                 directoryScanner.BaseDirectory = new DirectoryInfo (Path.GetDirectoryName (project.FullFileName));
284                         else
285                                 directoryScanner.BaseDirectory = new DirectoryInfo (Directory.GetCurrentDirectory ());
286                         
287                         directoryScanner.Scan ();
288                         
289                         foreach (ITaskItem matchedItem in directoryScanner.MatchedItems)
290                                 AddEvaluatedItem (project, evaluatedTo, matchedItem);
291                 }
292                 
293                 void AddEvaluatedItem (Project project, bool evaluatedTo, ITaskItem taskitem)
294                 {
295                         BuildItemGroup big;                     
296                         BuildItem bi = new BuildItem (this);
297                         bi.finalItemSpec = taskitem.ItemSpec;
298
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);
302                         }
303
304                         project.EvaluatedItemsIgnoringCondition.AddItem (bi);
305
306                         if (evaluatedTo) {
307                                 project.EvaluatedItems.AddItem (bi);
308         
309                                 if (!project.EvaluatedItemsByName.ContainsKey (bi.Name)) {
310                                         big = new BuildItemGroup (null, project, null, true);
311                                         project.EvaluatedItemsByName.Add (bi.Name, big);
312                                 } else {
313                                         big = project.EvaluatedItemsByName [bi.Name];
314                                 }
315
316                                 big.AddItem (bi);
317                         }
318
319                         if (!project.EvaluatedItemsByNameIgnoringCondition.ContainsKey (bi.Name)) {
320                                 big = new BuildItemGroup (null, project, null, true);
321                                 project.EvaluatedItemsByNameIgnoringCondition.Add (bi.Name, big);
322                         } else {
323                                 big = project.EvaluatedItemsByNameIgnoringCondition [bi.Name];
324                         }
325
326                         big.AddItem (bi);
327                 }
328                 
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_
331                 //
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
334                 //
335                 // at other times, item refs have already been expanded, so expand: false
336                 internal string ConvertToString (Expression transform, ExpressionOptions options)
337                 {
338                         return GetItemSpecFromTransform (transform, options);
339                 }
340                 
341                 internal ITaskItem ConvertToITaskItem (Expression transform, ExpressionOptions options)
342                 {
343                         TaskItem taskItem;
344                         taskItem = new TaskItem (GetItemSpecFromTransform (transform, options), evaluatedMetadata);
345                         return taskItem;
346                 }
347
348                 internal void Detach ()
349                 {
350                         if (FromXml)
351                                 itemElement.ParentNode.RemoveChild (itemElement);
352                         else if (HasParentItem) {
353                                 if (parent_item.child_items.Count > 1)
354                                         SplitParentItem ();
355                                 parent_item.Detach ();
356                         }
357                 }
358
359                 string GetItemSpecFromTransform (Expression transform, ExpressionOptions options)
360                 {
361                         StringBuilder sb;
362                 
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);
370
371                                         return (string) expr.ConvertTo (parent_item_group.ParentProject,
372                                                         typeof (string), ExpressionOptions.ExpandItemRefs);
373                                 } else {
374                                         return finalItemSpec;
375                                 }
376                         } else {
377                                 // Transform, _DONT_ expand itemrefs
378                                 sb = new StringBuilder ();
379                                 foreach (object o in transform.Collection) {
380                                         if (o is string) {
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));
392                                         }
393                                 }
394                                 return sb.ToString ();
395                         }
396                 }
397
398                 void SplitParentItem ()
399                 {
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;
406                                 list.Add (added);
407                         }
408                         parent.parent_item_group.ReplaceWith (parent, list);
409                         parent.itemElement.ParentNode.RemoveChild (parent.itemElement);                 
410                 }
411
412                 static BuildItem InsertElementAfter (BuildItem parent, BuildItem child, XmlElement insertAfter)
413                 {
414                         BuildItem newParent;
415
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);
424
425                         newParent = new BuildItem (newElement, parent.parent_item_group);
426                         newParent.child_items.Add (child);
427                         child.parent_item = newParent;
428
429                         return newParent;
430                 }
431
432                 public string Condition {
433                         get {
434                                 if (FromXml)
435                                         return itemElement.GetAttribute ("Condition");
436                                 else
437                                         return String.Empty;
438                         }
439                         set {
440                                 if (FromXml)
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.");
444                         }
445                 }
446
447                 public string Exclude {
448                         get {
449                                 if (FromXml)
450                                         return itemElement.GetAttribute ("Exclude");
451                                 else
452                                         return String.Empty;
453                         }
454                         set {
455                                 if (FromXml)
456                                         itemElement.SetAttribute ("Exclude", value);
457                                 else
458                                         throw new InvalidOperationException ("Assigning the \"Exclude\" attribute of a virtual item is not allowed.");
459                         }
460                 }
461
462                 public string FinalItemSpec {
463                         get { return finalItemSpec; }
464                 }
465
466                 public string Include {
467                         get {
468                                 if (FromXml)
469                                         return itemElement.GetAttribute ("Include");
470                                 else if (HasParentItem)
471                                         return parent_item.Include;
472                                 else
473                                         return itemInclude;
474                         }
475                         set {
476                                 if (FromXml)
477                                         itemElement.SetAttribute ("Include", value);
478                                 else if (HasParentItem) {
479                                         if (parent_item.child_items.Count > 1)
480                                                 SplitParentItem ();
481                                         parent_item.Include = value;
482                                 } else
483                                         itemInclude = value;
484                         }
485                 }
486
487                 public bool IsImported {
488                         get { return isImported; }
489                 }
490
491                 public string Name {
492                         get {
493                                 if (FromXml)
494                                         return itemElement.Name;
495                                 else if (HasParentItem)
496                                         return parent_item.Name;
497                                 else
498                                         return name;
499                         }
500                         set {
501                                 if (FromXml) {
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)
511                                                 SplitParentItem ();
512                                         parent_item.Name = value;
513                                 } else
514                                         name = value;
515                         }
516                 }
517                 
518                 internal bool FromXml {
519                         get { return itemElement != null; }
520                 }
521                 
522                 internal bool HasParentItem {
523                         get { return parent_item != null; }
524                 }
525
526                 internal BuildItem ParentItem {
527                         get { return parent_item; }
528                 }
529
530                 internal BuildItemGroup ParentItemGroup {
531                         get { return parent_item_group; }
532                         set { parent_item_group = value; }
533                 }
534         }
535 }