implement well-known and reserved properties.
[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)
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                                                         this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal));
189                         }
190                         
191                         foreach (var child in Xml.Children) {
192                                 var ige = child as ProjectItemGroupElement;
193                                 if (ige != null) {
194                                         foreach (var p in ige.Items) {
195                                                 var inc = ExpandString (p.Include);
196                                                 foreach (var each in inc.Split (item_sep, StringSplitOptions.RemoveEmptyEntries)) {
197                                                         var item = new ProjectItem (this, p, each);
198                                                         this.raw_items.Add (item);
199                                                         if (ShouldInclude (ige.Condition) && ShouldInclude (p.Condition))
200                                                                 all_evaluated_items.Add (item);
201                                                 }
202                                         }
203                                 }
204                                 var def = child as ProjectItemDefinitionGroupElement;
205                                 if (def != null) {
206                                         foreach (var p in def.ItemDefinitions) {
207                                                 if (ShouldInclude (p.Condition)) {
208                                                         ProjectItemDefinition existing;
209                                                         if (!item_definitions.TryGetValue (p.ItemType, out existing))
210                                                                 item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType)));
211                                                         existing.AddItems (p);
212                                                 }
213                                         }
214                                 }
215                         }
216                         all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
217                 }
218
219                 public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
220                 {
221                         return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
222                 }
223
224                 public void RemoveItems (IEnumerable<ProjectItem> items)
225                 {
226                         var removal = new List<ProjectItem> (items);
227                         foreach (var item in removal) {
228                                 var parent = item.Xml.Parent;
229                                 parent.RemoveChild (item.Xml);
230                                 if (parent.Count == 0)
231                                         parent.Parent.RemoveChild (parent);
232                         }
233                 }
234
235                 static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> ();
236
237                 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude)
238                 {
239                         return AddItem (itemType, unevaluatedInclude, empty_metadata);
240                 }
241
242                 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude,
243                                 IEnumerable<KeyValuePair<string, string>> metadata)
244                 {
245                         // FIXME: needs several check that AddItemFast() does not process (see MSDN for details).
246
247                         return AddItemFast (itemType, unevaluatedInclude, metadata);
248                 }
249
250                 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude)
251                 {
252                         return AddItemFast (itemType, unevaluatedInclude, empty_metadata);
253                 }
254
255                 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude,
256                                                                      IEnumerable<KeyValuePair<string, string>> metadata)
257                 {
258                         throw new NotImplementedException ();
259                 }
260
261                 public bool Build ()
262                 {
263                         return Build (Xml.DefaultTargets.Split (';'));
264                 }
265
266                 public bool Build (IEnumerable<ILogger> loggers)
267                 {
268                         return Build (Xml.DefaultTargets.Split (';'), loggers);
269                 }
270
271                 public bool Build (string target)
272                 {
273                         return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target});
274                 }
275
276                 public bool Build (string[] targets)
277                 {
278                         return Build (targets, new ILogger [0]);
279                 }
280
281                 public bool Build (ILogger logger)
282                 {
283                         return Build (Xml.DefaultTargets.Split (';'), new ILogger [] {logger});
284                 }
285
286                 public bool Build (string[] targets, IEnumerable<ILogger> loggers)
287                 {
288                         return Build (targets, loggers, new ForwardingLoggerRecord [0]);
289                 }
290
291                 public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
292                 {
293                         return Build (Xml.DefaultTargets.Split (';'), loggers, remoteLoggers);
294                 }
295
296                 public bool Build (string target, IEnumerable<ILogger> loggers)
297                 {
298                         return Build (new string [] { target }, loggers);
299                 }
300
301                 public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
302                 {
303                         // Unlike ProjectInstance.Build(), there is no place to fill outputs by targets, so ignore them
304                         // (i.e. we don't use the overload with output).
305                         //
306                         // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly.
307                         return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers);
308                 }
309
310                 public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
311                 {
312                         return Build (new string [] { target }, loggers, remoteLoggers);
313                 }
314
315                 public ProjectInstance CreateProjectInstance ()
316                 {
317                         var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection);
318                         // FIXME: maybe fill other properties to the result.
319                         return ret;
320                 }
321                 
322                 bool ShouldInclude (string unexpandedValue)
323                 {
324                         return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this).EvaluateAsBoolean (unexpandedValue);
325                 }
326
327                 public string ExpandString (string unexpandedValue)
328                 {
329                         return new ExpressionEvaluator (this).Evaluate (unexpandedValue);
330                 }
331
332                 public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
333                 {
334                         return ProjectCollection.Escape (item.EvaluatedInclude);
335                 }
336
337                 public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item)
338                 {
339                         throw new NotImplementedException ();
340                 }
341
342                 public ICollection<ProjectItem> GetItems (string itemType)
343                 {
344                         return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
345                 }
346
347                 public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude)
348                 {
349                         return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase)));
350                 }
351
352                 public IEnumerable<ProjectElement> GetLogicalProject ()
353                 {
354                         throw new NotImplementedException ();
355                 }
356
357                 public static string GetMetadataValueEscaped (ProjectMetadata metadatum)
358                 {
359                         return ProjectCollection.Escape (metadatum.EvaluatedValue);
360                 }
361
362                 public static string GetMetadataValueEscaped (ProjectItem item, string name)
363                 {
364                         var md = item.GetMetadata (name);
365                         return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
366                 }
367
368                 public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name)
369                 {
370                         var md = item.Metadata.FirstOrDefault (m => m.Name == name);
371                         return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
372                 }
373
374                 public string GetPropertyValue (string name)
375                 {
376                         var prop = GetProperty (name);
377                         return prop != null ? prop.EvaluatedValue : string.Empty;
378                 }
379
380                 public static string GetPropertyValueEscaped (ProjectProperty property)
381                 {
382                         return property.EvaluatedValue;
383                 }
384
385                 public ProjectProperty GetProperty (string name)
386                 {
387                         return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
388                 }
389
390                 public void MarkDirty ()
391                 {
392                         if (!DisableMarkDirty)
393                                 is_dirty = true;
394                 }
395
396                 public void ReevaluateIfNecessary ()
397                 {
398                         throw new NotImplementedException ();
399                 }
400
401                 public bool RemoveGlobalProperty (string name)
402                 {
403                         throw new NotImplementedException ();
404                 }
405
406                 public bool RemoveItem (ProjectItem item)
407                 {
408                         throw new NotImplementedException ();
409                 }
410
411                 public bool RemoveProperty (ProjectProperty property)
412                 {
413                         var removed = properties.FirstOrDefault (p => p.Name == property.Name);
414                         if (removed == null)
415                                 return false;
416                         properties.Remove (removed);
417                         return true;
418                 }
419
420                 public void Save ()
421                 {
422                         Xml.Save ();
423                 }
424
425                 public void Save (TextWriter writer)
426                 {
427                         Xml.Save (writer);
428                 }
429
430                 public void Save (string path)
431                 {
432                         Save (path, Encoding.Default);
433                 }
434
435                 public void Save (Encoding encoding)
436                 {
437                         Save (FullPath, encoding);
438                 }
439
440                 public void Save (string path, Encoding encoding)
441                 {
442                         using (var writer = new StreamWriter (path, false, encoding))
443                                 Save (writer);
444                 }
445
446                 public void SaveLogicalProject (TextWriter writer)
447                 {
448                         throw new NotImplementedException ();
449                 }
450
451                 public bool SetGlobalProperty (string name, string escapedValue)
452                 {
453                         throw new NotImplementedException ();
454                 }
455
456                 public ProjectProperty SetProperty (string name, string unevaluatedValue)
457                 {
458                         var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue);
459                         properties.Add (p);
460                         return p;
461                 }
462
463                 public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata {
464                         get { throw new NotImplementedException (); }
465                 }
466
467                 public ICollection<ProjectItem> AllEvaluatedItems {
468                         get { return all_evaluated_items; }
469                 }
470
471                 public ICollection<ProjectProperty> AllEvaluatedProperties {
472                         get { throw new NotImplementedException (); }
473                 }
474
475                 public IDictionary<string, List<string>> ConditionedProperties {
476                         get {
477                                 // this property returns different instances every time.
478                                 var dic = new Dictionary<string, List<string>> ();
479                                 
480                                 // but I dunno HOW this evaluates
481                                 
482                                 throw new NotImplementedException ();
483                         }
484                 }
485
486                 public string DirectoryPath {
487                         get { return dir_path; }
488                 }
489
490                 public bool DisableMarkDirty { get; set; }
491
492                 public int EvaluationCounter {
493                         get { throw new NotImplementedException (); }
494                 }
495
496                 public string FullPath {
497                         get { return Xml.FullPath; }
498                         set { Xml.FullPath = value; }
499                 }
500
501                 public IList<ResolvedImport> Imports {
502                         get { throw new NotImplementedException (); }
503                 }
504
505                 public IList<ResolvedImport> ImportsIncludingDuplicates {
506                         get { return raw_imports; }
507                 }
508
509                 public bool IsBuildEnabled {
510                         get { return ProjectCollection.IsBuildEnabled; }
511                 }
512
513                 bool is_dirty;
514                 public bool IsDirty {
515                         get { return is_dirty; }
516                 }
517
518                 public IDictionary<string, ProjectItemDefinition> ItemDefinitions {
519                         get { return item_definitions; }
520                 }
521
522                 [MonoTODO ("should be different from AllEvaluatedItems")]
523                 public ICollection<ProjectItem> Items {
524                         get { return AllEvaluatedItems; }
525                 }
526
527                 public ICollection<ProjectItem> ItemsIgnoringCondition {
528                         get { return raw_items; }
529                 }
530
531                 public ICollection<string> ItemTypes {
532                         get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
533                 }
534
535                 public ICollection<ProjectProperty> Properties {
536                         get { return properties; }
537                 }
538
539                 public bool SkipEvaluation { get; set; }
540
541                 #if NET_4_5
542                 public
543                 #else
544                 internal
545                 #endif
546                 IDictionary<string, ProjectTargetInstance> Targets {
547                         get { return targets; }
548                 }
549                 
550                 // These are required for reserved property, represents dynamically changing property values.
551                 // This should resolve to either the project file path or that of the imported file.
552                 Stack<string> files_in_process = new Stack<string> ();
553                 internal string GetEvaluationTimeThisFileDirectory ()
554                 {
555                         return Path.GetDirectoryName (GetEvaluationTimeThisFile ()) + Path.DirectorySeparatorChar;
556                 }
557
558                 internal string GetEvaluationTimeThisFile ()
559                 {
560                         return files_in_process.Count > 0 ? files_in_process.Peek () : FullPath;
561                 }
562         }
563 }