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.
31 using System.Collections;
32 using System.Collections.Generic;
35 using Microsoft.Build.Framework;
36 using Microsoft.Build.Utilities;
37 using Mono.XBuild.Utilities;
39 namespace Microsoft.Build.BuildEngine {
44 Dictionary<string, TaskDatabase> defaultTasksTableByToolsVersion;
45 const string defaultTasksProjectName = "Microsoft.Common.tasks";
46 EventSource eventSource;
48 //ToolsetDefinitionLocations toolsetLocations;
49 BuildPropertyGroup global_properties;
50 //IDictionary importedProjects;
51 List <ILogger> loggers;
52 //bool onlyLogCriticalEvents;
53 Dictionary <string, Project> projects;
54 string defaultToolsVersion;
56 // the key here represents the project+target+global_properties set
57 Dictionary <string, ITaskItem[]> builtTargetsOutputByName;
58 Stack<Project> currentlyBuildingProjectsStack;
60 static Engine globalEngine;
61 static Version version;
65 version = new Version ("0.1");
69 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
73 public Engine (ToolsetDefinitionLocations locations)
74 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
76 //toolsetLocations = locations;
79 public Engine (BuildPropertyGroup globalProperties)
80 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
82 this.global_properties = globalProperties;
85 public Engine (BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations)
86 : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
88 this.global_properties = globalProperties;
89 //toolsetLocations = locations;
92 // engine should be invoked with path where binary files are
93 // to find microsoft.build.tasks
94 public Engine (string binPath)
96 this.binPath = binPath;
97 this.buildEnabled = true;
98 this.projects = new Dictionary <string, Project> ();
99 this.eventSource = new EventSource ();
100 this.loggers = new List <ILogger> ();
101 this.buildStarted = false;
102 this.global_properties = new BuildPropertyGroup ();
103 this.builtTargetsOutputByName = new Dictionary<string, ITaskItem[]> ();
104 this.currentlyBuildingProjectsStack = new Stack<Project> ();
105 this.Toolsets = new ToolsetCollection ();
106 LoadDefaultToolsets ();
107 defaultTasksTableByToolsVersion = new Dictionary<string, TaskDatabase> ();
110 //FIXME: should be loaded from config file
111 void LoadDefaultToolsets ()
113 Toolsets.Add (new Toolset ("2.0",
114 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20)));
115 Toolsets.Add (new Toolset ("3.0",
116 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version30)));
117 Toolsets.Add (new Toolset ("3.5",
118 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version35)));
120 Toolsets.Add (new Toolset ("4.0",
121 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
126 public bool BuildProject (Project project)
129 throw new ArgumentException ("project");
130 builtTargetsOutputByName.Clear ();
131 return project.Build ();
135 public bool BuildProject (Project project, string targetName)
138 throw new ArgumentException ("project");
139 if (targetName == null)
142 return BuildProject (project, new string[] { targetName}, null, BuildSettings.None);
146 public bool BuildProject (Project project, string[] targetNames)
148 return BuildProject (project, targetNames, null, BuildSettings.None);
152 public bool BuildProject (Project project,
153 string[] targetNames,
154 IDictionary targetOutputs)
156 return BuildProject (project, targetNames, targetOutputs, BuildSettings.None);
159 public bool BuildProject (Project project,
160 string[] targetNames,
161 IDictionary targetOutputs,
162 BuildSettings buildFlags)
165 throw new ArgumentException ("project");
166 if (targetNames == null)
169 if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
170 builtTargetsOutputByName.Clear ();
172 if (defaultToolsVersion != null)
173 // it has been explicitly set, xbuild does this..
174 project.ToolsVersion = defaultToolsVersion;
175 return project.Build (targetNames, targetOutputs, buildFlags);
179 public bool BuildProjectFile (string projectFile)
181 return BuildProjectFile (projectFile, new string [0]);
185 public bool BuildProjectFile (string projectFile,
188 return BuildProjectFile (projectFile,
189 targetName == null ? new string [0] : new string [] {targetName});
193 public bool BuildProjectFile (string projectFile,
194 string[] targetNames)
196 return BuildProjectFile (projectFile, targetNames, null);
200 public bool BuildProjectFile (string projectFile,
201 string[] targetNames,
202 BuildPropertyGroup globalProperties)
204 return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
208 public bool BuildProjectFile (string projectFile,
209 string[] targetNames,
210 BuildPropertyGroup globalProperties,
211 IDictionary targetOutputs)
213 return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
216 public bool BuildProjectFile (string projectFile,
217 string[] targetNames,
218 BuildPropertyGroup globalProperties,
219 IDictionary targetOutputs,
220 BuildSettings buildFlags)
222 return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
225 //FIXME: add a test for null @toolsVersion
226 public bool BuildProjectFile (string projectFile,
227 string[] targetNames,
228 BuildPropertyGroup globalProperties,
229 IDictionary targetOutputs,
230 BuildSettings buildFlags, string toolsVersion)
235 result = BuildProjectFileInternal (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, toolsVersion);
237 } catch (InvalidProjectFileException ie) {
238 this.LogErrorWithFilename (projectFile, ie.Message);
239 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
241 } catch (Exception e) {
243 this.LogErrorWithFilename (projectFile, e.Message);
244 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, e.ToString ()));
248 EndEngineBuild (result);
252 bool BuildProjectFileInternal (string projectFile,
253 string[] targetNames,
254 BuildPropertyGroup globalProperties,
255 IDictionary targetOutputs,
256 BuildSettings buildFlags, string toolsVersion)
259 if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
260 builtTargetsOutputByName.Clear ();
264 bool newProject = false;
265 if (!projects.TryGetValue (projectFile, out project)) {
266 project = CreateNewProject ();
270 BuildPropertyGroup engine_old_grp = null;
271 BuildPropertyGroup project_old_grp = null;
272 if (globalProperties != null) {
273 engine_old_grp = GlobalProperties.Clone (true);
274 project_old_grp = project.GlobalProperties.Clone (true);
276 // Override project's global properties with the
277 // ones explicitlcur_y specified here
278 foreach (BuildProperty bp in globalProperties)
279 project.GlobalProperties.AddProperty (bp);
282 project.NeedToReevaluate ();
286 project.Load (projectFile);
289 string oldProjectToolsVersion = project.ToolsVersion;
290 if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
291 // no tv specified, let the project inherit it from the
292 // engine. 'defaultToolsVersion' will be effective only
293 // it has been overridden. Otherwise, the project's own
295 project.ToolsVersion = defaultToolsVersion;
297 project.ToolsVersion = toolsVersion;
300 return project.Build (targetNames, targetOutputs, buildFlags);
302 project.ToolsVersion = oldProjectToolsVersion;
305 if (globalProperties != null) {
306 GlobalProperties = engine_old_grp;
307 project.GlobalProperties = project_old_grp;
314 if (BinPath == null) {
315 throw new InvalidOperationException ("Before a project can be instantiated, " +
316 "Engine.BinPath must be set to the location on disk where MSBuild " +
317 "is installed. This is used to evaluate $(MSBuildBinPath).");
321 public Project CreateNewProject ()
323 return new Project (this);
326 public Project GetLoadedProject (string projectFullFileName)
328 if (projectFullFileName == null)
329 throw new ArgumentNullException ("projectFullFileName");
332 projects.TryGetValue (projectFullFileName, out project);
337 internal void RemoveLoadedProject (Project p)
339 if (!String.IsNullOrEmpty (p.FullFileName)) {
340 ClearBuiltTargetsForProject (p);
341 projects.Remove (p.FullFileName);
345 internal void AddLoadedProject (Project p)
347 if (p.FullFileName != String.Empty)
348 projects.Add (p.FullFileName, p);
351 public void UnloadProject (Project project)
354 throw new ArgumentNullException ("project");
356 if (project.ParentEngine != this)
357 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
359 project.CheckUnloaded ();
361 RemoveLoadedProject (project);
366 public void UnloadAllProjects ()
368 IList<Project> values = new List<Project> (projects.Values);
369 foreach (Project p in values)
374 public void RegisterLogger (ILogger logger)
377 throw new ArgumentNullException ("logger");
379 logger.Initialize (eventSource);
380 loggers.Add (logger);
384 public void UnregisterAllLoggers ()
386 // FIXME: check if build succeeded
387 // FIXME: it shouldn't be here
389 LogBuildFinished (true);
390 foreach (ILogger i in loggers) {
396 void StartEngineBuild ()
404 void EndEngineBuild (bool succeeded)
406 if (buildStarted && currentlyBuildingProjectsStack.Count == 0) {
407 LogBuildFinished (succeeded);
408 buildStarted = false;
412 internal void StartProjectBuild (Project project, string [] target_names)
416 if (currentlyBuildingProjectsStack.Count == 0 ||
417 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
418 LogProjectStarted (project, target_names);
420 currentlyBuildingProjectsStack.Push (project);
423 internal void EndProjectBuild (Project project, bool succeeded)
426 throw new Exception ("build isnt started currently");
428 Project top_project = currentlyBuildingProjectsStack.Pop ();
430 if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
431 throw new Exception (String.Format (
432 "INTERNAL ERROR: Project finishing is not the same as the one on top " +
433 "of the stack. Project: {0} Top of stack: {1}",
434 project.FullFileName, top_project.FullFileName));
436 if (currentlyBuildingProjectsStack.Count == 0 ||
437 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
438 LogProjectFinished (top_project, succeeded);
440 EndEngineBuild (succeeded);
443 internal void ClearBuiltTargetsForProject (Project project)
445 string project_key = project.GetKeyForTarget (String.Empty, false);
446 var to_remove_keys = BuiltTargetsOutputByName.Keys.Where (key => key.StartsWith (project_key)).ToList ();
447 foreach (string to_remove_key in to_remove_keys)
448 BuiltTargetsOutputByName.Remove (to_remove_key);
451 void LogProjectStarted (Project project, string [] target_names)
454 if (target_names == null || target_names.Length == 0)
455 targets = String.Empty;
457 targets = String.Join (";", target_names);
459 ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
460 project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
462 eventSource.FireProjectStarted (this, psea);
465 void LogProjectFinished (Project project, bool succeeded)
467 ProjectFinishedEventArgs pfea;
468 pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
469 eventSource.FireProjectFinished (this, pfea);
472 void LogBuildStarted ()
474 BuildStartedEventArgs bsea;
475 bsea = new BuildStartedEventArgs ("Build started.", null);
476 eventSource.FireBuildStarted (this, bsea);
479 void LogBuildFinished (bool succeeded)
481 BuildFinishedEventArgs bfea;
482 bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
483 eventSource.FireBuildFinished (this, bfea);
486 internal TaskDatabase GetDefaultTasks (string toolsVersion)
489 if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
492 var toolset = Toolsets [toolsVersion];
494 throw new UnknownToolsVersionException (toolsVersion);
496 string toolsPath = toolset.ToolsPath;
497 string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
498 this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
500 // set a empty taskdb here, because the project loading the tasks
501 // file will try to get the default task db
502 defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
504 db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
509 TaskDatabase RegisterDefaultTasks (string tasksFile)
511 Project defaultTasksProject = CreateNewProject ();
514 if (File.Exists (tasksFile)) {
515 defaultTasksProject.Load (tasksFile);
516 db = defaultTasksProject.TaskDatabase;
518 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
519 db = new TaskDatabase ();
525 public string BinPath {
526 get { return binPath; }
527 set { binPath = value; }
530 public bool BuildEnabled {
531 get { return buildEnabled; }
532 set { buildEnabled = value; }
535 public static Version Version {
536 get { return version; }
539 public static Engine GlobalEngine {
541 if (globalEngine == null)
542 globalEngine = new Engine ();
547 public BuildPropertyGroup GlobalProperties {
548 get { return global_properties; }
549 set { global_properties = value; }
552 public ToolsetCollection Toolsets {
556 public string DefaultToolsVersion {
558 // This is used as the fall back version if the
559 // project can't find a version to use
560 // Hard-coded to 2.0, so it allows even vs2005 projects
561 // to build correctly, as they won't have a ToolsVersion
563 return String.IsNullOrEmpty (defaultToolsVersion)
565 : defaultToolsVersion;
568 if (Toolsets [value] == null)
569 throw new UnknownToolsVersionException (value);
570 defaultToolsVersion = value;
574 public bool IsBuilding {
575 get { return buildStarted; }
578 public bool OnlyLogCriticalEvents {
579 get { return eventSource.OnlyLogCriticalEvents; }
580 set { eventSource.OnlyLogCriticalEvents = value; }
583 internal EventSource EventSource {
584 get { return eventSource; }
587 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
588 get { return builtTargetsOutputByName; }