2 // Engine.cs: Main engine of XBuild.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections;
30 using System.Collections.Generic;
33 using Microsoft.Build.Framework;
34 using Microsoft.Build.Utilities;
35 using Mono.XBuild.Utilities;
37 namespace Microsoft.Build.BuildEngine {
42 Dictionary<string, TaskDatabase> defaultTasksTableByToolsVersion;
43 const string defaultTasksProjectName = "Microsoft.Common.tasks";
44 EventSource eventSource;
46 //ToolsetDefinitionLocations toolsetLocations;
47 BuildPropertyGroup global_properties;
48 //IDictionary importedProjects;
49 List <ILogger> loggers;
50 //bool onlyLogCriticalEvents;
51 Dictionary <string, Project> projects;
52 string defaultToolsVersion;
54 // the key here represents the project+target+global_properties set
55 Dictionary <string, ITaskItem[]> builtTargetsOutputByName;
56 Stack<Project> currentlyBuildingProjectsStack;
58 static Engine globalEngine;
59 static Version version;
63 version = new Version ("0.1");
67 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
71 public Engine (ToolsetDefinitionLocations locations)
72 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
74 //toolsetLocations = locations;
77 public Engine (BuildPropertyGroup globalProperties)
78 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
80 this.global_properties = globalProperties;
83 public Engine (BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations)
84 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
86 this.global_properties = globalProperties;
87 //toolsetLocations = locations;
90 // engine should be invoked with path where binary files are
91 // to find microsoft.build.tasks
92 public Engine (string binPath)
94 this.binPath = binPath;
95 this.buildEnabled = true;
96 this.projects = new Dictionary <string, Project> ();
97 this.eventSource = new EventSource ();
98 this.loggers = new List <ILogger> ();
99 this.buildStarted = false;
100 this.global_properties = new BuildPropertyGroup ();
101 this.builtTargetsOutputByName = new Dictionary<string, ITaskItem[]> ();
102 this.currentlyBuildingProjectsStack = new Stack<Project> ();
103 this.Toolsets = new ToolsetCollection ();
104 LoadDefaultToolsets ();
105 defaultTasksTableByToolsVersion = new Dictionary<string, TaskDatabase> ();
108 //FIXME: should be loaded from config file
109 void LoadDefaultToolsets ()
111 Toolsets.Add (new Toolset ("2.0",
112 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20)));
113 Toolsets.Add (new Toolset ("3.0",
114 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30)));
115 Toolsets.Add (new Toolset ("3.5",
116 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35)));
118 Toolsets.Add (new Toolset ("4.0",
119 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
122 Toolsets.Add (new Toolset("12.0",
123 ToolLocationHelper.GetMSBuildInstallPath ("12.0"),
124 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
129 public bool BuildProject (Project project)
132 throw new ArgumentException ("project");
133 builtTargetsOutputByName.Clear ();
134 return project.Build ();
138 public bool BuildProject (Project project, string targetName)
141 throw new ArgumentException ("project");
142 if (targetName == null)
145 return BuildProject (project, new string[] { targetName}, null, BuildSettings.None);
149 public bool BuildProject (Project project, string[] targetNames)
151 return BuildProject (project, targetNames, null, BuildSettings.None);
155 public bool BuildProject (Project project,
156 string[] targetNames,
157 IDictionary targetOutputs)
159 return BuildProject (project, targetNames, targetOutputs, BuildSettings.None);
162 public bool BuildProject (Project project,
163 string[] targetNames,
164 IDictionary targetOutputs,
165 BuildSettings buildFlags)
168 throw new ArgumentException ("project");
169 if (targetNames == null)
172 if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
173 builtTargetsOutputByName.Clear ();
175 if (defaultToolsVersion != null)
176 // it has been explicitly set, xbuild does this..
177 project.ToolsVersion = defaultToolsVersion;
178 return project.Build (targetNames, targetOutputs, buildFlags);
182 public bool BuildProjectFile (string projectFile)
184 return BuildProjectFile (projectFile, new string [0]);
188 public bool BuildProjectFile (string projectFile,
191 return BuildProjectFile (projectFile,
192 targetName == null ? new string [0] : new string [] {targetName});
196 public bool BuildProjectFile (string projectFile,
197 string[] targetNames)
199 return BuildProjectFile (projectFile, targetNames, null);
203 public bool BuildProjectFile (string projectFile,
204 string[] targetNames,
205 BuildPropertyGroup globalProperties)
207 return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
211 public bool BuildProjectFile (string projectFile,
212 string[] targetNames,
213 BuildPropertyGroup globalProperties,
214 IDictionary targetOutputs)
216 return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
219 public bool BuildProjectFile (string projectFile,
220 string[] targetNames,
221 BuildPropertyGroup globalProperties,
222 IDictionary targetOutputs,
223 BuildSettings buildFlags)
225 return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
228 //FIXME: add a test for null @toolsVersion
229 public bool BuildProjectFile (string projectFile,
230 string[] targetNames,
231 BuildPropertyGroup globalProperties,
232 IDictionary targetOutputs,
233 BuildSettings buildFlags, string toolsVersion)
238 result = BuildProjectFileInternal (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, toolsVersion);
240 } catch (InvalidProjectFileException ie) {
241 this.LogErrorWithFilename (projectFile, ie.Message);
242 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
244 } catch (Exception e) {
246 this.LogErrorWithFilename (projectFile, e.Message);
247 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, e.ToString ()));
251 EndEngineBuild (result);
255 bool BuildProjectFileInternal (string projectFile,
256 string[] targetNames,
257 BuildPropertyGroup globalProperties,
258 IDictionary targetOutputs,
259 BuildSettings buildFlags, string toolsVersion)
262 if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
263 builtTargetsOutputByName.Clear ();
267 bool newProject = false;
268 if (!projects.TryGetValue (projectFile, out project)) {
269 project = CreateNewProject ();
273 BuildPropertyGroup engine_old_grp = null;
274 BuildPropertyGroup project_old_grp = null;
275 if (globalProperties != null) {
276 engine_old_grp = GlobalProperties.Clone (true);
277 project_old_grp = project.GlobalProperties.Clone (true);
279 // Override project's global properties with the
280 // ones explicitlcur_y specified here
281 foreach (BuildProperty bp in globalProperties)
282 project.GlobalProperties.AddProperty (bp);
285 project.NeedToReevaluate ();
289 project.Load (projectFile);
292 string oldProjectToolsVersion = project.ToolsVersion;
293 if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
294 // no tv specified, let the project inherit it from the
295 // engine. 'defaultToolsVersion' will be effective only
296 // it has been overridden. Otherwise, the project's own
298 project.ToolsVersion = defaultToolsVersion;
300 project.ToolsVersion = toolsVersion;
303 return project.Build (targetNames, targetOutputs, buildFlags);
305 project.ToolsVersion = oldProjectToolsVersion;
308 if (globalProperties != null) {
309 GlobalProperties = engine_old_grp;
310 project.GlobalProperties = project_old_grp;
317 if (BinPath == null) {
318 throw new InvalidOperationException ("Before a project can be instantiated, " +
319 "Engine.BinPath must be set to the location on disk where MSBuild " +
320 "is installed. This is used to evaluate $(MSBuildBinPath).");
324 public Project CreateNewProject ()
326 return new Project (this);
329 public Project GetLoadedProject (string projectFullFileName)
331 if (projectFullFileName == null)
332 throw new ArgumentNullException ("projectFullFileName");
335 projects.TryGetValue (projectFullFileName, out project);
340 internal void RemoveLoadedProject (Project p)
342 if (!String.IsNullOrEmpty (p.FullFileName)) {
343 ClearBuiltTargetsForProject (p);
344 projects.Remove (p.FullFileName);
348 internal void AddLoadedProject (Project p)
350 if (p.FullFileName != String.Empty)
351 projects.Add (p.FullFileName, p);
354 public void UnloadProject (Project project)
357 throw new ArgumentNullException ("project");
359 if (project.ParentEngine != this)
360 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
362 project.CheckUnloaded ();
364 RemoveLoadedProject (project);
369 public void UnloadAllProjects ()
371 IList<Project> values = new List<Project> (projects.Values);
372 foreach (Project p in values)
377 public void RegisterLogger (ILogger logger)
380 throw new ArgumentNullException ("logger");
382 logger.Initialize (eventSource);
383 loggers.Add (logger);
387 public void UnregisterAllLoggers ()
389 // FIXME: check if build succeeded
390 // FIXME: it shouldn't be here
392 LogBuildFinished (true);
393 foreach (ILogger i in loggers) {
399 void StartEngineBuild ()
407 void EndEngineBuild (bool succeeded)
409 if (buildStarted && currentlyBuildingProjectsStack.Count == 0) {
410 LogBuildFinished (succeeded);
411 buildStarted = false;
415 internal void StartProjectBuild (Project project, string [] target_names)
419 if (currentlyBuildingProjectsStack.Count == 0 ||
420 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
421 LogProjectStarted (project, target_names);
423 currentlyBuildingProjectsStack.Push (project);
426 internal void EndProjectBuild (Project project, bool succeeded)
429 throw new Exception ("build isnt started currently");
431 Project top_project = currentlyBuildingProjectsStack.Pop ();
433 if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
434 throw new Exception (String.Format (
435 "INTERNAL ERROR: Project finishing is not the same as the one on top " +
436 "of the stack. Project: {0} Top of stack: {1}",
437 project.FullFileName, top_project.FullFileName));
439 if (currentlyBuildingProjectsStack.Count == 0 ||
440 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
441 LogProjectFinished (top_project, succeeded);
443 EndEngineBuild (succeeded);
446 internal void ClearBuiltTargetsForProject (Project project)
448 string project_key = project.GetKeyForTarget (String.Empty, false);
449 var to_remove_keys = BuiltTargetsOutputByName.Keys.Where (key => key.StartsWith (project_key)).ToList ();
450 foreach (string to_remove_key in to_remove_keys)
451 BuiltTargetsOutputByName.Remove (to_remove_key);
454 void LogProjectStarted (Project project, string [] target_names)
457 if (target_names == null || target_names.Length == 0)
458 targets = String.Empty;
460 targets = String.Join (";", target_names);
462 ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
463 project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
465 eventSource.FireProjectStarted (this, psea);
468 void LogProjectFinished (Project project, bool succeeded)
470 ProjectFinishedEventArgs pfea;
471 pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
472 eventSource.FireProjectFinished (this, pfea);
475 void LogBuildStarted ()
477 BuildStartedEventArgs bsea;
478 bsea = new BuildStartedEventArgs ("Build started.", null);
479 eventSource.FireBuildStarted (this, bsea);
482 void LogBuildFinished (bool succeeded)
484 BuildFinishedEventArgs bfea;
485 bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
486 eventSource.FireBuildFinished (this, bfea);
489 internal TaskDatabase GetDefaultTasks (string toolsVersion)
492 if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
495 var toolset = Toolsets [toolsVersion];
497 throw new UnknownToolsVersionException (toolsVersion);
499 string toolsPath = toolset.ToolsPath;
500 string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
501 this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
503 // set a empty taskdb here, because the project loading the tasks
504 // file will try to get the default task db
505 defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
507 db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
512 TaskDatabase RegisterDefaultTasks (string tasksFile)
514 Project defaultTasksProject = CreateNewProject ();
517 if (File.Exists (tasksFile)) {
518 defaultTasksProject.Load (tasksFile);
519 db = defaultTasksProject.TaskDatabase;
521 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
522 db = new TaskDatabase ();
528 public string BinPath {
529 get { return binPath; }
530 set { binPath = value; }
533 public bool BuildEnabled {
534 get { return buildEnabled; }
535 set { buildEnabled = value; }
538 public static Version Version {
539 get { return version; }
542 public static Engine GlobalEngine {
544 if (globalEngine == null)
545 globalEngine = new Engine ();
550 public BuildPropertyGroup GlobalProperties {
551 get { return global_properties; }
552 set { global_properties = value; }
555 public ToolsetCollection Toolsets {
559 public string DefaultToolsVersion {
561 // This is used as the fall back version if the
562 // project can't find a version to use
563 // Hard-coded to 2.0, so it allows even vs2005 projects
564 // to build correctly, as they won't have a ToolsVersion
566 return String.IsNullOrEmpty (defaultToolsVersion)
568 : defaultToolsVersion;
571 if (Toolsets [value] == null)
572 throw new UnknownToolsVersionException (value);
573 defaultToolsVersion = value;
577 public bool IsBuilding {
578 get { return buildStarted; }
581 public bool OnlyLogCriticalEvents {
582 get { return eventSource.OnlyLogCriticalEvents; }
583 set { eventSource.OnlyLogCriticalEvents = value; }
586 internal EventSource EventSource {
587 get { return eventSource; }
590 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
591 get { return builtTargetsOutputByName; }