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 {
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;
+ //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;
}
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
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]
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]
string[] targetNames,
BuildPropertyGroup globalProperties)
{
- return BuildProjectFile (projectFile, targetNames, globalProperties, new Hashtable (), BuildSettings.None);
+ return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
}
[MonoTODO]
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.LogError (projectFile, ie.Message);
+ this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
return false;
-
- LogProjectFinished (project, result);
-
- return result;
+ } catch (Exception e) {
+ this.LogError (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);
+
+ 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;
+ }
+ }
}
- private void CheckBinPath ()
+ void CheckBinPath ()
{
if (BinPath == null) {
throw new InvalidOperationException ("Before a project can be instantiated, " +
public Project CreateNewProject ()
{
- if (defaultTasksRegistered == true)
- CheckBinPath ();
- // FIXME: I don't really know if it should be here
- LogBuildStarted ();
return new Project (this);
}
if (projectFullFileName == null)
throw new ArgumentNullException ("projectFullFileName");
- return (Project) 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)
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 ((Project) e.Value);
+ IList<Project> values = new List<Project> (projects.Values);
+ foreach (Project p in values)
+ UnloadProject (p);
}
[MonoTODO]
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 {
}
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; }
get { return eventSource; }
}
- internal bool DefaultTasksRegistered {
- get { return defaultTasksRegistered; }
- }
-
- internal TaskDatabase DefaultTasks {
- get { return defaultTasks; }
+ internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
+ get { return builtTargetsOutputByName; }
}
}
}