[xbuild] LogExtensions.LogError - disambiguate.
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Engine.cs
index 9d303ac94754c2dd0c08f7924c1e28e372b2f117..47b5ffddffd0e5213883adcdeb7ff3040db9727c 100644 (file)
@@ -31,7 +31,9 @@ using System;
 using System.Collections;
 using System.Collections.Generic;
 using System.IO;
+using System.Linq;
 using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
 using Mono.XBuild.Utilities;
 
 namespace Microsoft.Build.BuildEngine {
@@ -39,16 +41,21 @@ namespace Microsoft.Build.BuildEngine {
                
                string                  binPath;
                bool                    buildEnabled;
-               TaskDatabase            defaultTasks;
-               bool                    defaultTasksRegistered;
+               Dictionary<string, TaskDatabase> defaultTasksTableByToolsVersion;
                const string            defaultTasksProjectName = "Microsoft.Common.tasks";
                EventSource             eventSource;
                bool                    buildStarted;
-               BuildPropertyGroup      globalProperties;
+               //ToolsetDefinitionLocations toolsetLocations;
+               BuildPropertyGroup      global_properties;
                //IDictionary           importedProjects;
                List <ILogger>          loggers;
                //bool                  onlyLogCriticalEvents;
                Dictionary <string, Project>    projects;
+               string defaultToolsVersion;
+
+               // the key here represents the project+target+global_properties set
+               Dictionary <string, ITaskItem[]> builtTargetsOutputByName;
+               Stack<Project> currentlyBuildingProjectsStack;
 
                static Engine           globalEngine;
                static Version          version;
@@ -59,8 +66,27 @@ namespace Microsoft.Build.BuildEngine {
                }
                
                public Engine ()
-                       : this (null)
+                       : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
+               {
+               }
+
+               public Engine (ToolsetDefinitionLocations locations)
+                       : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
                {
+                       //toolsetLocations = locations;
+               }
+               
+               public Engine (BuildPropertyGroup globalProperties)
+                       : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
+               {
+                       this.global_properties = globalProperties;
+               }
+
+               public Engine (BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations)
+                       : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
+               {
+                       this.global_properties = globalProperties;
+                       //toolsetLocations = locations;
                }
 
                // engine should be invoked with path where binary files are
@@ -73,27 +99,53 @@ namespace Microsoft.Build.BuildEngine {
                        this.eventSource = new EventSource ();
                        this.loggers = new List <ILogger> ();
                        this.buildStarted = false;
-                       this.globalProperties = new BuildPropertyGroup ();
-                       
-                       RegisterDefaultTasks ();
+                       this.global_properties = new BuildPropertyGroup ();
+                       this.builtTargetsOutputByName = new Dictionary<string, ITaskItem[]> ();
+                       this.currentlyBuildingProjectsStack = new Stack<Project> ();
+                       this.Toolsets = new ToolsetCollection ();
+                       LoadDefaultToolsets ();
+                       defaultTasksTableByToolsVersion = new Dictionary<string, TaskDatabase> ();
+               }
+
+               //FIXME: should be loaded from config file
+               void LoadDefaultToolsets ()
+               {
+                       Toolsets.Add (new Toolset ("2.0",
+                                               ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20)));
+                       Toolsets.Add (new Toolset ("3.0",
+                                               ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30)));
+                       Toolsets.Add (new Toolset ("3.5",
+                                               ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35)));
+#if NET_4_0
+                       Toolsets.Add (new Toolset ("4.0",
+                                               ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
+#endif
                }
                
                [MonoTODO]
                public bool BuildProject (Project project)
                {
+                       if (project == null)
+                               throw new ArgumentException ("project");
+                       builtTargetsOutputByName.Clear ();
                        return project.Build ();
                }
                
                [MonoTODO]
                public bool BuildProject (Project project, string targetName)
                {
-                       return BuildProject (project, new string[] { targetName}, new Hashtable (), BuildSettings.None);
+                       if (project == null)
+                               throw new ArgumentException ("project");
+                       if (targetName == null)
+                               return false;
+
+                       return BuildProject (project, new string[] { targetName}, null, BuildSettings.None);
                }
                
                [MonoTODO]
                public bool BuildProject (Project project, string[] targetNames)
                {
-                       return BuildProject (project, targetNames, new Hashtable (), BuildSettings.None);
+                       return BuildProject (project, targetNames, null, BuildSettings.None);
                }
 
                [MonoTODO]
@@ -104,41 +156,44 @@ namespace Microsoft.Build.BuildEngine {
                        return BuildProject (project, targetNames, targetOutputs, BuildSettings.None);
                }
                
-               [MonoTODO ("use buildFlags")]
                public bool BuildProject (Project project,
                                          string[] targetNames,
                                          IDictionary targetOutputs,
                                          BuildSettings buildFlags)
                {
-                       bool result;
-                       
-                       LogProjectStarted (project, targetNames);
-                               
-                       result =  project.Build (targetNames, targetOutputs);
-                       
-                       LogProjectFinished (project, result);
-                       
-                       return result;
+                       if (project == null)
+                               throw new ArgumentException ("project");
+                       if (targetNames == null)
+                               return false;
+
+                       if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
+                               builtTargetsOutputByName.Clear ();
+
+                       if (defaultToolsVersion != null)
+                               // it has been explicitly set, xbuild does this..
+                               project.ToolsVersion = defaultToolsVersion;
+                       return project.Build (targetNames, targetOutputs, buildFlags);
                }
 
                [MonoTODO]
                public bool BuildProjectFile (string projectFile)
                {
-                       throw new NotImplementedException ();
+                       return BuildProjectFile (projectFile, new string [0]);
                }
                
                [MonoTODO]
                public bool BuildProjectFile (string projectFile,
                                              string targetName)
                {
-                       throw new NotImplementedException ();
+                       return BuildProjectFile (projectFile,
+                                                targetName == null ? new string [0] : new string [] {targetName});
                }
                
                [MonoTODO]
                public bool BuildProjectFile (string projectFile,
                                              string[] targetNames)
                {
-                       throw new NotImplementedException ();
+                       return BuildProjectFile (projectFile, targetNames, null);
                }
                
                [MonoTODO]
@@ -146,7 +201,7 @@ namespace Microsoft.Build.BuildEngine {
                                              string[] targetNames,
                                              BuildPropertyGroup globalProperties)
                {
-                       return BuildProjectFile (projectFile, targetNames, globalProperties, new Hashtable (), BuildSettings.None);
+                       return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
                }
                
                [MonoTODO]
@@ -158,33 +213,101 @@ namespace Microsoft.Build.BuildEngine {
                        return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
                }
                
-               [MonoTODO ("use buildFlags")]
                public bool BuildProjectFile (string projectFile,
                                              string[] targetNames,
                                              BuildPropertyGroup globalProperties,
                                              IDictionary targetOutputs,
                                              BuildSettings buildFlags)
                {
-                       bool result;
-                       Project project;
+                       return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
+               }
                        
-                       if (projects.ContainsKey (projectFile)) {
-                               project = (Project) projects [projectFile];
-                               LogProjectStarted (project, targetNames);
-                               result = project.Build (targetNames, targetOutputs);
-                       } else {
+               //FIXME: add a test for null @toolsVersion
+               public bool BuildProjectFile (string projectFile,
+                                             string[] targetNames,
+                                             BuildPropertyGroup globalProperties,
+                                             IDictionary targetOutputs,
+                                             BuildSettings buildFlags, string toolsVersion)
+               {
+                       bool result = false;
+                       try {
+                               StartEngineBuild ();
+                               result = BuildProjectFileInternal (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, toolsVersion);
+                               return result;
+                       } catch (InvalidProjectFileException ie) {
+                               this.LogErrorWithFilename (projectFile, ie.Message);
+                               this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
+                               return false;
+                       } catch (Exception e) {
+                               this.LogErrorWithFilename (projectFile, e.Message);
+                               this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, e.ToString ()));
+                               return false;
+                       } finally {
+                               EndEngineBuild (result);
+                       }
+               }
+
+               bool BuildProjectFileInternal (string projectFile,
+                                             string[] targetNames,
+                                             BuildPropertyGroup globalProperties,
+                                             IDictionary targetOutputs,
+                                             BuildSettings buildFlags, string toolsVersion)
+               {
+
+                       if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
+                               builtTargetsOutputByName.Clear ();
+
+                       Project project;
+
+                       bool newProject = false;
+                       if (!projects.TryGetValue (projectFile, out project)) {
                                project = CreateNewProject ();
+                               newProject = true;
+                       }
+
+                       BuildPropertyGroup engine_old_grp = null;
+                       BuildPropertyGroup project_old_grp = null;
+                       if (globalProperties != null) {
+                               engine_old_grp = GlobalProperties.Clone (true);
+                               project_old_grp = project.GlobalProperties.Clone (true);
+
+                               // Override project's global properties with the
+                               // ones explicitlcur_y specified here
+                               foreach (BuildProperty bp in globalProperties)
+                                       project.GlobalProperties.AddProperty (bp);
+
+                               if (!newProject)
+                                       project.NeedToReevaluate ();
+                       }
+
+                       if (newProject)
                                project.Load (projectFile);
-                               LogProjectStarted (project, targetNames);
-                               result = project.Build (targetNames, targetOutputs);
+
+                       try {
+                               string oldProjectToolsVersion = project.ToolsVersion;
+                               if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
+                                       // no tv specified, let the project inherit it from the
+                                       // engine. 'defaultToolsVersion' will be effective only
+                                       // it has been overridden. Otherwise, the project's own
+                                       // tv will be used.
+                                       project.ToolsVersion = defaultToolsVersion;
+                               else
+                                       project.ToolsVersion = toolsVersion;
+
+                               try {
+                                       return project.Build (targetNames, targetOutputs, buildFlags);
+                               } finally {
+                                       project.ToolsVersion = oldProjectToolsVersion;
+                               }
+                       } finally {
+                               if (globalProperties != null) {
+                                       GlobalProperties = engine_old_grp;
+                                       project.GlobalProperties = project_old_grp;
+                               }
                        }
-                       
-                       LogProjectFinished (project, result);
-                       
-                       return result;
                }
 
