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;
46 namespace Microsoft.Build.Evaluation
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}")]
54 public Project (XmlReader xml)
55 : this (ProjectRootElement.Create (xml))
59 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
61 : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion)
65 public Project (XmlReader xml, IDictionary<string, string> globalProperties,
66 string toolsVersion, ProjectCollection projectCollection)
67 : this (ProjectRootElement.Create (xml), globalProperties, toolsVersion, projectCollection)
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)
78 public Project (ProjectRootElement xml) : this (xml, null, null)
82 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
84 : this (xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
88 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
89 string toolsVersion, ProjectCollection projectCollection)
90 : this (xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
94 public Project (ProjectRootElement xml, IDictionary<string, string> globalProperties,
95 string toolsVersion, ProjectCollection projectCollection,
96 ProjectLoadSettings loadSettings)
98 if (projectCollection == null)
99 throw new ArgumentNullException ("projectCollection");
101 this.GlobalProperties = globalProperties ?? new Dictionary<string, string> ();
102 this.ToolsVersion = toolsVersion;
103 this.ProjectCollection = projectCollection;
104 this.load_settings = loadSettings;
109 public Project (string projectFile)
110 : this (projectFile, null, null)
114 public Project (string projectFile, IDictionary<string, string> globalProperties,
116 : this (projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection, ProjectLoadSettings.Default)
120 public Project (string projectFile, IDictionary<string, string> globalProperties,
121 string toolsVersion, ProjectCollection projectCollection)
122 : this (projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
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)
133 ProjectLoadSettings load_settings;
135 public IDictionary<string, string> GlobalProperties { get; private set; }
137 public ProjectCollection ProjectCollection { get; private set; }
139 public string ToolsVersion { get; private set; }
141 public ProjectRootElement Xml { get; private set; }
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;
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> ();
165 static readonly char [] item_sep = {';'};
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);
179 all_evaluated_items = new List<ProjectItem> ();
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));
192 foreach (var child in Xml.Children) {
193 var ige = child as ProjectItemGroupElement;
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);
205 var def = child as ProjectItemDefinitionGroupElement;
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);
217 all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
220 public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
222 return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
225 public void RemoveItems (IEnumerable<ProjectItem> items)
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);
236 static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> ();
238 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude)
240 return AddItem (itemType, unevaluatedInclude, empty_metadata);
243 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude,
244 IEnumerable<KeyValuePair<string, string>> metadata)
246 // FIXME: needs several check that AddItemFast() does not process (see MSDN for details).
248 return AddItemFast (itemType, unevaluatedInclude, metadata);
251 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude)
253 return AddItemFast (itemType, unevaluatedInclude, empty_metadata);
256 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude,
257 IEnumerable<KeyValuePair<string, string>> metadata)
259 throw new NotImplementedException ();
264 return Build (Xml.DefaultTargets.Split (';'));
267 public bool Build (IEnumerable<ILogger> loggers)
269 return Build (Xml.DefaultTargets.Split (';'), loggers);
272 public bool Build (string target)
274 return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target});
277 public bool Build (string[] targets)
279 return Build (targets, new ILogger [0]);
282 public bool Build (ILogger logger)
284 return Build (Xml.DefaultTargets.Split (';'), new ILogger [] {logger});
287 public bool Build (string[] targets, IEnumerable<ILogger> loggers)
289 return Build (targets, loggers, new ForwardingLoggerRecord [0]);
292 public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
294 return Build (Xml.DefaultTargets.Split (';'), loggers, remoteLoggers);
297 public bool Build (string target, IEnumerable<ILogger> loggers)
299 return Build (new string [] { target }, loggers);
302 public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
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).
307 // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly.
308 return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers);
311 public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
313 return Build (new string [] { target }, loggers, remoteLoggers);
316 public ProjectInstance CreateProjectInstance ()
318 var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection);
319 // FIXME: maybe fill other properties to the result.
323 bool ShouldInclude (string unexpandedValue)
325 return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this, null).EvaluateAsBoolean (ExpandString (unexpandedValue, "''"));
328 public string ExpandString (string unexpandedValue)
330 return ExpandString (unexpandedValue, null);
333 string ExpandString (string unexpandedValue, string replacementForMissingStuff)
335 return new ExpressionEvaluator (this, replacementForMissingStuff).Evaluate (unexpandedValue);
338 public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
340 return ProjectCollection.Escape (item.EvaluatedInclude);
343 public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item)
345 throw new NotImplementedException ();
348 public ICollection<ProjectItem> GetItems (string itemType)
350 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
353 public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude)
355 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase)));
358 public IEnumerable<ProjectElement> GetLogicalProject ()
360 throw new NotImplementedException ();
363 public static string GetMetadataValueEscaped (ProjectMetadata metadatum)
365 return ProjectCollection.Escape (metadatum.EvaluatedValue);
368 public static string GetMetadataValueEscaped (ProjectItem item, string name)
370 var md = item.GetMetadata (name);
371 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
374 public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name)
376 var md = item.Metadata.FirstOrDefault (m => m.Name == name);
377 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
380 public string GetPropertyValue (string name)
382 var prop = GetProperty (name);
383 return prop != null ? prop.EvaluatedValue : string.Empty;
386 public static string GetPropertyValueEscaped (ProjectProperty property)
389 return property.EvaluatedValue;
392 public ProjectProperty GetProperty (string name)
394 return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
397 public void MarkDirty ()
399 if (!DisableMarkDirty)
403 public void ReevaluateIfNecessary ()
405 throw new NotImplementedException ();
408 public bool RemoveGlobalProperty (string name)
410 throw new NotImplementedException ();
413 public bool RemoveItem (ProjectItem item)
415 throw new NotImplementedException ();
418 public bool RemoveProperty (ProjectProperty property)
420 var removed = properties.FirstOrDefault (p => p.Name == property.Name);
423 properties.Remove (removed);
432 public void Save (TextWriter writer)
437 public void Save (string path)
439 Save (path, Encoding.Default);
442 public void Save (Encoding encoding)
444 Save (FullPath, encoding);
447 public void Save (string path, Encoding encoding)
449 using (var writer = new StreamWriter (path, false, encoding))
453 public void SaveLogicalProject (TextWriter writer)
455 throw new NotImplementedException ();
458 public bool SetGlobalProperty (string name, string escapedValue)
460 throw new NotImplementedException ();
463 public ProjectProperty SetProperty (string name, string unevaluatedValue)
465 var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue);
470 public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata {
471 get { throw new NotImplementedException (); }
474 public ICollection<ProjectItem> AllEvaluatedItems {
475 get { return all_evaluated_items; }
478 public ICollection<ProjectProperty> AllEvaluatedProperties {
479 get { throw new NotImplementedException (); }
482 public IDictionary<string, List<string>> ConditionedProperties {
484 // this property returns different instances every time.
485 var dic = new Dictionary<string, List<string>> ();
487 // but I dunno HOW this evaluates
489 throw new NotImplementedException ();
493 public string DirectoryPath {
494 get { return dir_path; }
497 public bool DisableMarkDirty { get; set; }
499 public int EvaluationCounter {
500 get { throw new NotImplementedException (); }
503 public string FullPath {
504 get { return Xml.FullPath; }
505 set { Xml.FullPath = value; }
508 public IList<ResolvedImport> Imports {
509 get { throw new NotImplementedException (); }
512 public IList<ResolvedImport> ImportsIncludingDuplicates {
513 get { return raw_imports; }
516 public bool IsBuildEnabled {
517 get { return ProjectCollection.IsBuildEnabled; }
521 public bool IsDirty {
522 get { return is_dirty; }
525 public IDictionary<string, ProjectItemDefinition> ItemDefinitions {
526 get { return item_definitions; }
529 [MonoTODO ("should be different from AllEvaluatedItems")]
530 public ICollection<ProjectItem> Items {
531 get { return AllEvaluatedItems; }
534 public ICollection<ProjectItem> ItemsIgnoringCondition {
535 get { return raw_items; }
538 public ICollection<string> ItemTypes {
539 get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
542 public ICollection<ProjectProperty> Properties {
543 get { return properties; }
546 public bool SkipEvaluation { get; set; }
553 IDictionary<string, ProjectTargetInstance> Targets {
554 get { return targets; }
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 ()
562 return Path.GetDirectoryName (GetEvaluationTimeThisFile ()) + Path.DirectorySeparatorChar;
565 internal string GetEvaluationTimeThisFile ()
567 return files_in_process.Count > 0 ? files_in_process.Peek () : FullPath;