2 // ProjectCollection.cs
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.
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.
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;
38 using System.Collections.Generic;
39 using System.Collections.ObjectModel;
43 using System.Reflection;
45 namespace Microsoft.Build.Evaluation
47 public class ProjectCollection : IDisposable
49 public delegate void ProjectAddedEventHandler (object target, ProjectAddedToProjectCollectionEventArgs args);
51 public class ProjectAddedToProjectCollectionEventArgs : EventArgs
53 public ProjectAddedToProjectCollectionEventArgs (ProjectRootElement project)
56 throw new ArgumentNullException ("project");
57 ProjectRootElement = project;
60 public ProjectRootElement ProjectRootElement { get; private set; }
65 static readonly ProjectCollection global_project_collection;
67 static ProjectCollection ()
70 global_project_collection = new ProjectCollection (new ReadOnlyDictionary<string, string> (new Dictionary<string, string> ()));
72 global_project_collection = new ProjectCollection (new Dictionary<string, string> ());
76 public static string Escape (string unescapedString)
78 return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString);
81 public static string Unescape (string escapedString)
83 return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString);
86 public static ProjectCollection GlobalProjectCollection {
87 get { return global_project_collection; }
90 // semantic model part
92 public ProjectCollection ()
97 public ProjectCollection (IDictionary<string, string> globalProperties)
98 : this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile)
102 public ProjectCollection (ToolsetDefinitionLocations toolsetDefinitionLocations)
103 : this (null, null, toolsetDefinitionLocations)
107 public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers,
108 ToolsetDefinitionLocations toolsetDefinitionLocations)
109 : this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false)
113 public ProjectCollection (IDictionary<string, string> globalProperties,
114 IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers,
115 ToolsetDefinitionLocations toolsetDefinitionLocations,
116 int maxNodeCount, bool onlyLogCriticalEvents)
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;
124 LoadDefaultToolsets ();
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;
136 public void AddProject (Project project)
138 this.loaded_projects.Add (project);
139 if (ProjectAdded != null)
140 ProjectAdded (this, new ProjectAddedToProjectCollectionEventArgs (project.Xml));
144 get { return loaded_projects.Count; }
147 public string DefaultToolsVersion {
148 get { return Toolsets.Any () ? Toolsets.First ().ToolsVersion : null; }
151 public void Dispose ()
154 GC.SuppressFinalize (this);
157 protected virtual void Dispose (bool disposing)
163 public ICollection<Project> GetLoadedProjects (string fullPath)
165 return LoadedProjects.Where (p => p.FullPath != null && Path.GetFullPath (p.FullPath) == Path.GetFullPath (fullPath)).ToList ();
168 readonly IDictionary<string, string> global_properties;
170 public IDictionary<string, string> GlobalProperties {
171 get { return global_properties; }
174 readonly List<Project> loaded_projects = new List<Project> ();
176 public Project LoadProject (string fileName)
178 return LoadProject (fileName, DefaultToolsVersion);
181 public Project LoadProject (string fileName, string toolsVersion)
183 return LoadProject (fileName, toolsVersion);
186 public Project LoadProject (string fileName, IDictionary<string,string> globalProperties, string toolsVersion)
188 return new Project (fileName, globalProperties, toolsVersion);
191 // These methods somehow don't add the project to ProjectCollection...
192 public Project LoadProject (XmlReader xmlReader)
194 return LoadProject (xmlReader, DefaultToolsVersion);
197 public Project LoadProject (XmlReader xmlReader, string toolsVersion)
199 return LoadProject (xmlReader, null, toolsVersion);
202 public Project LoadProject (XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion)
204 return new Project (xmlReader, globalProperties, toolsVersion);
207 public ICollection<Project> LoadedProjects {
208 get { return loaded_projects; }
211 readonly List<ILogger> loggers = new List<ILogger> ();
213 public ICollection<ILogger> Loggers {
214 get { return loggers; }
218 public bool OnlyLogCriticalEvents { get; set; }
221 public bool SkipEvaluation { get; set; }
223 readonly ToolsetDefinitionLocations toolset_locations;
224 public ToolsetDefinitionLocations ToolsetLocations {
225 get { return toolset_locations; }
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 (); }
235 public Toolset GetToolset (string toolsVersion)
237 return Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion);
240 //FIXME: should also support config file, depending on ToolsetLocations
241 void LoadDefaultToolsets ()
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));
250 AddToolset (new Toolset ("4.0",
251 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40), this, null));
254 AddToolset (new Toolset ("12.0",
255 ToolLocationHelper.GetMSBuildInstallPath ("12.0"), this, ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
259 [MonoTODO ("not verified at all")]
260 public void AddToolset (Toolset toolset)
262 toolsets.Add (toolset);
265 [MonoTODO ("not verified at all")]
266 public void RemoveAllToolsets ()
271 [MonoTODO ("not verified at all")]
272 public void RegisterLogger (ILogger logger)
274 loggers.Add (logger);
277 [MonoTODO ("not verified at all")]
278 public void RegisterLoggers (IEnumerable<ILogger> loggers)
280 foreach (var logger in loggers)
281 this.loggers.Add (logger);
284 public void UnloadAllProjects ()
286 throw new NotImplementedException ();
289 [MonoTODO ("Not verified at all")]
290 public void UnloadProject (Project project)
292 this.loaded_projects.Remove (project);
295 [MonoTODO ("Not verified at all")]
296 public void UnloadProject (ProjectRootElement projectRootElement)
298 foreach (var proj in loaded_projects.Where (p => p.Xml == projectRootElement).ToArray ())
299 UnloadProject (proj);
302 public static Version Version {
303 get { throw new NotImplementedException (); }
309 public bool DisableMarkDirty { get; set; }
312 public HostServices HostServices { get; set; }
315 public bool IsBuildEnabled { get; set; }
317 internal string BuildStartupDirectory { get; set; }
319 internal int MaxNodeCount { get; private set; }
321 Stack<string> ongoing_imports = new Stack<string> ();
323 internal Stack<string> OngoingImports {
324 get { return ongoing_imports; }
328 internal static IEnumerable<EnvironmentProjectProperty> GetWellKnownProperties (Project project)
330 Func<string,string,EnvironmentProjectProperty> create = (name, value) => new EnvironmentProjectProperty (project, name, value, true);
331 return GetWellKnownProperties (create);
334 internal static IEnumerable<ProjectPropertyInstance> GetWellKnownProperties (ProjectInstance project)
336 Func<string,string,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, value);
337 return GetWellKnownProperties (create);
340 static IEnumerable<T> GetWellKnownProperties<T> (Func<string,string,T> create)
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);
350 static string extensions_path;
351 internal static string DefaultExtensionsPath {
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);
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")));
365 return extensions_path;
369 internal IEnumerable<ReservedProjectProperty> GetReservedProperties (Toolset toolset, Project project)
371 Func<string,Func<string>,ReservedProjectProperty> create = (name, value) => new ReservedProjectProperty (project, name, value);
372 return GetReservedProperties<ReservedProjectProperty> (toolset, project.Xml, create, () => project.FullPath);
375 internal IEnumerable<ProjectPropertyInstance> GetReservedProperties (Toolset toolset, ProjectInstance project, ProjectRootElement xml)
377 Func<string,Func<string>,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, null, value);
378 return GetReservedProperties<ProjectPropertyInstance> (toolset, xml, create, () => project.FullPath);
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)
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)));
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);
406 yield return create ("MSBuildToolsPath", () => toolset.ToolsPath);
407 yield return create ("MSBuildToolsVersion", () => toolset.ToolsVersion);
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)
414 var file = GetEvaluationTimeThisFile (nonImportingTimeFullPath);
415 var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory ();
416 return dir + Path.DirectorySeparatorChar;
419 internal string GetEvaluationTimeThisFile (Func<string> nonImportingTimeFullPath)
421 return OngoingImports.Count > 0 ? OngoingImports.Peek () : (nonImportingTimeFullPath () ?? string.Empty);
424 static readonly char [] item_target_sep = {';'};
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)
428 var includes = expandString (include).Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
429 var excludes = expandString (exclude).Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
431 if (includes.Length == 0)
433 if (includes.Length == 1 && includes [0].IndexOf ('*') < 0 && excludes.Length == 0) {
434 // for most case - shortcut.
435 var item = creator (includes [0]);
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 (),
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);