Project.GetItems() and ProjectInstance.GetItems() are now shared code.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Evaluation / ProjectCollection.cs
1 //
2 // ProjectCollection.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.
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 Microsoft.Build.Construction;
33 using Microsoft.Build.Execution;
34 using Microsoft.Build.Framework;
35 using Microsoft.Build.Logging;
36 using Microsoft.Build.Utilities;
37 using System;
38 using System.Collections.Generic;
39 using System.Collections.ObjectModel;
40 using System.IO;
41 using System.Linq;
42 using System.Xml;
43 using System.Reflection;
44
45 namespace Microsoft.Build.Evaluation
46 {
47         public class ProjectCollection : IDisposable
48         {
49                 public delegate void ProjectAddedEventHandler (object target, ProjectAddedToProjectCollectionEventArgs args);
50                 
51                 public class ProjectAddedToProjectCollectionEventArgs : EventArgs
52                 {
53                         public ProjectAddedToProjectCollectionEventArgs (ProjectRootElement project)
54                         {
55                                 if (project == null)
56                                         throw new ArgumentNullException ("project");
57                                 ProjectRootElement = project;
58                         }
59                         
60                         public ProjectRootElement ProjectRootElement { get; private set; }
61                 }
62
63                 // static members
64
65                 static readonly ProjectCollection global_project_collection;
66
67                 static ProjectCollection ()
68                 {
69                         #if NET_4_5
70                         global_project_collection = new ProjectCollection (new ReadOnlyDictionary<string, string> (new Dictionary<string, string> ()));
71                         #else
72                         global_project_collection = new ProjectCollection (new Dictionary<string, string> ());
73                         #endif
74                 }
75
76                 public static string Escape (string unescapedString)
77                 {
78                         return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString);
79                 }
80
81                 public static string Unescape (string escapedString)
82                 {
83                         return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString);
84                 }
85
86                 public static ProjectCollection GlobalProjectCollection {
87                         get { return global_project_collection; }
88                 }
89
90                 // semantic model part
91
92                 public ProjectCollection ()
93                         : this (null)
94                 {
95                 }
96
97                 public ProjectCollection (IDictionary<string, string> globalProperties)
98                 : this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile)
99                 {
100                 }
101
102                 public ProjectCollection (ToolsetDefinitionLocations toolsetDefinitionLocations)
103                 : this (null, null, toolsetDefinitionLocations)
104                 {
105                 }
106
107                 public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers,
108                                 ToolsetDefinitionLocations toolsetDefinitionLocations)
109                         : this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false)
110                 {
111                 }
112
113                 public ProjectCollection (IDictionary<string, string> globalProperties,
114                                 IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers,
115                                 ToolsetDefinitionLocations toolsetDefinitionLocations,
116                                 int maxNodeCount, bool onlyLogCriticalEvents)
117                 {
118                         global_properties = globalProperties ?? new Dictionary<string, string> ();
119                         this.loggers = loggers != null ? loggers.ToList () : new List<ILogger> ();
120                         toolset_locations = toolsetDefinitionLocations;
121                         MaxNodeCount = maxNodeCount;
122                         OnlyLogCriticalEvents = onlyLogCriticalEvents;
123
124                         LoadDefaultToolsets ();
125                 }
126                 
127                 [MonoTODO ("not fired yet")]
128                 public event ProjectAddedEventHandler ProjectAdded;
129                 [MonoTODO ("not fired yet")]
130                 public event EventHandler<ProjectChangedEventArgs> ProjectChanged;
131                 [MonoTODO ("not fired yet")]
132                 public event EventHandler<ProjectCollectionChangedEventArgs> ProjectCollectionChanged;
133                 [MonoTODO ("not fired yet")]
134                 public event EventHandler<ProjectXmlChangedEventArgs> ProjectXmlChanged;
135
136                 public void AddProject (Project project)
137                 {
138                         this.loaded_projects.Add (project);
139                         if (ProjectAdded != null)
140                                 ProjectAdded (this, new ProjectAddedToProjectCollectionEventArgs (project.Xml));
141                 }
142
143                 public int Count {
144                         get { return loaded_projects.Count; }
145                 }
146
147                 public string DefaultToolsVersion {
148                         get { return Toolsets.Any () ? Toolsets.First ().ToolsVersion : null; }
149                 }
150
151                 public void Dispose ()
152                 {
153                         Dispose (true);
154                         GC.SuppressFinalize (this);
155                 }
156
157                 protected virtual void Dispose (bool disposing)
158                 {
159                         if (disposing) {
160                         }
161                 }
162
163                 public ICollection<Project> GetLoadedProjects (string fullPath)
164                 {
165                         return LoadedProjects.Where (p => p.FullPath != null && Path.GetFullPath (p.FullPath) == Path.GetFullPath (fullPath)).ToList ();
166                 }
167
168                 readonly IDictionary<string, string> global_properties;
169
170                 public IDictionary<string, string> GlobalProperties {
171                         get { return global_properties; }
172                 }
173
174                 readonly List<Project> loaded_projects = new List<Project> ();
175                 
176                 public Project LoadProject (string fileName)
177                 {
178                         return LoadProject (fileName, DefaultToolsVersion);
179                 }
180                 
181                 public Project LoadProject (string fileName, string toolsVersion)
182                 {
183                         return LoadProject (fileName, toolsVersion);
184                 }
185                 
186                 public Project LoadProject (string fileName, IDictionary<string,string> globalProperties, string toolsVersion)
187                 {
188                         return new Project (fileName, globalProperties, toolsVersion);
189                 }
190                 
191                 // These methods somehow don't add the project to ProjectCollection...
192                 public Project LoadProject (XmlReader xmlReader)
193                 {
194                         return LoadProject (xmlReader, DefaultToolsVersion);
195                 }
196                 
197                 public Project LoadProject (XmlReader xmlReader, string toolsVersion)
198                 {
199                         return LoadProject (xmlReader, null, toolsVersion);
200                 }
201                 
202                 public Project LoadProject (XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion)
203                 {
204                         return new Project (xmlReader, globalProperties, toolsVersion);
205                 }
206                 
207                 public ICollection<Project> LoadedProjects {
208                         get { return loaded_projects; }
209                 }
210
211                 readonly List<ILogger> loggers = new List<ILogger> ();
212                 [MonoTODO]
213                 public ICollection<ILogger> Loggers {
214                         get { return loggers; }
215                 }
216
217                 [MonoTODO]
218                 public bool OnlyLogCriticalEvents { get; set; }
219
220                 [MonoTODO]
221                 public bool SkipEvaluation { get; set; }
222
223                 readonly ToolsetDefinitionLocations toolset_locations;
224                 public ToolsetDefinitionLocations ToolsetLocations {
225                         get { return toolset_locations; }
226                 }
227
228                 readonly List<Toolset> toolsets = new List<Toolset> ();
229                 // so what should we do without ToolLocationHelper in Microsoft.Build.Utilities.dll? There is no reference to it in this dll.
230                 public ICollection<Toolset> Toolsets {
231                         // For ConfigurationFile and None, they cannot be added externally.
232                         get { return (ToolsetLocations & ToolsetDefinitionLocations.Registry) != 0 ? toolsets : toolsets.ToList (); }
233                 }
234                 
235                 public Toolset GetToolset (string toolsVersion)
236                 {
237                         return Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion);
238                 }
239
240                 //FIXME: should also support config file, depending on ToolsetLocations
241                 void LoadDefaultToolsets ()
242                 {
243                         AddToolset (new Toolset ("2.0",
244                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20), this, null));
245                         AddToolset (new Toolset ("3.0",
246                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30), this, null));
247                         AddToolset (new Toolset ("3.5",
248                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35), this, null));
249 #if NET_4_0
250                         AddToolset (new Toolset ("4.0",
251                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40), this, null));
252 #endif
253 #if NET_4_5
254                         AddToolset (new Toolset ("12.0",
255                                 ToolLocationHelper.GetMSBuildInstallPath ("12.0"), this, ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
256 #endif
257                 }
258                 
259                 [MonoTODO ("not verified at all")]
260                 public void AddToolset (Toolset toolset)
261                 {
262                         toolsets.Add (toolset);
263                 }
264                 
265                 [MonoTODO ("not verified at all")]
266                 public void RemoveAllToolsets ()
267                 {
268                         toolsets.Clear ();
269                 }
270                 
271                 [MonoTODO ("not verified at all")]
272                 public void RegisterLogger (ILogger logger)
273                 {
274                         loggers.Add (logger);
275                 }
276                 
277                 [MonoTODO ("not verified at all")]
278                 public void RegisterLoggers (IEnumerable<ILogger> loggers)
279                 {
280                         foreach (var logger in loggers)
281                                 this.loggers.Add (logger);
282                 }
283
284                 public void UnloadAllProjects ()
285                 {
286                         throw new NotImplementedException ();
287                 }
288
289                 [MonoTODO ("Not verified at all")]
290                 public void UnloadProject (Project project)
291                 {
292                         this.loaded_projects.Remove (project);
293                 }
294
295                 [MonoTODO ("Not verified at all")]
296                 public void UnloadProject (ProjectRootElement projectRootElement)
297                 {
298                         foreach (var proj in loaded_projects.Where (p => p.Xml == projectRootElement).ToArray ())
299                                 UnloadProject (proj);
300                 }
301
302                 public static Version Version {
303                         get { throw new NotImplementedException (); }
304                 }
305
306                 // Execution part
307
308                 [MonoTODO]
309                 public bool DisableMarkDirty { get; set; }
310
311                 [MonoTODO]
312                 public HostServices HostServices { get; set; }
313
314                 [MonoTODO]
315                 public bool IsBuildEnabled { get; set; }
316                 
317                 internal string BuildStartupDirectory { get; set; }
318                 
319                 internal int MaxNodeCount { get; private set; }
320                 
321                 Stack<string> ongoing_imports = new Stack<string> ();
322                 
323                 internal Stack<string> OngoingImports {
324                         get { return ongoing_imports; }
325                 }
326                 
327                 // common part
328                 internal static IEnumerable<EnvironmentProjectProperty> GetWellKnownProperties (Project project)
329                 {
330                         Func<string,string,EnvironmentProjectProperty> create = (name, value) => new EnvironmentProjectProperty (project, name, value, true);
331                         return GetWellKnownProperties (create);
332                 }
333                 
334                 internal static IEnumerable<ProjectPropertyInstance> GetWellKnownProperties (ProjectInstance project)
335                 {
336                         Func<string,string,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, value);
337                         return GetWellKnownProperties (create);
338                 }
339                 
340                 static IEnumerable<T> GetWellKnownProperties<T> (Func<string,string,T> create)
341                 {
342                         var ext = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath") ?? DefaultExtensionsPath;
343                         yield return create ("MSBuildExtensionsPath", ext);
344                         var ext32 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath32") ?? DefaultExtensionsPath;
345                         yield return create ("MSBuildExtensionsPath32", ext32);
346                         var ext64 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath64") ?? DefaultExtensionsPath;
347                         yield return create ("MSBuildExtensionsPath64", ext64);
348                 }
349
350                 static string extensions_path;
351                 internal static string DefaultExtensionsPath {
352                         get {
353                                 if (extensions_path == null) {
354                                         // NOTE: code from mcs/tools/gacutil/driver.cs
355                                         PropertyInfo gac = typeof (System.Environment).GetProperty (
356                                                         "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
357
358                                         if (gac != null) {
359                                                 MethodInfo get_gac = gac.GetGetMethod (true);
360                                                 string gac_path = (string) get_gac.Invoke (null, null);
361                                                 extensions_path = Path.GetFullPath (Path.Combine (
362                                                                         gac_path, Path.Combine ("..", "xbuild")));
363                                         }
364                                 }
365                                 return extensions_path;
366                         }
367                 }
368                 
369                 internal IEnumerable<ReservedProjectProperty> GetReservedProperties (Toolset toolset, Project project)
370                 {
371                         Func<string,Func<string>,ReservedProjectProperty> create = (name, value) => new ReservedProjectProperty (project, name, value);
372                         return GetReservedProperties<ReservedProjectProperty> (toolset, project.Xml, create, () => project.FullPath);
373                 }
374                 
375                 internal IEnumerable<ProjectPropertyInstance> GetReservedProperties (Toolset toolset, ProjectInstance project, ProjectRootElement xml)
376                 {
377                         Func<string,Func<string>,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, null, value);
378                         return GetReservedProperties<ProjectPropertyInstance> (toolset, xml, create, () => project.FullPath);
379                 }
380                 
381                 // seealso http://msdn.microsoft.com/en-us/library/ms164309.aspx
382                 IEnumerable<T> GetReservedProperties<T> (Toolset toolset, ProjectRootElement project, Func<string,Func<string>,T> create, Func<string> projectFullPath)
383                 {
384                         yield return create ("MSBuildBinPath", () => toolset.ToolsPath);
385                         // FIXME: add MSBuildLastTaskResult
386                         // FIXME: add MSBuildNodeCount
387                         // FIXME: add MSBuildProgramFiles32
388                         yield return create ("MSBuildProjectDefaultTargets", () => project.DefaultTargets);
389                         yield return create ("MSBuildProjectDirectory", () => project.DirectoryPath + Path.DirectorySeparatorChar);
390                         yield return create ("MSBuildProjectDirectoryNoRoot", () => project.DirectoryPath.Substring (Path.GetPathRoot (project.DirectoryPath).Length));
391                         yield return create ("MSBuildProjectExtension", () => Path.GetExtension (project.FullPath));
392                         yield return create ("MSBuildProjectFile", () => Path.GetFileName (project.FullPath));
393                         yield return create ("MSBuildProjectFullPath", () => project.FullPath);
394                         yield return create ("MSBuildProjectName", () => Path.GetFileNameWithoutExtension (project.FullPath));
395                         yield return create ("MSBuildStartupDirectory", () => BuildStartupDirectory);
396                         yield return create ("MSBuildThisFile", () => Path.GetFileName (GetEvaluationTimeThisFile (projectFullPath)));
397                         yield return create ("MSBuildThisFileFullPath", () => GetEvaluationTimeThisFile (projectFullPath));
398                         yield return create ("MSBuildThisFileName", () => Path.GetFileNameWithoutExtension (GetEvaluationTimeThisFile (projectFullPath)));
399                         yield return create ("MSBuildThisFileExtension", () => Path.GetExtension (GetEvaluationTimeThisFile (projectFullPath)));
400
401                         yield return create ("MSBuildThisFileDirectory", () => Path.GetDirectoryName (GetEvaluationTimeThisFileDirectory (projectFullPath)));
402                         yield return create ("MSBuildThisFileDirectoryNoRoot", () => {
403                                 string dir = GetEvaluationTimeThisFileDirectory (projectFullPath) + Path.DirectorySeparatorChar;
404                                 return dir.Substring (Path.GetPathRoot (dir).Length);
405                                 });
406                         yield return create ("MSBuildToolsPath", () => toolset.ToolsPath);
407                         yield return create ("MSBuildToolsVersion", () => toolset.ToolsVersion);
408                 }
409                 
410                 // These are required for reserved property, represents dynamically changing property values.
411                 // This should resolve to either the project file path or that of the imported file.
412                 internal string GetEvaluationTimeThisFileDirectory (Func<string> nonImportingTimeFullPath)
413                 {
414                         var file = GetEvaluationTimeThisFile (nonImportingTimeFullPath);
415                         var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory ();
416                         return dir + Path.DirectorySeparatorChar;
417                 }
418
419                 internal string GetEvaluationTimeThisFile (Func<string> nonImportingTimeFullPath)
420                 {
421                         return OngoingImports.Count > 0 ? OngoingImports.Peek () : (nonImportingTimeFullPath () ?? string.Empty);
422                 }
423                 
424                 static readonly char [] item_target_sep = {';'};
425                 
426                 internal static IEnumerable<T> GetAllItems<T> (Func<string,string> expandString, string include, string exclude, Func<string,T> creator, Func<string,ITaskItem> taskItemCreator, string directory, Action<T,string> assignRecurse, Func<ITaskItem,bool> isDuplicate)
427                 {
428                         var includes = expandString (include).Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
429                         var excludes = expandString (exclude).Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
430                         
431                         if (includes.Length == 0)
432                                 yield break;
433                         if (includes.Length == 1 && includes [0].IndexOf ('*') < 0 && excludes.Length == 0) {
434                                 // for most case - shortcut.
435                                 var item = creator (includes [0]);
436                                 yield return item;
437                         } else {
438                                 var ds = new Microsoft.Build.BuildEngine.DirectoryScanner () {
439                                         BaseDirectory = new DirectoryInfo (directory),
440                                         Includes = includes.Select (i => taskItemCreator (i)).ToArray (),
441                                         Excludes = excludes.Select (e => taskItemCreator (e)).ToArray (),
442                                 };
443                                 ds.Scan ();
444                                 foreach (var taskItem in ds.MatchedItems) {
445                                         if (isDuplicate (taskItem))
446                                                 continue; // skip duplicate
447                                         var item = creator (taskItem.ItemSpec);
448                                         string recurse = taskItem.GetMetadata ("RecursiveDir");
449                                         assignRecurse (item, recurse);
450                                         yield return item;
451                                 }
452                         }
453                 }
454         }
455 }