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;
44 using System.Globalization;
45 using Mono.XBuild.Utilities;
46 using Microsoft.Build.Internal;
48 namespace Microsoft.Build.Evaluation
50 public class ProjectCollection : IDisposable
52 public delegate void ProjectAddedEventHandler (object target, ProjectAddedToProjectCollectionEventArgs args);
54 public class ProjectAddedToProjectCollectionEventArgs : EventArgs
56 public ProjectAddedToProjectCollectionEventArgs (ProjectRootElement project)
59 throw new ArgumentNullException ("project");
60 ProjectRootElement = project;
63 public ProjectRootElement ProjectRootElement { get; private set; }
68 static readonly ProjectCollection global_project_collection;
70 static ProjectCollection ()
73 global_project_collection = new ProjectCollection (new ReadOnlyDictionary<string, string> (new Dictionary<string, string> ()));
75 global_project_collection = new ProjectCollection (new Dictionary<string, string> ());
79 public static string Escape (string unescapedString)
81 return Mono.XBuild.Utilities.MSBuildUtils.Escape (unescapedString);
84 public static string Unescape (string escapedString)
86 return Mono.XBuild.Utilities.MSBuildUtils.Unescape (escapedString);
89 public static ProjectCollection GlobalProjectCollection {
90 get { return global_project_collection; }
93 // semantic model part
95 public ProjectCollection ()
100 public ProjectCollection (IDictionary<string, string> globalProperties)
101 : this (globalProperties, null, ToolsetDefinitionLocations.Registry | ToolsetDefinitionLocations.ConfigurationFile)
105 public ProjectCollection (ToolsetDefinitionLocations toolsetDefinitionLocations)
106 : this (null, null, toolsetDefinitionLocations)
110 public ProjectCollection (IDictionary<string, string> globalProperties, IEnumerable<ILogger> loggers,
111 ToolsetDefinitionLocations toolsetDefinitionLocations)
112 : this (globalProperties, loggers, null, toolsetDefinitionLocations, 1, false)
116 public ProjectCollection (IDictionary<string, string> globalProperties,
117 IEnumerable<ILogger> loggers, IEnumerable<ForwardingLoggerRecord> remoteLoggers,
118 ToolsetDefinitionLocations toolsetDefinitionLocations,
119 int maxNodeCount, bool onlyLogCriticalEvents)
121 global_properties = globalProperties ?? new Dictionary<string, string> ();
122 this.loggers = loggers != null ? loggers.ToList () : new List<ILogger> ();
123 toolset_locations = toolsetDefinitionLocations;
124 MaxNodeCount = maxNodeCount;
125 OnlyLogCriticalEvents = onlyLogCriticalEvents;
127 LoadDefaultToolsets ();
130 [MonoTODO ("not fired yet")]
131 public event ProjectAddedEventHandler ProjectAdded;
132 [MonoTODO ("not fired yet")]
133 public event EventHandler<ProjectChangedEventArgs> ProjectChanged;
134 [MonoTODO ("not fired yet")]
135 public event EventHandler<ProjectCollectionChangedEventArgs> ProjectCollectionChanged;
136 [MonoTODO ("not fired yet")]
137 public event EventHandler<ProjectXmlChangedEventArgs> ProjectXmlChanged;
139 public void AddProject (Project project)
141 this.loaded_projects.Add (project);
142 if (ProjectAdded != null)
143 ProjectAdded (this, new ProjectAddedToProjectCollectionEventArgs (project.Xml));
147 get { return loaded_projects.Count; }
150 string default_tools_version;
151 public string DefaultToolsVersion {
152 get { return default_tools_version; }
154 if (GetToolset (value) == null)
155 throw new InvalidOperationException (string.Format ("Toolset '{0}' does not exist", value));
156 default_tools_version = value;
160 public void Dispose ()
163 GC.SuppressFinalize (this);
166 protected virtual void Dispose (bool disposing)
172 public ICollection<Project> GetLoadedProjects (string fullPath)
174 return LoadedProjects.Where (p => p.FullPath != null && Path.GetFullPath (p.FullPath) == Path.GetFullPath (fullPath)).ToList ();
177 readonly IDictionary<string, string> global_properties;
179 public IDictionary<string, string> GlobalProperties {
180 get { return global_properties; }
183 readonly List<Project> loaded_projects = new List<Project> ();
185 public Project LoadProject (string fileName)
187 return LoadProject (fileName, DefaultToolsVersion);
190 public Project LoadProject (string fileName, string toolsVersion)
192 return LoadProject (fileName, null, toolsVersion);
195 public Project LoadProject (string fileName, IDictionary<string,string> globalProperties, string toolsVersion)
197 var ret = new Project (fileName, globalProperties, toolsVersion);
198 loaded_projects.Add (ret);
202 // These methods somehow don't add the project to ProjectCollection...
203 public Project LoadProject (XmlReader xmlReader)
205 return LoadProject (xmlReader, DefaultToolsVersion);
208 public Project LoadProject (XmlReader xmlReader, string toolsVersion)
210 return LoadProject (xmlReader, null, toolsVersion);
213 public Project LoadProject (XmlReader xmlReader, IDictionary<string,string> globalProperties, string toolsVersion)
215 return new Project (xmlReader, globalProperties, toolsVersion);
218 public ICollection<Project> LoadedProjects {
219 get { return loaded_projects; }
222 readonly List<ILogger> loggers = new List<ILogger> ();
224 public ICollection<ILogger> Loggers {
225 get { return loggers; }
229 public bool OnlyLogCriticalEvents { get; set; }
232 public bool SkipEvaluation { get; set; }
234 readonly ToolsetDefinitionLocations toolset_locations;
235 public ToolsetDefinitionLocations ToolsetLocations {
236 get { return toolset_locations; }
239 readonly List<Toolset> toolsets = new List<Toolset> ();
240 // so what should we do without ToolLocationHelper in Microsoft.Build.Utilities.dll? There is no reference to it in this dll.
241 public ICollection<Toolset> Toolsets {
242 // For ConfigurationFile and None, they cannot be added externally.
243 get { return (ToolsetLocations & ToolsetDefinitionLocations.Registry) != 0 ? toolsets : toolsets.ToList (); }
246 public Toolset GetToolset (string toolsVersion)
248 return Toolsets.FirstOrDefault (t => t.ToolsVersion == toolsVersion);
251 //FIXME: should also support config file, depending on ToolsetLocations
252 void LoadDefaultToolsets ()
255 AddToolset (new Toolset ("4.0",
256 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40), this, null));
259 AddToolset (new Toolset ("12.0", ToolLocationHelper.GetPathToBuildTools ("12.0"), this, null));
262 AddToolset (new Toolset ("14.0", ToolLocationHelper.GetPathToBuildTools ("14.0"), this, null));
265 // We don't support these anymore
266 AddToolset (new Toolset ("2.0",
267 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20), this, null));
268 AddToolset (new Toolset ("3.0",
269 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30), this, null));
270 AddToolset (new Toolset ("3.5",
271 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35), this, null));
273 default_tools_version = toolsets [0].ToolsVersion;
276 [MonoTODO ("not verified at all")]
277 public void AddToolset (Toolset toolset)
279 toolsets.Add (toolset);
282 [MonoTODO ("not verified at all")]
283 public void RemoveAllToolsets ()
288 public void RegisterLogger (ILogger logger)
290 loggers.Add (logger);
293 public void RegisterLoggers (IEnumerable<ILogger> loggers)
295 foreach (var logger in loggers)
296 this.loggers.Add (logger);
299 public void UnloadAllProjects ()
301 throw new NotImplementedException ();
304 [MonoTODO ("Not verified at all")]
305 public void UnloadProject (Project project)
307 this.loaded_projects.Remove (project);
310 [MonoTODO ("Not verified at all")]
311 public void UnloadProject (ProjectRootElement projectRootElement)
313 foreach (var proj in loaded_projects.Where (p => p.Xml == projectRootElement).ToArray ())
314 UnloadProject (proj);
317 public static Version Version {
318 get { throw new NotImplementedException (); }
324 public bool DisableMarkDirty { get; set; }
327 public HostServices HostServices { get; set; }
330 public bool IsBuildEnabled { get; set; }
332 internal string BuildStartupDirectory { get; set; }
334 internal int MaxNodeCount { get; private set; }
336 Stack<string> ongoing_imports = new Stack<string> ();
338 internal Stack<string> OngoingImports {
339 get { return ongoing_imports; }
343 internal static IEnumerable<EnvironmentProjectProperty> GetWellKnownProperties (Project project)
345 Func<string,string,EnvironmentProjectProperty> create = (name, value) => new EnvironmentProjectProperty (project, name, value, true);
346 return GetWellKnownProperties (create);
349 internal static IEnumerable<ProjectPropertyInstance> GetWellKnownProperties (ProjectInstance project)
351 Func<string,string,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, value);
352 return GetWellKnownProperties (create);
355 static IEnumerable<T> GetWellKnownProperties<T> (Func<string,string,T> create)
357 yield return create ("OS", OS);
358 var ext = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath") ?? DefaultExtensionsPath;
359 yield return create ("MSBuildExtensionsPath", ext);
360 var ext32 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath32") ?? ext;
361 yield return create ("MSBuildExtensionsPath32", ext32);
362 var ext64 = Environment.GetEnvironmentVariable ("MSBuildExtensionsPath64") ?? ext;
363 yield return create ("MSBuildExtensionsPath64", ext64);
368 PlatformID pid = Environment.OSVersion.Platform;
381 #region Extension Paths resolution
383 static string extensions_path;
384 internal static string DefaultExtensionsPath {
386 if (extensions_path == null) {
387 // NOTE: code from mcs/tools/gacutil/driver.cs
388 PropertyInfo gac = typeof (System.Environment).GetProperty (
389 "GacPath", BindingFlags.Static | BindingFlags.NonPublic);
392 MethodInfo get_gac = gac.GetGetMethod (true);
393 string gac_path = (string) get_gac.Invoke (null, null);
394 extensions_path = Path.GetFullPath (Path.Combine (
395 gac_path, Path.Combine ("..", "xbuild")));
398 return extensions_path;
402 static string DotConfigExtensionsPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
403 Path.Combine ("xbuild", "tasks"));
404 const string MacOSXExternalXBuildDir = "/Library/Frameworks/Mono.framework/External/xbuild";
405 static string PathSeparatorAsString = Path.PathSeparator.ToString ();
407 // Gives a list of extensions paths to try for $(MSBuildExtensionsPath),
409 internal static IEnumerable<string> GetApplicableExtensionsPaths (Action<string> logMessage)
411 string envvar = String.Join (PathSeparatorAsString, new string [] {
412 // For mac osx, look in the 'External' dir on macosx,
414 MSBuildUtils.RunningOnMac ? MacOSXExternalXBuildDir : String.Empty,
415 DotConfigExtensionsPath,
416 DefaultExtensionsPath});
418 var pathsTable = new Dictionary<string, string> ();
419 foreach (string extn_path in envvar.Split (new char [] {Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)) {
420 if (pathsTable.ContainsKey (extn_path))
423 if (!Directory.Exists (extn_path)) {
424 logMessage (string.Format ("Extension path '{0}' not found, ignoring.", extn_path));
428 pathsTable [extn_path] = extn_path;
429 yield return extn_path;
433 internal static string FindFileInSeveralExtensionsPath (ref string extensionsPathOverride, Func<string,string> expandString, string file, Action<string> logMessage)
436 string ex = extensionsPathOverride;
437 Func<bool> action = () => {
438 string path = WindowsCompatibilityExtensions.FindMatchingPath (expandString (file));
439 if (File.Exists (path))
448 foreach (var s in ProjectCollection.GetApplicableExtensionsPaths (logMessage)) {
449 extensionsPathOverride = s;
456 extensionsPathOverride = null;
459 return ret ?? WindowsCompatibilityExtensions.FindMatchingPath (expandString (file));
464 internal IEnumerable<ReservedProjectProperty> GetReservedProperties (Toolset toolset, Project project)
466 Func<string,Func<string>,ReservedProjectProperty> create = (name, value) => new ReservedProjectProperty (project, name, value);
467 return GetReservedProperties<ReservedProjectProperty> (toolset, project.Xml, create, () => project.FullPath);
470 internal IEnumerable<ProjectPropertyInstance> GetReservedProperties (Toolset toolset, ProjectInstance project, ProjectRootElement xml)
472 Func<string,Func<string>,ProjectPropertyInstance> create = (name, value) => new ProjectPropertyInstance (name, true, null, value);
473 return GetReservedProperties<ProjectPropertyInstance> (toolset, xml, create, () => project.FullPath);
476 // seealso http://msdn.microsoft.com/en-us/library/ms164309.aspx
477 IEnumerable<T> GetReservedProperties<T> (Toolset toolset, ProjectRootElement project, Func<string,Func<string>,T> create, Func<string> projectFullPath)
479 yield return create ("MSBuildBinPath", () => toolset.ToolsPath);
480 // FIXME: add MSBuildLastTaskResult
481 // FIXME: add MSBuildNodeCount
482 // FIXME: add MSBuildProgramFiles32
483 yield return create ("MSBuildProjectDefaultTargets", () => project.DefaultTargets);
484 yield return create ("MSBuildProjectDirectory", () => project.DirectoryPath + Path.DirectorySeparatorChar);
485 yield return create ("MSBuildProjectDirectoryNoRoot", () => project.DirectoryPath.Substring (Path.GetPathRoot (project.DirectoryPath).Length));
486 yield return create ("MSBuildProjectExtension", () => Path.GetExtension (project.FullPath));
487 yield return create ("MSBuildProjectFile", () => Path.GetFileName (project.FullPath));
488 yield return create ("MSBuildProjectFullPath", () => project.FullPath);
489 yield return create ("MSBuildProjectName", () => Path.GetFileNameWithoutExtension (project.FullPath));
490 yield return create ("MSBuildStartupDirectory", () => BuildStartupDirectory);
491 yield return create ("MSBuildThisFile", () => Path.GetFileName (GetEvaluationTimeThisFile (projectFullPath)));
492 yield return create ("MSBuildThisFileFullPath", () => GetEvaluationTimeThisFile (projectFullPath));
493 yield return create ("MSBuildThisFileName", () => Path.GetFileNameWithoutExtension (GetEvaluationTimeThisFile (projectFullPath)));
494 yield return create ("MSBuildThisFileExtension", () => Path.GetExtension (GetEvaluationTimeThisFile (projectFullPath)));
496 yield return create ("MSBuildThisFileDirectory", () => Path.GetDirectoryName (GetEvaluationTimeThisFileDirectory (projectFullPath)));
497 yield return create ("MSBuildThisFileDirectoryNoRoot", () => {
498 string dir = GetEvaluationTimeThisFileDirectory (projectFullPath) + Path.DirectorySeparatorChar;
499 return dir.Substring (Path.GetPathRoot (dir).Length);
501 yield return create ("MSBuildToolsPath", () => toolset.ToolsPath);
502 yield return create ("MSBuildToolsVersion", () => toolset.ToolsVersion);
504 // This is an implementation specific special property for this Microsoft.Build.dll to differentiate
505 // the build from Microsoft.Build.Engine.dll. It is significantly used in some *.targets file we share
506 // between old and new build engine.
507 yield return create ("MonoUseMicrosoftBuildDll", () => "True");
510 // These are required for reserved property, represents dynamically changing property values.
511 // This should resolve to either the project file path or that of the imported file.
512 internal string GetEvaluationTimeThisFileDirectory (Func<string> nonImportingTimeFullPath)
514 var file = GetEvaluationTimeThisFile (nonImportingTimeFullPath);
515 var dir = Path.IsPathRooted (file) ? Path.GetDirectoryName (file) : Directory.GetCurrentDirectory ();
516 return dir + Path.DirectorySeparatorChar;
519 internal string GetEvaluationTimeThisFile (Func<string> nonImportingTimeFullPath)
521 return OngoingImports.Count > 0 ? OngoingImports.Peek () : (nonImportingTimeFullPath () ?? string.Empty);
524 static readonly char [] item_target_sep = {';'};
526 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)
528 var includes = expandString (include).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
529 var excludes = expandString (exclude).Trim ().Split (item_target_sep, StringSplitOptions.RemoveEmptyEntries);
531 if (includes.Length == 0)
533 if (includes.Length == 1 && includes [0].IndexOf ('*') < 0 && excludes.Length == 0) {
534 // for most case - shortcut.
535 var item = creator (includes [0]);
538 var ds = new Microsoft.Build.BuildEngine.DirectoryScanner () {
539 BaseDirectory = new DirectoryInfo (directory),
540 Includes = includes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (i => taskItemCreator (i)).ToArray (),
541 Excludes = excludes.Where (s => !string.IsNullOrWhiteSpace (s)).Select (e => taskItemCreator (e)).ToArray (),
544 foreach (var taskItem in ds.MatchedItems) {
545 if (isDuplicate (taskItem))
546 continue; // skip duplicate
547 var item = creator (taskItem.ItemSpec);
548 string recurse = taskItem.GetMetadata ("RecursiveDir");
549 assignRecurse (item, recurse);
555 static readonly char [] path_sep = {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
557 internal static string GetWellKnownMetadata (string name, string file, Func<string,string> getFullPath, string recursiveDir)
559 switch (name.ToLower (CultureInfo.InvariantCulture)) {
561 return getFullPath (file);
563 return Path.GetPathRoot (getFullPath (file));
565 return Path.GetFileNameWithoutExtension (file);
567 return Path.GetExtension (file);
569 var idx = file.LastIndexOfAny (path_sep);
570 return idx < 0 ? string.Empty : file.Substring (0, idx + 1);
572 var fp = getFullPath (file);
573 return Path.GetDirectoryName (fp).Substring (Path.GetPathRoot (fp).Length);
579 return new FileInfo (getFullPath (file)).LastWriteTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");
581 return new FileInfo (getFullPath (file)).CreationTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");
583 return new FileInfo (getFullPath (file)).LastAccessTime.ToString ("yyyy-MM-dd HH:mm:ss.fffffff");