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)
232 if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
233 builtTargetsOutputByName.Clear ();
237 bool newProject = false;
238 if (!projects.TryGetValue (projectFile, out project)) {
239 project = CreateNewProject ();
243 BuildPropertyGroup engine_old_grp = null;
244 BuildPropertyGroup project_old_grp = null;
245 if (globalProperties != null) {
246 engine_old_grp = GlobalProperties.Clone (true);
247 project_old_grp = project.GlobalProperties.Clone (true);
249 // Override project's global properties with the
250 // ones explicitlcur_y specified here
251 foreach (BuildProperty bp in globalProperties)
252 project.GlobalProperties.AddProperty (bp);
255 project.NeedToReevaluate ();
259 project.Load (projectFile);
262 string oldProjectToolsVersion = project.ToolsVersion;
263 if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
264 // no tv specified, let the project inherit it from the
265 // engine. 'defaultToolsVersion' will be effective only
266 // it has been overridden. Otherwise, the project's own
268 project.ToolsVersion = defaultToolsVersion;
270 project.ToolsVersion = toolsVersion;
273 return project.Build (targetNames, targetOutputs, buildFlags);
275 project.ToolsVersion = oldProjectToolsVersion;
278 if (globalProperties != null) {
279 GlobalProperties = engine_old_grp;
280 project.GlobalProperties = project_old_grp;
287 if (BinPath == null) {
288 throw new InvalidOperationException ("Before a project can be instantiated, " +
289 "Engine.BinPath must be set to the location on disk where MSBuild " +
290 "is installed. This is used to evaluate $(MSBuildBinPath).");
294 public Project CreateNewProject ()
296 return new Project (this);
299 public Project GetLoadedProject (string projectFullFileName)
301 if (projectFullFileName == null)
302 throw new ArgumentNullException ("projectFullFileName");
305 projects.TryGetValue (projectFullFileName, out project);
310 internal void RemoveLoadedProject (Project p)
312 if (!String.IsNullOrEmpty (p.FullFileName)) {
313 ClearBuiltTargetsForProject (p);
314 projects.Remove (p.FullFileName);
318 internal void AddLoadedProject (Project p)
320 if (p.FullFileName != String.Empty)
321 projects.Add (p.FullFileName, p);
324 public void UnloadProject (Project project)
327 throw new ArgumentNullException ("project");
329 if (project.ParentEngine != this)
330 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
332 project.CheckUnloaded ();
334 RemoveLoadedProject (project);
339 public void UnloadAllProjects ()
341 IList<Project> values = new List<Project> (projects.Values);
342 foreach (Project p in values)
347 public void RegisterLogger (ILogger logger)
350 throw new ArgumentNullException ("logger");
352 logger.Initialize (eventSource);
353 loggers.Add (logger);
357 public void UnregisterAllLoggers ()
359 // FIXME: check if build succeeded
360 // FIXME: it shouldn't be here
362 LogBuildFinished (true);
363 foreach (ILogger i in loggers) {
369 internal void StartProjectBuild (Project project, string [] target_names)
376 if (currentlyBuildingProjectsStack.Count == 0 ||
377 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
378 LogProjectStarted (project, target_names);
380 currentlyBuildingProjectsStack.Push (project);
383 internal void EndProjectBuild (Project project, bool succeeded)
386 throw new Exception ("build isnt started currently");
388 Project top_project = currentlyBuildingProjectsStack.Pop ();
390 if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
391 throw new Exception (String.Format (
392 "INTERNAL ERROR: Project finishing is not the same as the one on top " +
393 "of the stack. Project: {0} Top of stack: {1}",
394 project.FullFileName, top_project.FullFileName));
396 if (currentlyBuildingProjectsStack.Count == 0 ||
397 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
398 LogProjectFinished (top_project, succeeded);
400 if (currentlyBuildingProjectsStack.Count == 0) {
401 LogBuildFinished (succeeded);
402 buildStarted = false;
406 internal void ClearBuiltTargetsForProject (Project project)
408 string project_key = project.GetKeyForTarget (String.Empty, false);
409 var to_remove_keys = BuiltTargetsOutputByName.Keys.Where (key => key.StartsWith (project_key)).ToList ();
410 foreach (string to_remove_key in to_remove_keys)
411 BuiltTargetsOutputByName.Remove (to_remove_key);
414 void LogProjectStarted (Project project, string [] target_names)
417 if (target_names == null || target_names.Length == 0)
418 targets = String.Empty;
420 targets = String.Join (";", target_names);
422 ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
423 project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
425 eventSource.FireProjectStarted (this, psea);
428 void LogProjectFinished (Project project, bool succeeded)
430 ProjectFinishedEventArgs pfea;
431 pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
432 eventSource.FireProjectFinished (this, pfea);
435 void LogBuildStarted ()
437 BuildStartedEventArgs bsea;
438 bsea = new BuildStartedEventArgs ("Build started.", null);
439 eventSource.FireBuildStarted (this, bsea);
442 void LogBuildFinished (bool succeeded)
444 BuildFinishedEventArgs bfea;
445 bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
446 eventSource.FireBuildFinished (this, bfea);
449 internal TaskDatabase GetDefaultTasks (string toolsVersion)
452 if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
455 var toolset = Toolsets [toolsVersion];
457 throw new UnknownToolsVersionException (toolsVersion);
459 string toolsPath = toolset.ToolsPath;
460 string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
461 this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
463 // set a empty taskdb here, because the project loading the tasks
464 // file will try to get the default task db
465 defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
467 db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
472 TaskDatabase RegisterDefaultTasks (string tasksFile)
474 Project defaultTasksProject = CreateNewProject ();
477 if (File.Exists (tasksFile)) {
478 defaultTasksProject.Load (tasksFile);
479 db = defaultTasksProject.TaskDatabase;
481 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
482 db = new TaskDatabase ();
488 public string BinPath {
489 get { return binPath; }
490 set { binPath = value; }
493 public bool BuildEnabled {
494 get { return buildEnabled; }
495 set { buildEnabled = value; }
498 public static Version Version {
499 get { return version; }
502 public static Engine GlobalEngine {
504 if (globalEngine == null)
505 globalEngine = new Engine ();
510 public BuildPropertyGroup GlobalProperties {
511 get { return global_properties; }
512 set { global_properties = value; }
515 public ToolsetCollection Toolsets {
519 public string DefaultToolsVersion {
521 // This is used as the fall back version if the
522 // project can't find a version to use
523 // Hard-coded to 2.0, so it allows even vs2005 projects
524 // to build correctly, as they won't have a ToolsVersion
526 return String.IsNullOrEmpty (defaultToolsVersion)
528 : defaultToolsVersion;
531 if (Toolsets [value] == null)
532 throw new UnknownToolsVersionException (value);
533 defaultToolsVersion = value;
537 public bool IsBuilding {
538 get { return buildStarted; }
541 public bool OnlyLogCriticalEvents {
542 get { return eventSource.OnlyLogCriticalEvents; }
543 set { eventSource.OnlyLogCriticalEvents = value; }
546 internal EventSource EventSource {
547 get { return eventSource; }
550 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
551 get { return builtTargetsOutputByName; }