Now we can directly evaluate boolean expression with jay-based parser.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Evaluation / Project.cs
1 //
2 // Project.cs
3 //
4 // Author:
5 //   Leszek Ciesielski (skolima@gmail.com)
6 //   Rolf Bjarne Kvinge (rolf@xamarin.com)
7 //   Atsushi Enomoto (atsushi@xamarin.com)
8 //
9 // (C) 2011 Leszek Ciesielski
10 // Copyright (C) 2011,2013 Xamarin Inc. (http://www.xamarin.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System;
33 using System.Collections.Generic;
34 using System.Diagnostics;
35 using System.IO;
36 using System.Linq;
37 using System.Text;
38 using System.Xml;
39 using Microsoft.Build.Construction;
40 using Microsoft.Build.Internal;
41 using Microsoft.Build.Execution;
42 using Microsoft.Build.Framework;
43 using Microsoft.Build.Logging;
44 using System.Collections;
45
46 namespace Microsoft.Build.Evaluation
47 {
48         [DebuggerDisplay ("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties="
49         + "{data.globalProperties.Count} #Properties={data.Properties.Count} #ItemTypes="
50         + "{data.ItemTypes.Count} #ItemDefinitions={data.ItemDefinitions.Count} #Items="
51         + "{data.Items.Count} #Targets={data.Targets.Count}")]
52         public class Project
53         {
54                 public Project (XmlReader xml)
55                         : this (ProjectRootElement.Create (xml))
56                 {
57                 }
58
59                 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
60                                               string toolsVersion)
61                         : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion)
62                 {
63                 }
64
65                 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
66                                               string toolsVersion, ProjectCollection projectCollection)
67                         : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection)
68                 {
69                 }
70
71                 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
72                                               string toolsVersion, ProjectCollection projectCollection,
73                                               ProjectLoadSettings loadSettings)
74                         : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection, loadSettings)
75                 {
76                 }
77
78                 public Project (ProjectRootElement xml) : this (xml, null, null)
79                 {
80                 }
81
82                 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
83                                               string toolsVersion)
84                         : this (xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
85                 {
86                 }
87
88                 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
89                                               string toolsVersion, ProjectCollection projectCollection)
90                         : this (xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
91                 {
92                 }
93
94                 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
95                                               string toolsVersion, ProjectCollection projectCollection,
96                                               ProjectLoadSettings loadSettings)
97                 {
98                         if (projectCollection == null)
99                                 throw new ArgumentNullException ("projectCollection");
100                         this.Xml = xml;
101                         this.GlobalProperties = globalProperties ?? new Dictionary<string, string> ();
102                         this.ToolsVersion = toolsVersion;
103                         this.ProjectCollection = projectCollection;
104                         this.load_settings = loadSettings;
105
106                         Initialize ();
107                 }
108
109                 public Project (string projectFile)
110                         : this (projectFile, null, null)
111                 {
112                 }
113
114                 public Project (string projectFile, IDictionary<string, string> globalProperties,
115                                 string toolsVersion)
116                 : this (projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection, ProjectLoadSettings.Default)
117                 {
118                 }
119
120                 public Project (string projectFile, IDictionary<string, string> globalProperties,
121                                 string toolsVersion, ProjectCollection projectCollection)
122                 : this (projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
123                 {
124                 }
125
126                 public Project (string projectFile, IDictionary<string, string> globalProperties,
127                                 string toolsVersion, ProjectCollection projectCollection,
128                                 ProjectLoadSettings loadSettings)
129                         : this (ProjectRootElement.Create (projectFile), globalProperties, toolsVersion, projectCollection, loadSettings)
130                 {
131                 }
132
133                 ProjectLoadSettings load_settings;
134
135                 public IDictionary<string, string> GlobalProperties { get; private set; }
136
137                 public ProjectCollection ProjectCollection { get; private set; }
138
139                 public string ToolsVersion { get; private set; }
140
141                 public ProjectRootElement Xml { get; private set; }
142
143                 string dir_path;
144                 Dictionary<string, ProjectItemDefinition> item_definitions;
145                 List<ResolvedImport> raw_imports;
146                 List<ProjectItem> raw_items;
147                 List<ProjectItem> all_evaluated_items;
148                 List<string> item_types;
149                 List<ProjectProperty> properties;
150                 Dictionary<string, ProjectTargetInstance> targets;
151
152                 void Initialize ()
153                 {
154                         dir_path = Directory.GetCurrentDirectory ();
155                         raw_imports = new List<ResolvedImport> ();
156                         item_definitions = new Dictionary<string, ProjectItemDefinition> ();
157                         item_types = new List<string> ();
158                         properties = new List<ProjectProperty> ();
159                         targets = new Dictionary<string, ProjectTargetInstance> ();
160                         raw_items = new List<ProjectItem> ();
161                         
162                         ProcessXml ();
163                 }
164                 
165                 static readonly char [] item_sep = {';'};
166                 
167                 void ProcessXml ()
168                 {
169                         foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ())
170                                 this.properties.Add (new EnvironmentProjectProperty (this, (string)p.Key, (string)p.Value));
171                         foreach (var p in GlobalProperties)
172                                 this.properties.Add (new GlobalProjectProperty (this, p.Key, p.Value));
173                         var tools = ProjectCollection.GetToolset (this.ToolsVersion) ?? ProjectCollection.GetToolset (this.ProjectCollection.DefaultToolsVersion);
174                         foreach (var p in ReservedProjectProperty.GetReservedProperties (tools, this))
175                                 this.properties.Add (p);
176                         foreach (var p in EnvironmentProjectProperty.GetWellKnownProperties (this))
177                                 this.properties.Add (p);
178
179                         all_evaluated_items = new List<ProjectItem> ();
180                         
181                         // First step: evaluate Properties
182                         foreach (var child in Xml.Children) {
183                                 var pge = child as ProjectPropertyGroupElement;
184                                 if (pge != null && ShouldInclude (pge.Condition))
185                                         foreach (var p in pge.Properties)
186                                                 // do not allow overwriting reserved or well-known properties by user
187                                                 if (!this.properties.Any (_ => (_.IsReservedProperty || _.IsWellKnownProperty) && _.Name.Equals (p.Name, StringComparison.InvariantCultureIgnoreCase)))
188                                                         if (ShouldInclude (p.Condition))
189                                                                 this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal));
190                         }
191                         
192                         foreach (var child in Xml.Children) {
193                                 var ige = child as ProjectItemGroupElement;
194                                 if (ige != null) {
195                                         foreach (var p in ige.Items) {
196                                                 var inc = ExpandString (p.Include);
197                                                 foreach (var each in inc.Split (item_sep, StringSplitOptions.RemoveEmptyEntries)) {
198                                                         var item = new ProjectItem (this, p, each);
199                                                         this.raw_items.Add (item);
200                                                         if (ShouldInclude (ige.Condition) && ShouldInclude (p.Condition))
201                                                                 all_evaluated_items.Add (item);
202                                                 }
203                                         }
204                                 }
205                                 var def = child as ProjectItemDefinitionGroupElement;
206                                 if (def != null) {
207                                         foreach (var p in def.ItemDefinitions) {
208                                                 if (ShouldInclude (p.Condition)) {
209                                                         ProjectItemDefinition existing;
210                                                         if (!item_definitions.TryGetValue (p.ItemType, out existing))
211                                                                 item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType)));
212                                                         existing.AddItems (p);
213                                                 }
214                                         }
215                                 }
216                         }
217                         all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
218                 }
219
220                 public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
221                 {
222                         return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
223                 }
224
225                 public void RemoveItems (IEnumerable<ProjectItem> items)
226                 {
227                         var removal = new List<ProjectItem> (items);
228                         foreach (var item in removal) {
229                                 var parent = item.Xml.Parent;
230                                 parent.RemoveChild (item.Xml);
231                                 if (parent.Count == 0)
232                                         parent.Parent.RemoveChild (parent);
233                         }
234                 }
235
236                 static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> ();
237
238                 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude)
239                 {
240                         return AddItem (itemType, unevaluatedInclude, empty_metadata);
241                 }
242
243                 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude,
244                                 IEnumerable<KeyValuePair<string, string>> metadata)
245                 {
246                         // FIXME: needs several check that AddItemFast() does not process (see MSDN for details).
247
248                         return AddItemFast (itemType, unevaluatedInclude, metadata);
249                 }
250
251                 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude)
252                 {
253                         return AddItemFast (itemType, unevaluatedInclude, empty_metadata);
254                 }
255
256                 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude,
257                                                                      IEnumerable<KeyValuePair<string, string>> metadata)
258                 {
259                         throw new NotImplementedException ();
260                 }
261
262                 public bool Build ()
263                 {
264                         return Build (Xml.DefaultTargets.Split (';'));
265                 }
266
267                 public bool Build (IEnumerable<ILogger> loggers)
268                 {
269                         return Build (Xml.DefaultTargets.Split (';'), loggers);
270                 }
271
272                 public bool Build (string target)
273                 {
274                         return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target});
275                 }
276
277                 public bool Build (string[] targets)
278                 {
279                         return Build (targets, new ILogger [0]);
280                 }
281
282                 public bool Build (ILogger logger)
283                 {
284                         return Build (Xml.DefaultTargets.Split (';'), new ILogger [] {logger});
285                 }
286
287                 public bool Build (string[] targets, IEnumerable<ILogger> loggers)
288                 {
289                         return Build (targets, loggers, new ForwardingLoggerRecord [0]);
290                 }
291
292                 public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
293                 {
294                         return Build (Xml.DefaultTargets.Split (';'), loggers, remoteLoggers);
295                 }
296
297                 public bool Build (string target, IEnumerable<ILogger> loggers)
298                 {
299                         return Build (new string [] { target }, loggers);
300                 }
301
302                 public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
303                 {
304                         // Unlike ProjectInstance.Build(), there is no place to fill outputs by targets, so ignore them
305                         // (i.e. we don't use the overload with output).
306                         //
307                         // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly.
308                         return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers);
309                 }
310
311                 public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
312                 {
313                         return Build (new string [] { target }, loggers, remoteLoggers);
314                 }
315
316                 public ProjectInstance CreateProjectInstance ()
317                 {
318                         var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection);
319                         // FIXME: maybe fill other properties to the result.
320                         return ret;
321                 }
322                 
323                 bool ShouldInclude (string unexpandedValue)
324                 {
325                         return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this, null).EvaluateAsBoolean (unexpandedValue);
326                 }
327
328                 public string ExpandString (string unexpandedValue)
329                 {
330                         return ExpandString (unexpandedValue, null);
331                 }
332                 
333                 string ExpandString (string unexpandedValue, string replacementForMissingStuff)
334                 {
335                         return new ExpressionEvaluator (this, replacementForMissingStuff).Evaluate (unexpandedValue);
336                 }
337
338                 public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
339                 {
340                         return ProjectCollection.Escape (item.EvaluatedInclude);
341                 }
342
343                 public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item)
344                 {
345                         throw new NotImplementedException ();
346                 }
347
348                 public ICollection<ProjectItem> GetItems (string itemType)
349                 {
350                         return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
351                 }
352
353                 public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude)
354                 {
355                         return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase)));
356                 }
357
358                 public IEnumerable<ProjectElement> GetLogicalProject ()
359                 {
360                         throw new NotImplementedException ();
361                 }
362
363                 public static string GetMetadataValueEscaped (ProjectMetadata metadatum)
364                 {
365                         return ProjectCollection.Escape (metadatum.EvaluatedValue);
366                 }
367
368                 public static string GetMetadataValueEscaped (ProjectItem item, string name)
369                 {
370                         var md = item.GetMetadata (name);
371                         return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
372                 }
373
374                 public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name)
375                 {
376                         var md = item.Metadata.FirstOrDefault (m => m.Name == name);
377                         return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
378                 }
379
380                 public string GetPropertyValue (string name)
381                 {
382                         var prop = GetProperty (name);
383                         return prop != null ? prop.EvaluatedValue : string.Empty;
384                 }
385
386                 public static string GetPropertyValueEscaped (ProjectProperty property)
387                 {
388                         // WTF happens here.
389                         return property.EvaluatedValue;
390                 }
391
392                 public ProjectProperty GetProperty (string name)
393                 {
394                         return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
395                 }
396
397                 public void MarkDirty ()
398                 {
399                         if (!DisableMarkDirty)
400                                 is_dirty = true;
401                 }
402
403                 public void ReevaluateIfNecessary ()
404                 {
405                         throw new NotImplementedException ();
406                 }
407
408                 public bool RemoveGlobalProperty (string name)
409                 {
410                         throw new NotImplementedException ();
411                 }
412
413                 public bool RemoveItem (ProjectItem item)
414                 {
415                         throw new NotImplementedException ();
416                 }
417
418                 public bool RemoveProperty (ProjectProperty property)
419                 {
420                         var removed = properties.FirstOrDefault (p => p.Name == property.Name);
421                         if (removed == null)
422                                 return false;
423                         properties.Remove (removed);
424                         return true;
425                 }
426
427                 public void Save ()
428                 {
429                         Xml.Save ();
430                 }
431
432                 public void Save (TextWriter writer)
433                 {
434                         Xml.Save (writer);
435                 }
436
437                 public void Save (string path)
438                 {
439                         Save (path, Encoding.Default);
440                 }
441
442                 public void Save (Encoding encoding)
443                 {
444                         Save (FullPath, encoding);
445                 }
446
447                 public void Save (string path, Encoding encoding)
448                 {
449                         using (var writer = new StreamWriter (path, false, encoding))
450                                 Save (writer);
451                 }
452
453                 public void SaveLogicalProject (TextWriter writer)
454                 {
455                         throw new NotImplementedException ();
456                 }
457
458                 public bool SetGlobalProperty (string name, string escapedValue)
459                 {
460                         throw new NotImplementedException ();
461                 }
462
463                 public ProjectProperty SetProperty (string name, string unevaluatedValue)
464                 {
465                         var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue);
466                         properties.Add (p);
467                         return p;
468                 }
469
470                 public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata {
471                         get { throw new NotImplementedException (); }
472                 }
473
474                 public ICollection<ProjectItem> AllEvaluatedItems {
475                         get { return all_evaluated_items; }
476                 }
477
478                 public ICollection<ProjectProperty> AllEvaluatedProperties {
479                         get { throw new NotImplementedException (); }
480                 }
481
482                 public IDictionary<string, List<string>> ConditionedProperties {
483                         get {
484                                 // this property returns different instances every time.
485                                 var dic = new Dictionary<string, List<string>> ();
486                                 
487                                 // but I dunno HOW this evaluates
488                                 
489                                 throw new NotImplementedException ();
490                         }
491                 }
492
493                 public string DirectoryPath {
494                         get { return dir_path; }
495                 }
496
497                 public bool DisableMarkDirty { get; set; }
498
499                 public int EvaluationCounter {
500                         get { throw new NotImplementedException (); }
501                 }
502
503                 public string FullPath {
504                         get { return Xml.FullPath; }
505                         set { Xml.FullPath = value; }
506                 }
507
508                 public IList<ResolvedImport> Imports {
509                         get { throw new NotImplementedException (); }
510                 }
511
512                 public IList<ResolvedImport> ImportsIncludingDuplicates {
513                         get { return raw_imports; }
514                 }
515
516                 public bool IsBuildEnabled {
517                         get { return ProjectCollection.IsBuildEnabled; }
518                 }
519
520                 bool is_dirty;
521                 public bool IsDirty {
522                         get { return is_dirty; }
523                 }
524
525                 public IDictionary<string, ProjectItemDefinition> ItemDefinitions {
526                         get { return item_definitions; }
527                 }
528
529                 [MonoTODO ("should be different from AllEvaluatedItems")]
530                 public ICollection<ProjectItem> Items {
531                         get { return AllEvaluatedItems; }
532                 }
533
534                 public ICollection<ProjectItem> ItemsIgnoringCondition {
535                         get { return raw_items; }
536                 }
537
538                 public ICollection<string> ItemTypes {
539                         get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
540                 }
541
542                 public ICollection<ProjectProperty> Properties {
543                         get { return properties; }
544                 }
545
546                 public bool SkipEvaluation { get; set; }
547
548                 #if NET_4_5
549                 public
550                 #else
551                 internal
552                 #endif
553                 IDictionary<string, ProjectTargetInstance> Targets {
554                         get { return targets; }
555                 }
556                 
557                 // These are required for reserved property, represents dynamically changing property values.
558                 // This should resolve to either the project file path or that of the imported file.
559                 Stack<string> files_in_process = new Stack<string> ();
560                 internal string GetEvaluationTimeThisFileDirectory ()
561                 {
562                         return Path.GetDirectoryName (GetEvaluationTimeThisFile ()) + Path.DirectorySeparatorChar;
563                 }
564
565                 internal string GetEvaluationTimeThisFile ()
566                 {
567                         return files_in_process.Count > 0 ? files_in_process.Peek () : FullPath;
568                 }
569         }
570 }