-               private void CheckBinPath ()
+               void CheckBinPath ()
                {
                        if (BinPath == null) {
                                throw new InvalidOperationException ("Before a project can be instantiated, " +
@@ -195,10 +318,6 @@ namespace Microsoft.Build.BuildEngine {
 
                public Project CreateNewProject ()
                {
-                       if (defaultTasksRegistered == true)
-                               CheckBinPath ();
-                       // FIXME: I don't really know if it should be here
-                       LogBuildStarted ();
                        return new Project (this);
                }
 
@@ -207,13 +326,18 @@ namespace Microsoft.Build.BuildEngine {
                        if (projectFullFileName == null)
                                throw new ArgumentNullException ("projectFullFileName");
                        
-                       return projects [projectFullFileName];
+                       Project project;
+                       projects.TryGetValue (projectFullFileName, out project);
+
+                       return project;
                }
 
                internal void RemoveLoadedProject (Project p)
                {
-                       if (p.FullFileName != String.Empty)
+                       if (!String.IsNullOrEmpty (p.FullFileName)) {
+                               ClearBuiltTargetsForProject (p);
                                projects.Remove (p.FullFileName);
+                       }
                }
 
                internal void AddLoadedProject (Project p)
@@ -224,21 +348,24 @@ namespace Microsoft.Build.BuildEngine {
        
                public void UnloadProject (Project project)
                {
+                       if (project == null)
+                               throw new ArgumentNullException ("project");
+
                        if (project.ParentEngine != this)
-                               throw new InvalidOperationException ("This project is not loaded in this engine");
+                               throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
                        
                        project.CheckUnloaded ();
                        
-                       if (project.FullFileName != String.Empty)
-                               projects.Remove (project.FullFileName);
+                       RemoveLoadedProject (project);
                        
                        project.Unload ();
                }
 
                public void UnloadAllProjects ()
                {
-                       foreach (KeyValuePair <string, Project> e in projects)
-                               UnloadProject (e.Value);
+                       IList<Project> values = new List<Project> (projects.Values);
+                       foreach (Project p in values)
+                               UnloadProject (p);
                }
 
                [MonoTODO]
@@ -255,67 +382,142 @@ namespace Microsoft.Build.BuildEngine {
                public void UnregisterAllLoggers ()
                {
                        // FIXME: check if build succeeded
-                       LogBuildFinished (true);
+                       // FIXME: it shouldn't be here
+                       if (buildStarted)
+                               LogBuildFinished (true);
                        foreach (ILogger i in loggers) {
                                i.Shutdown ();
                        }
                        loggers.Clear ();
                }
-               
-               private void LogProjectStarted (Project project, string[] targetNames)
+
+               void StartEngineBuild ()
                {
-                       ProjectStartedEventArgs psea;
-                       if (targetNames.Length == 0) {
-                               if (project.DefaultTargets != String.Empty)
-                                       psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName,
-                                               project.DefaultTargets, null, null);
-                               else
-                                       psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, "default", null, null);
-                       } else
-                       psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, String.Join (";",
-                               targetNames), null, null);
+                       if (!buildStarted) {
+                               LogBuildStarted ();
+                               buildStarted = true;
+                       }
+               }
+
+               void EndEngineBuild (bool succeeded)
+               {
+                       if (buildStarted && currentlyBuildingProjectsStack.Count == 0) {
+                               LogBuildFinished (succeeded);
+                               buildStarted = false;
+                       }
+               }
+
+               internal void StartProjectBuild (Project project, string [] target_names)
+               {
+                       StartEngineBuild ();
+
+                       if (currentlyBuildingProjectsStack.Count == 0 ||
+                               String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
+                                       LogProjectStarted (project, target_names);
+
+                       currentlyBuildingProjectsStack.Push (project);
+               }
+
+               internal void EndProjectBuild (Project project, bool succeeded)
+               {
+                       if (!buildStarted)
+                               throw new Exception ("build isnt started currently");
+
+                       Project top_project = currentlyBuildingProjectsStack.Pop ();
+
+                       if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
+                               throw new Exception (String.Format (
+                                                       "INTERNAL ERROR: Project finishing is not the same as the one on top " +
+                                                       "of the stack. Project: {0} Top of stack: {1}",
+                                                       project.FullFileName, top_project.FullFileName));
+
+                       if (currentlyBuildingProjectsStack.Count == 0 ||
+                               String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
+                               LogProjectFinished (top_project, succeeded);
+
+                       EndEngineBuild (succeeded);
+               }
+
+               internal void ClearBuiltTargetsForProject (Project project)
+               {
+                       string project_key = project.GetKeyForTarget (String.Empty, false);
+                       var to_remove_keys = BuiltTargetsOutputByName.Keys.Where (key => key.StartsWith (project_key)).ToList ();
+                       foreach (string to_remove_key in to_remove_keys)
+                               BuiltTargetsOutputByName.Remove (to_remove_key);
+               }
+
+               void LogProjectStarted (Project project, string [] target_names)
+               {
+                       string targets;
+                       if (target_names == null || target_names.Length == 0)
+                               targets = String.Empty;
+                       else
+                               targets = String.Join (";", target_names);
+
+                       ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
+                                       project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
+
                        eventSource.FireProjectStarted (this, psea);
                }
-               
-               private void LogProjectFinished (Project project, bool succeeded)
+
+               void LogProjectFinished (Project project, bool succeeded)
                {
                        ProjectFinishedEventArgs pfea;
                        pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
                        eventSource.FireProjectFinished (this, pfea);
                }
-               
-               private void LogBuildStarted ()
+
+               void LogBuildStarted ()
                {
                        BuildStartedEventArgs bsea;
                        bsea = new BuildStartedEventArgs ("Build started.", null);
                        eventSource.FireBuildStarted (this, bsea);
                }
                
-               private void LogBuildFinished (bool succeeded)
+               void LogBuildFinished (bool succeeded)
                {
                        BuildFinishedEventArgs bfea;
                        bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
                        eventSource.FireBuildFinished (this, bfea);
                }
+
+               internal TaskDatabase GetDefaultTasks (string toolsVersion)
+               {
+                       TaskDatabase db;
+                       if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
+                               return db;
+
+                       var toolset = Toolsets [toolsVersion];
+                       if (toolset == null)
+                               throw new UnknownToolsVersionException (toolsVersion);
+
+                       string toolsPath = toolset.ToolsPath;
+                       string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
+                       this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
+
+                       // set a empty taskdb here, because the project loading the tasks
+                       // file will try to get the default task db
+                       defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
+
+                       db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
+
+                       return db;
+               }
                
-               private void RegisterDefaultTasks ()
+               TaskDatabase RegisterDefaultTasks (string tasksFile)
                {
-                       this.defaultTasksRegistered = false;
-                       
                        Project defaultTasksProject = CreateNewProject ();
+                       TaskDatabase db;
                        
-                       if (binPath != null) {
-                               if (File.Exists (Path.Combine (binPath, defaultTasksProjectName)) == true) {
-                                       defaultTasksProject.Load (Path.Combine (binPath, defaultTasksProjectName));
-                                       defaultTasks = defaultTasksProject.TaskDatabase;
-                               } else {
-                                       defaultTasks = new TaskDatabase ();
-                               }
+                       if (File.Exists (tasksFile)) {
+                               defaultTasksProject.Load (tasksFile);
+                               db = defaultTasksProject.TaskDatabase;
                        } else {
-                               defaultTasks = new TaskDatabase ();
+                               this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
+                               db = new TaskDatabase ();
                        }
-                       
-                       this.defaultTasksRegistered = true;
+
+                       return db;
                }
 
                public string BinPath {
@@ -341,10 +543,36 @@ namespace Microsoft.Build.BuildEngine {
                }
 
                public BuildPropertyGroup GlobalProperties {
-                       get { return globalProperties; }
-                       set { globalProperties = value; }
+                       get { return global_properties; }
+                       set { global_properties = value; }
+               }
+               
+               public ToolsetCollection Toolsets {
+                       get; private set;
                }
 
+               public string DefaultToolsVersion {
+                       get {
+                               // This is used as the fall back version if the
+                               // project can't find a version to use
+                               // Hard-coded to 2.0, so it allows even vs2005 projects
+                               // to build correctly, as they won't have a ToolsVersion
+                               // set!
+                               return String.IsNullOrEmpty (defaultToolsVersion)
+                                               ? "2.0"
+                                               : defaultToolsVersion;
+                       }
+                       set {
+                               if (Toolsets [value] == null)
+                                       throw new UnknownToolsVersionException (value);
+                               defaultToolsVersion = value;
+                       }
+               }
+               
+               public bool IsBuilding {
+                       get { return buildStarted; }
+               }
+               
                public bool OnlyLogCriticalEvents {
                        get { return eventSource.OnlyLogCriticalEvents; }
                        set { eventSource.OnlyLogCriticalEvents = value; }
@@ -354,12 +582,8 @@ namespace Microsoft.Build.BuildEngine {
                        get { return eventSource; }
                }
                
-               internal bool DefaultTasksRegistered {
-                       get { return defaultTasksRegistered; }
-               }
-               
-               internal TaskDatabase DefaultTasks {
-                       get { return defaultTasks; }
+               internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
+                       get { return builtTargetsOutputByName; }
                }
        }
 }