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;
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));
191 foreach (var child in Xml.Children) {
192 var ige = child as ProjectItemGroupElement;
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);
204 var def = child as ProjectItemDefinitionGroupElement;
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);
216 all_evaluated_items.Sort ((p1, p2) => string.Compare (p1.ItemType, p2.ItemType, StringComparison.OrdinalIgnoreCase));
219 public ICollection<ProjectItem> GetItemsIgnoringCondition (string itemType)
221 return new CollectionFromEnumerable<ProjectItem> (raw_items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
224 public void RemoveItems (IEnumerable<ProjectItem> items)
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);
235 static readonly Dictionary<string, string> empty_metadata = new Dictionary<string, string> ();
237 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude)
239 return AddItem (itemType, unevaluatedInclude, empty_metadata);
242 public IList<ProjectItem> AddItem (string itemType, string unevaluatedInclude,
243 IEnumerable<KeyValuePair<string, string>> metadata)
245 // FIXME: needs several check that AddItemFast() does not process (see MSDN for details).
247 return AddItemFast (itemType, unevaluatedInclude, metadata);
250 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude)
252 return AddItemFast (itemType, unevaluatedInclude, empty_metadata);
255 public IList<ProjectItem> AddItemFast (string itemType, string unevaluatedInclude,
256 IEnumerable<KeyValuePair<string, string>> metadata)
258 throw new NotImplementedException ();
263 return Build (Xml.DefaultTargets.Split (';'));
266 public bool Build (IEnumerable<ILogger> loggers)
268 return Build (Xml.DefaultTargets.Split (';'), loggers);
271 public bool Build (string target)
273 return string.IsNullOrWhiteSpace (target) ? Build () : Build (new string [] {target});
276 public bool Build (string[] targets)
278 return Build (targets, new ILogger [0]);
281 public bool Build (ILogger logger)
283 return Build (Xml.DefaultTargets.Split (';'), new ILogger [] {logger});
286 public bool Build (string[] targets, IEnumerable<ILogger> loggers)
288 return Build (targets, loggers, new ForwardingLoggerRecord [0]);
291 public bool Build (IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
293 return Build (Xml.DefaultTargets.Split (';'), loggers, remoteLoggers);
296 public bool Build (string target, IEnumerable<ILogger> loggers)
298 return Build (new string [] { target }, loggers);
301 public bool Build (string[] targets, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
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).
306 // This does not check FullPath, so don't call GetProjectInstanceForBuild() directly.
307 return new BuildManager ().GetProjectInstanceForBuildInternal (this).Build (targets, loggers, remoteLoggers);
310 public bool Build (string target, IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers)
312 return Build (new string [] { target }, loggers, remoteLoggers);
315 public ProjectInstance CreateProjectInstance ()
317 var ret = new ProjectInstance (Xml, GlobalProperties, ToolsVersion, ProjectCollection);
318 // FIXME: maybe fill other properties to the result.
322 bool ShouldInclude (string unexpandedValue)
324 return string.IsNullOrWhiteSpace (unexpandedValue) || new ExpressionEvaluator (this).EvaluateAsBoolean (unexpandedValue);
327 public string ExpandString (string unexpandedValue)
329 return new ExpressionEvaluator (this).Evaluate (unexpandedValue);
332 public static string GetEvaluatedItemIncludeEscaped (ProjectItem item)
334 return ProjectCollection.Escape (item.EvaluatedInclude);
337 public static string GetEvaluatedItemIncludeEscaped (ProjectItemDefinition item)
339 throw new NotImplementedException ();
342 public ICollection<ProjectItem> GetItems (string itemType)
344 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.ItemType.Equals (itemType, StringComparison.OrdinalIgnoreCase)));
347 public ICollection<ProjectItem> GetItemsByEvaluatedInclude (string evaluatedInclude)
349 return new CollectionFromEnumerable<ProjectItem> (Items.Where (p => p.EvaluatedInclude.Equals (evaluatedInclude, StringComparison.OrdinalIgnoreCase)));
352 public IEnumerable<ProjectElement> GetLogicalProject ()
354 throw new NotImplementedException ();
357 public static string GetMetadataValueEscaped (ProjectMetadata metadatum)
359 return ProjectCollection.Escape (metadatum.EvaluatedValue);
362 public static string GetMetadataValueEscaped (ProjectItem item, string name)
364 var md = item.GetMetadata (name);
365 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
368 public static string GetMetadataValueEscaped (ProjectItemDefinition item, string name)
370 var md = item.Metadata.FirstOrDefault (m => m.Name == name);
371 return md != null ? ProjectCollection.Escape (md.EvaluatedValue) : null;
374 public string GetPropertyValue (string name)
376 var prop = GetProperty (name);
377 return prop != null ? prop.EvaluatedValue : string.Empty;
380 public static string GetPropertyValueEscaped (ProjectProperty property)
382 return property.EvaluatedValue;
385 public ProjectProperty GetProperty (string name)
387 return properties.FirstOrDefault (p => p.Name.Equals (name, StringComparison.OrdinalIgnoreCase));
390 public void MarkDirty ()
392 if (!DisableMarkDirty)
396 public void ReevaluateIfNecessary ()
398 throw new NotImplementedException ();
401 public bool RemoveGlobalProperty (string name)
403 throw new NotImplementedException ();
406 public bool RemoveItem (ProjectItem item)
408 throw new NotImplementedException ();
411 public bool RemoveProperty (ProjectProperty property)
413 var removed = properties.FirstOrDefault (p => p.Name == property.Name);
416 properties.Remove (removed);
425 public void Save (TextWriter writer)
430 public void Save (string path)
432 Save (path, Encoding.Default);
435 public void Save (Encoding encoding)
437 Save (FullPath, encoding);
440 public void Save (string path, Encoding encoding)
442 using (var writer = new StreamWriter (path, false, encoding))
446 public void SaveLogicalProject (TextWriter writer)
448 throw new NotImplementedException ();
451 public bool SetGlobalProperty (string name, string escapedValue)
453 throw new NotImplementedException ();
456 public ProjectProperty SetProperty (string name, string unevaluatedValue)
458 var p = new ManuallyAddedProjectProperty (this, name, unevaluatedValue);
463 public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata {
464 get { throw new NotImplementedException (); }
467 public ICollection<ProjectItem> AllEvaluatedItems {
468 get { return all_evaluated_items; }
471 public ICollection<ProjectProperty> AllEvaluatedProperties {
472 get { throw new NotImplementedException (); }
475 public IDictionary<string, List<string>> ConditionedProperties {
477 // this property returns different instances every time.
478 var dic = new Dictionary<string, List<string>> ();
480 // but I dunno HOW this evaluates
482 throw new NotImplementedException ();
486 public string DirectoryPath {
487 get { return dir_path; }
490 public bool DisableMarkDirty { get; set; }
492 public int EvaluationCounter {
493 get { throw new NotImplementedException (); }
496 public string FullPath {
497 get { return Xml.FullPath; }
498 set { Xml.FullPath = value; }
501 public IList<ResolvedImport> Imports {
502 get { throw new NotImplementedException (); }
505 public IList<ResolvedImport> ImportsIncludingDuplicates {
506 get { return raw_imports; }
509 public bool IsBuildEnabled {
510 get { return ProjectCollection.IsBuildEnabled; }
514 public bool IsDirty {
515 get { return is_dirty; }
518 public IDictionary<string, ProjectItemDefinition> ItemDefinitions {
519 get { return item_definitions; }
522 [MonoTODO ("should be different from AllEvaluatedItems")]
523 public ICollection<ProjectItem> Items {
524 get { return AllEvaluatedItems; }
527 public ICollection<ProjectItem> ItemsIgnoringCondition {
528 get { return raw_items; }
531 public ICollection<string> ItemTypes {
532 get { return new CollectionFromEnumerable<string> (raw_items.Select (i => i.ItemType).Distinct ()); }
535 public ICollection<ProjectProperty> Properties {
536 get { return properties; }
539 public bool SkipEvaluation { get; set; }
546 IDictionary<string, ProjectTargetInstance> Targets {
547 get { return targets; }
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 ()
555 return Path.GetDirectoryName (GetEvaluationTimeThisFile ()) + Path.DirectorySeparatorChar;
558 internal string GetEvaluationTimeThisFile ()
560 return files_in_process.Count > 0 ? files_in_process.Peek () : FullPath;