5 // Leszek Ciesielski (skolima@gmail.com)
6 // Rolf Bjarne Kvinge (rolf@xamarin.com)
7 // Atsushi Enomoto (atsushi@xamarin.com)
9 // (C) 2011 Leszek Ciesielski
10 // Copyright (C) 2011,2013 Xamarin Inc. (http://www.xamarin.com)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
33 using System.Collections.Generic;
34 using System.Diagnostics;
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 using Microsoft.Build.Exceptions;
47 namespace Microsoft.Build.Evaluation
49 [DebuggerDisplay ("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties="
50 + "{data.globalProperties.Count} #Properties={data.Properties.Count} #ItemTypes="
51 + "{data.ItemTypes.Count} #ItemDefinitions={data.ItemDefinitions.Count} #Items="
52 + "{data.Items.Count} #Targets={data.Targets.Count}")]
55 public Project (XmlReader xml)
56 : this (ProjectRootElement.Create (xml))
60 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
62 : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion)
66 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
67 string toolsVersion, ProjectCollection projectCollection)
68 : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection)
72 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
73 string toolsVersion, ProjectCollection projectCollection,
74 ProjectLoadSettings loadSettings)
75 : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection, loadSettings)
79 public Project (ProjectRootElement xml) : this (xml, null, null)
83 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
85 : this (xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
89 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
90 string toolsVersion, ProjectCollection projectCollection)
91 : this (xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
95 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
96 string toolsVersion, ProjectCollection projectCollection,
97 ProjectLoadSettings loadSettings)
99 if (projectCollection == null)
100 throw new ArgumentNullException ("projectCollection");
102 this.GlobalProperties = globalProperties ?? new Dictionary<string, string> ();
103 this.ToolsVersion = toolsVersion;
104 this.ProjectCollection = projectCollection;
105 this.load_settings = loadSettings;
110 Project (ProjectRootElement imported, Project parent)
113 this.GlobalProperties = parent.GlobalProperties;
114 this.ToolsVersion = parent.ToolsVersion;
115 this.ProjectCollection = parent.ProjectCollection;
116 this.load_settings = parent.load_settings;
121 public Project (string projectFile)
122 : this (projectFile, null, null)
126 public Project (string projectFile, IDictionary<string, string> globalProperties,
128 : this (projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection, ProjectLoadSettings.Default)
132 public Project (string projectFile, IDictionary<string, string> globalProperties,
133 string toolsVersion, ProjectCollection projectCollection)
134 : this (projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
138 public Project (string projectFile, IDictionary<string, string> globalProperties,
139 string toolsVersion, ProjectCollection projectCollection,
140 ProjectLoadSettings loadSettings)
141 : this (ProjectRootElement.Create (projectFile), globalProperties, toolsVersion, projectCollection, loadSettings)
145 ProjectLoadSettings load_settings;
147 public IDictionary<string, string> GlobalProperties { get; private set; }
149 public ProjectCollection ProjectCollection { get; private set; }
151 public string ToolsVersion { get; private set; }
153 public ProjectRootElement Xml { get; private set; }
156 Dictionary<string, ProjectItemDefinition> item_definitions;
157 List<ResolvedImport> raw_imports;
158 List<ProjectItem> raw_items;
159 List<ProjectItem> all_evaluated_items;
160 List<string> item_types;
161 List<ProjectProperty> properties;
162 Dictionary<string, ProjectTargetInstance> targets;
164 void Initialize (Project parent)
166 dir_path = Directory.GetCurrentDirectory ();
167 raw_imports = new List<ResolvedImport> ();
168 item_definitions = new Dictionary<string, ProjectItemDefinition> ();
169 item_types = new List<string> ();
170 targets = new Dictionary<string, ProjectTargetInstance> ();
171 raw_items = new List<ProjectItem> ();
173 // FIXME: this is likely hack. Test ImportedProject.Properties to see what exactly should happen.
174 if (parent != null) {
175 properties = parent.properties;
177 properties = new List<ProjectProperty> ();
179 foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ())
180 this.properties.Add (new EnvironmentProjectProperty (this, (string)p.Key, (string)p.Value));
181 foreach (var p in GlobalProperties)
182 this.properties.Add (new GlobalProjectProperty (this, p.Key, p.Value));
183 var tools = ProjectCollection.GetToolset (this.ToolsVersion) ?? ProjectCollection.GetToolset (this.ProjectCollection.DefaultToolsVersion);
184 foreach (var p in ReservedProjectProperty.GetReservedProperties (tools, this))
185 this.properties.Add (p);
186 foreach (var p in EnvironmentProjectProperty.GetWellKnownProperties (this))
187 this.properties.Add (p);
193 static readonly char [] item_sep = {';'};
195 void ProcessXml (Project parent)
197 // this needs to be initialized here (regardless of that items won't be evaluated at property evaluation;
198 // Conditions could incorrectly reference items and lack of this list causes NRE.
199 all_evaluated_items = new List<ProjectItem> ();
201 // property evaluation happens couple of times.
202 // At first step, all non-imported properties are evaluated TOO, WHILE those properties are being evaluated.
203 // This means, Include and IncludeGroup elements with Condition attribute MAY contain references to
204 // properties and they will be expanded.
205 var elements = EvaluatePropertiesAndImports (Xml.Children).ToArray (); // ToArray(): to not lazily evaluate elements.
207 // next, evaluate items
208 EvaluateItems (elements);
211 IEnumerable<ProjectElement> EvaluatePropertiesAndImports (IEnumerable<ProjectElement> elements)
213 // First step: evaluate Properties
214 foreach (var child in elements) {
216 var pge = child as ProjectPropertyGroupElement;
217 if (pge != null && ShouldInclude (pge.Condition))
218 foreach (var p in pge.Properties)
219 // do not allow overwriting reserved or well-known properties by user
220 if (!this.properties.Any (_ => (_.IsReservedProperty || _.IsWellKnownProperty) && _.Name.Equals (p.Name, StringComparison.InvariantCultureIgnoreCase)))
221 if (ShouldInclude (p.Condition))
222 this.properties.Add (new XmlProjectProperty (this, p, PropertyType.Normal, ProjectCollection.OngoingImports.Any ()));
224 var ige = child as ProjectImportGroupElement;
225 if (ige != null && ShouldInclude (ige.Condition)) {
226 foreach (var incc in ige.Imports) {
227 foreach (var e in Import (incc))
231 var inc = child as ProjectImportElement;
232 if (inc != null && ShouldInclude (inc.Condition))
233 foreach (var e in Import (inc))
238 void EvaluateItems (IEnumerable<ProjectElement> elements)
240 foreach (var child in elements) {
241 var ige = child as ProjectItemGroupElement;
243 foreach (var p in ige.Items) {
244 var inc = ExpandString (p.Include);
245 foreach (var each in inc.Split (item_sep, StringSplitOptions.RemoveEmptyEntries)) {
246 var item = new ProjectItem (this, p, each);
247 this.raw_items.Add (item);
248 if (ShouldInclude (ige.Condition) && ShouldInclude (p.Condition))
249 all_evaluated_items.Add (item);
253 var def = child as ProjectItemDefinitionGroupElement;
255 foreach (var p in def.ItemDefinitions) {
256 if (ShouldInclude (p.Condition)) {
257 ProjectItemDefinition existing;
258 if (!item_definitions.TryGetValue (p.ItemType, out existing))
259 item_definitions.Add (p.ItemType, (existing = new ProjectItemDefinition (this, p.ItemType)));
260 existing.AddItems (p);
265 all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
268 IEnumerable<ProjectElement> Import (ProjectImportElement import)
270 string dir = GetEvaluationTimeThisFileDirectory ();
271 string path = Path.IsPathRooted (import.Project) ? import.Project : dir != null ? Path.Combine (dir, import.Project) : Path.GetFullPath (import.Project);
272 if (ProjectCollection.OngoingImports.Contains (path)) {
273 switch (load_settings) {
274 case ProjectLoadSettings.RejectCircularImports:
275 throw new InvalidProjectFileException (import.Location, null, string.Format ("Circular imports was detected: {0} is already on \"importing\" stack", path));
277 return new ProjectElement [0]; // do not import circular references
279 ProjectCollection.OngoingImports.Push (path);
281 using (var reader = XmlReader.Create (path)) {
282 var root = ProjectRootElement.Create (reader, ProjectCollection);
283 raw_imports.Add (new ResolvedImport (import, root, true));
284 return this.EvaluatePropertiesAndImports (root.Children).ToArray ();
287 ProjectCollection.OngoingImports.Pop ();
291 public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
293 return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
296 public void RemoveItems (IEnumerable<ProjectItem> items)
298 var removal = new List<ProjectItem> (items);
299 foreach (var item in removal) {
300 var parent = item.Xml.Parent;
301 parent.RemoveChild (item.Xml);
302 if (parent.Count == 0)
303 parent.Parent.RemoveChild (parent);
307 static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> ();
309 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude)
311 return AddItem (itemType, unevaluatedInclude, empty_metadata);
314 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude,
315 IEnumerable<KeyValuePair<string, string>> metadata)
317 // FIXME: needs several check that AddItemFast() does not process (see MSDN for details).
319 return AddItemFast (itemType, unevaluatedInclude, metadata);
322 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude)
324 return AddItemFast (itemType, unevaluatedInclude, empty_metadata);
327 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude,
328 IEnumerable<KeyValuePair<string, string>> metadata)
330 throw new NotImplementedException ();
335 return Build (Xml.DefaultTargets.Split (';'));
338 public bool Build (IEnumerable<ILogger> loggers)
340 return Build (Xml.DefaultTargets.Split (';'), loggers);
343 public bool Build (string target)
345 return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target});
348 public bool Build (string[] targets)
350 return Build (targets, new ILogger [0]);
353 public bool Build (ILogger logger)
355 return Build (Xml.DefaultTargets.Split (';'), new ILogger [] {logger});
358 public bool Build (string[] targets, IEnumerable<ILogger> loggers)
360 return Build (targets, loggers, new ForwardingLoggerRecord [0]);
363 public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
365 return Build (Xml.DefaultTargets.Split (';'), loggers, remoteLoggers);
368 public bool Build (string target, IEnumerable<ILogger> loggers)
370 return Build (new string [] { target }, loggers);
373 public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
375 // Unlike ProjectInstance.Build(), there is no place to fill outputs by targets, so ignore them
376 // (i.e. we don't use the overload with output).
378 // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly.
379 return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers);
382 public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
384 return Build (new string [] { target }, loggers, remoteLoggers);
387 public ProjectInstance CreateProjectInstance ()
389 var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection);
390 // FIXME: maybe fill other properties to the result.
394 bool ShouldInclude (string unexpandedValue)
396 return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this, null).EvaluateAsBoolean (unexpandedValue);
399 public string ExpandString (string unexpandedValue)
401 return ExpandString (unexpandedValue, null);
404 string ExpandString (string unexpandedValue, string replacementForMissingStuff)
406 return new ExpressionEvaluator (this, replacementForMissingStuff).Evaluate (unexpandedValue);
409 public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
411 return ProjectCollection.Escape (item.EvaluatedInclude);
414 public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item)
416 throw new NotImplementedException ();
419 public ICollection<ProjectItem> GetItems (string itemType)
421 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
424 public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude)
426 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase)));
429 public IEnumerable<ProjectElement> GetLogicalProject ()
431 throw new NotImplementedException ();
434 public static string GetMetadataValueEscaped (ProjectMetadata metadatum)
436 return ProjectCollection.Escape (metadatum.EvaluatedValue);
439 public static string GetMetadataValueEscaped (ProjectItem item, string name)
441 var md = item.GetMetadata (name);
442 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
445 public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name)
447 var md = item.Metadata.FirstOrDefault (m => m.Name == name);
448 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
451 public string GetPropertyValue (string name)
453 var prop = GetProperty (name);
454 return prop != null ? prop.EvaluatedValue : string.Empty;
457 public static string GetPropertyValueEscaped (ProjectProperty property)
460 return property.EvaluatedValue;
463 public ProjectProperty GetProperty (string name)
465 return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
468 public void MarkDirty ()
470 if (!DisableMarkDirty)
474 public void ReevaluateIfNecessary ()
476 throw new NotImplementedException ();
479 public bool RemoveGlobalProperty (string name)
481 throw new NotImplementedException ();
484 public bool RemoveItem (ProjectItem item)
486 throw new NotImplementedException ();
489 public bool RemoveProperty (ProjectProperty property)
491 var removed = properties.FirstOrDefault (p => p.Name == property.Name);
494 properties.Remove (removed);
503 public void Save (TextWriter writer)
508 public void Save (string path)
510 Save (path, Encoding.Default);
513 public void Save (Encoding encoding)
515 Save (FullPath, encoding);
518 public void Save (string path, Encoding encoding)
520 using (var writer = new StreamWriter (path, false, encoding))
524 public void SaveLogicalProject (TextWriter writer)
526 throw new NotImplementedException ();
529 public bool SetGlobalProperty (string name, string escapedValue)
531 throw new NotImplementedException ();
534 public ProjectProperty SetProperty (string name, string unevaluatedValue)
536 var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue);
541 public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata {
542 get { throw new NotImplementedException (); }
545 public ICollection<ProjectItem> AllEvaluatedItems {
546 get { return all_evaluated_items; }
549 public ICollection<ProjectProperty> AllEvaluatedProperties {
550 get { return properties; }
553 public IDictionary<string, List<string>> ConditionedProperties {
555 // this property returns different instances every time.
556 var dic = new Dictionary<string, List<string>> ();
558 // but I dunno HOW this evaluates
560 throw new NotImplementedException ();
564 public string DirectoryPath {
565 get { return dir_path; }
568 public bool DisableMarkDirty { get; set; }
570 public int EvaluationCounter {
571 get { throw new NotImplementedException (); }
574 public string FullPath {
575 get { return Xml.FullPath; }
576 set { Xml.FullPath = value; }
579 class ResolvedImportComparer : IEqualityComparer<ResolvedImport>
581 public static ResolvedImportComparer Instance = new ResolvedImportComparer ();
583 public bool Equals (ResolvedImport x, ResolvedImport y)
585 return x.ImportedProject.FullPath.Equals (y.ImportedProject.FullPath);
587 public int GetHashCode (ResolvedImport obj)
589 return obj.ImportedProject.FullPath.GetHashCode ();
593 public IList<ResolvedImport> Imports {
594 get { return raw_imports.Distinct (ResolvedImportComparer.Instance).ToList (); }
597 public IList<ResolvedImport> ImportsIncludingDuplicates {
598 get { return raw_imports; }
601 public bool IsBuildEnabled {
602 get { return ProjectCollection.IsBuildEnabled; }
606 public bool IsDirty {
607 get { return is_dirty; }
610 public IDictionary<string, ProjectItemDefinition> ItemDefinitions {
611 get { return item_definitions; }
614 [MonoTODO ("should be different from AllEvaluatedItems")]
615 public ICollection<ProjectItem> Items {
616 get { return AllEvaluatedItems; }
619 public ICollection<ProjectItem> ItemsIgnoringCondition {
620 get { return raw_items; }
623 public ICollection<string> ItemTypes {
624 get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
627 [MonoTODO ("should be different from AllEvaluatedProperties")]
628 public ICollection<ProjectProperty> Properties {
629 get { return AllEvaluatedProperties; }
632 public bool SkipEvaluation { get; set; }
639 IDictionary<string, ProjectTargetInstance> Targets {
640 get { return targets; }
643 // These are required for reserved property, represents dynamically changing property values.
644 // This should resolve to either the project file path or that of the imported file.
645 internal string GetEvaluationTimeThisFileDirectory ()
647 var file = GetEvaluationTimeThisFile ();
648 var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory ();
649 return dir + Path.DirectorySeparatorChar;
652 internal string GetEvaluationTimeThisFile ()
654 return ProjectCollection.OngoingImports.Count > 0 ? ProjectCollection.OngoingImports.Peek () : FullPath;