Add unit test for AggregateException.GetBaseException that works on .net but is broke...
[mono.git] / mcs / class / Microsoft.Build.Engine / Microsoft.Build.BuildEngine / Engine.cs
1 //
2 // Engine.cs: Main engine of XBuild.
3 //
4 // Author:
5 //   Marek Sieradzki (marek.sieradzki@gmail.com)
6 // 
7 // (C) 2005 Marek Sieradzki
8 //
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:
16 //
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 //
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.
27
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Linq;
33 using Microsoft.Build.Framework;
34 using Microsoft.Build.Utilities;
35 using Mono.XBuild.Utilities;
36
37 namespace Microsoft.Build.BuildEngine {
38         public class Engine {
39                 
40                 string                  binPath;
41                 bool                    buildEnabled;
42                 Dictionary<string, TaskDatabase> defaultTasksTableByToolsVersion;
43                 const string            defaultTasksProjectName = "Microsoft.Common.tasks";
44                 EventSource             eventSource;
45                 bool                    buildStarted;
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;
53
54                 // the key here represents the project+target+global_properties set
55                 Dictionary <string, ITaskItem[]> builtTargetsOutputByName;
56                 Stack<Project> currentlyBuildingProjectsStack;
57
58                 static Engine           globalEngine;
59                 static Version          version;
60
61                 static Engine ()
62                 {
63                         version = new Version ("0.1");
64                 }
65                 
66                 public Engine ()
67                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
68                 {
69                 }
70
71                 public Engine (ToolsetDefinitionLocations locations)
72                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
73                 {
74                         //toolsetLocations = locations;
75                 }
76                 
77                 public Engine (BuildPropertyGroup globalProperties)
78                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
79                 {
80                         this.global_properties = globalProperties;
81                 }
82
83                 public Engine (BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations)
84                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
85                 {
86                         this.global_properties = globalProperties;
87                         //toolsetLocations = locations;
88                 }
89
90                 // engine should be invoked with path where binary files are
91                 // to find microsoft.build.tasks
92                 public Engine (string binPath)
93                 {
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> ();
106                 }
107
108                 //FIXME: should be loaded from config file
109                 void LoadDefaultToolsets ()
110                 {
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)));
117 #if NET_4_0
118                         Toolsets.Add (new Toolset ("4.0",
119                                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
120 #endif
121 #if NET_4_5
122                         Toolsets.Add (new Toolset("12.0",
123                                                 ToolLocationHelper.GetMSBuildInstallPath ("12.0"),
124                                                 ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version40)));
125 #endif
126                 }
127                 
128                 [MonoTODO]
129                 public bool BuildProject (Project project)
130                 {
131                         if (project == null)
132                                 throw new ArgumentException ("project");
133                         builtTargetsOutputByName.Clear ();
134                         return project.Build ();
135                 }
136                 
137                 [MonoTODO]
138                 public bool BuildProject (Project project, string targetName)
139                 {
140                         if (project == null)
141                                 throw new ArgumentException ("project");
142                         if (targetName == null)
143                                 return false;
144
145                         return BuildProject (project, new string[] { targetName}, null, BuildSettings.None);
146                 }
147                 
148                 [MonoTODO]
149                 public bool BuildProject (Project project, string[] targetNames)
150                 {
151                         return BuildProject (project, targetNames, null, BuildSettings.None);
152                 }
153
154                 [MonoTODO]
155                 public bool BuildProject (Project project,
156                                           string[] targetNames,
157                                           IDictionary targetOutputs)
158                 {
159                         return BuildProject (project, targetNames, targetOutputs, BuildSettings.None);
160                 }
161                 
162                 public bool BuildProject (Project project,
163                                           string[] targetNames,
164                                           IDictionary targetOutputs,
165                                           BuildSettings buildFlags)
166                 {
167                         if (project == null)
168                                 throw new ArgumentException ("project");
169                         if (targetNames == null)
170                                 return false;
171
172                         if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
173                                 builtTargetsOutputByName.Clear ();
174
175                         if (defaultToolsVersion != null)
176                                 // it has been explicitly set, xbuild does this..
177                                 project.ToolsVersion = defaultToolsVersion;
178                         return project.Build (targetNames, targetOutputs, buildFlags);
179                 }
180
181                 [MonoTODO]
182                 public bool BuildProjectFile (string projectFile)
183                 {
184                         return BuildProjectFile (projectFile, new string [0]);
185                 }
186                 
187                 [MonoTODO]
188                 public bool BuildProjectFile (string projectFile,
189                                               string targetName)
190                 {
191                         return BuildProjectFile (projectFile,
192                                                  targetName == null ? new string [0] : new string [] {targetName});
193                 }
194                 
195                 [MonoTODO]
196                 public bool BuildProjectFile (string projectFile,
197                                               string[] targetNames)
198                 {
199                         return BuildProjectFile (projectFile, targetNames, null);
200                 }
201                 
202                 [MonoTODO]
203                 public bool BuildProjectFile (string projectFile,
204                                               string[] targetNames,
205                                               BuildPropertyGroup globalProperties)
206                 {
207                         return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
208                 }
209                 
210                 [MonoTODO]
211                 public bool BuildProjectFile (string projectFile,
212                                               string[] targetNames,
213                                               BuildPropertyGroup globalProperties,
214                                               IDictionary targetOutputs)
215                 {
216                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
217                 }
218                 
219                 public bool BuildProjectFile (string projectFile,
220                                               string[] targetNames,
221                                               BuildPropertyGroup globalProperties,
222                                               IDictionary targetOutputs,
223                                               BuildSettings buildFlags)
224                 {
225                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
226                 }
227                         
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)
234                 {
235                         bool result = false;
236                         try {
237                                 StartEngineBuild ();
238                                 result = BuildProjectFileInternal (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, toolsVersion);
239                                 return result;
240                         } catch (InvalidProjectFileException ie) {
241                                 this.LogErrorWithFilename (projectFile, ie.Message);
242                                 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
243                                 return false;
244                         } catch (Exception e) {
245                                 if (buildStarted) {
246                                         this.LogErrorWithFilename (projectFile, e.Message);
247                                         this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, e.ToString ()));
248                                 }
249                                 throw;
250                         } finally {
251                                 EndEngineBuild (result);
252                         }
253                 }
254
255                 bool BuildProjectFileInternal (string projectFile,
256                                               string[] targetNames,
257                                               BuildPropertyGroup globalProperties,
258                                               IDictionary targetOutputs,
259                                               BuildSettings buildFlags, string toolsVersion)
260                 {
261
262                         if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
263                                 builtTargetsOutputByName.Clear ();
264
265                         Project project;
266
267                         bool newProject = false;
268                         if (!projects.TryGetValue (projectFile, out project)) {
269                                 project = CreateNewProject ();
270                                 newProject = true;
271                         }
272
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);
278
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);
283
284                                 if (!newProject)
285                                         project.NeedToReevaluate ();
286                         }
287
288                         if (newProject)
289                                 project.Load (projectFile);
290
291                         try {
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
297                                         // tv will be used.
298                                         project.ToolsVersion = defaultToolsVersion;
299                                 else
300                                         project.ToolsVersion = toolsVersion;
301
302                                 try {
303                                         return project.Build (targetNames, targetOutputs, buildFlags);
304                                 } finally {
305                                         project.ToolsVersion = oldProjectToolsVersion;
306                                 }
307                         } finally {
308                                 if (globalProperties != null) {
309                                         GlobalProperties = engine_old_grp;
310                                         project.GlobalProperties = project_old_grp;
311                                 }
312                         }
313                 }
314
315                 void CheckBinPath ()
316                 {
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).");
321                         }
322                 }
323
324                 public Project CreateNewProject ()
325                 {
326                         return new Project (this);
327                 }
328
329                 public Project GetLoadedProject (string projectFullFileName)
330                 {
331                         if (projectFullFileName == null)
332                                 throw new ArgumentNullException ("projectFullFileName");
333                         
334                         Project project;
335                         projects.TryGetValue (projectFullFileName, out project);
336
337                         return project;
338                 }
339
340                 internal void RemoveLoadedProject (Project p)
341                 {
342                         if (!String.IsNullOrEmpty (p.FullFileName)) {
343                                 ClearBuiltTargetsForProject (p);
344                                 projects.Remove (p.FullFileName);
345                         }
346                 }
347
348                 internal void AddLoadedProject (Project p)
349                 {
350                         if (p.FullFileName != String.Empty)
351                                 projects.Add (p.FullFileName, p);
352                 }
353         
354                 public void UnloadProject (Project project)
355                 {
356                         if (project == null)
357                                 throw new ArgumentNullException ("project");
358
359                         if (project.ParentEngine != this)
360                                 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
361                         
362                         project.CheckUnloaded ();
363                         
364                         RemoveLoadedProject (project);
365                         
366                         project.Unload ();
367                 }
368
369                 public void UnloadAllProjects ()
370                 {
371                         IList<Project> values = new List<Project> (projects.Values);
372                         foreach (Project p in values)
373                                 UnloadProject (p);
374                 }
375
376                 [MonoTODO]
377                 public void RegisterLogger (ILogger logger)
378                 {
379                         if (logger == null)
380                                 throw new ArgumentNullException ("logger");
381                         
382                         logger.Initialize (eventSource);
383                         loggers.Add (logger);
384                 }
385                 
386                 [MonoTODO]
387                 public void UnregisterAllLoggers ()
388                 {
389                         // FIXME: check if build succeeded
390                         // FIXME: it shouldn't be here
391                         if (buildStarted)
392                                 LogBuildFinished (true);
393                         foreach (ILogger i in loggers) {
394                                 i.Shutdown ();
395                         }
396                         loggers.Clear ();
397                 }
398
399                 void StartEngineBuild ()
400                 {
401                         if (!buildStarted) {
402                                 LogBuildStarted ();
403                                 buildStarted = true;
404                         }
405                 }
406
407                 void EndEngineBuild (bool succeeded)
408                 {
409                         if (buildStarted && currentlyBuildingProjectsStack.Count == 0) {
410                                 LogBuildFinished (succeeded);
411                                 buildStarted = false;
412                         }
413                 }
414
415                 internal void StartProjectBuild (Project project, string [] target_names)
416                 {
417                         StartEngineBuild ();
418
419                         if (currentlyBuildingProjectsStack.Count == 0 ||
420                                 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
421                                         LogProjectStarted (project, target_names);
422
423                         currentlyBuildingProjectsStack.Push (project);
424                 }
425
426                 internal void EndProjectBuild (Project project, bool succeeded)
427                 {
428                         if (!buildStarted)
429                                 throw new Exception ("build isnt started currently");
430
431                         Project top_project = currentlyBuildingProjectsStack.Pop ();
432
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));
438
439                         if (currentlyBuildingProjectsStack.Count == 0 ||
440                                 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
441                                 LogProjectFinished (top_project, succeeded);
442
443                         EndEngineBuild (succeeded);
444                 }
445
446                 internal void ClearBuiltTargetsForProject (Project project)
447                 {
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);
452                 }
453
454                 void LogProjectStarted (Project project, string [] target_names)
455                 {
456                         string targets;
457                         if (target_names == null || target_names.Length == 0)
458                                 targets = String.Empty;
459                         else
460                                 targets = String.Join (";", target_names);
461
462                         ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
463                                         project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
464
465                         eventSource.FireProjectStarted (this, psea);
466                 }
467
468                 void LogProjectFinished (Project project, bool succeeded)
469                 {
470                         ProjectFinishedEventArgs pfea;
471                         pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
472                         eventSource.FireProjectFinished (this, pfea);
473                 }
474
475                 void LogBuildStarted ()
476                 {
477                         BuildStartedEventArgs bsea;
478                         bsea = new BuildStartedEventArgs ("Build started.", null);
479                         eventSource.FireBuildStarted (this, bsea);
480                 }
481                 
482                 void LogBuildFinished (bool succeeded)
483                 {
484                         BuildFinishedEventArgs bfea;
485                         bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
486                         eventSource.FireBuildFinished (this, bfea);
487                 }
488
489                 internal TaskDatabase GetDefaultTasks (string toolsVersion)
490                 {
491                         TaskDatabase db;
492                         if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
493                                 return db;
494
495                         var toolset = Toolsets [toolsVersion];
496                         if (toolset == null)
497                                 throw new UnknownToolsVersionException (toolsVersion);
498
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);
502
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 ();
506
507                         db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
508
509                         return db;
510                 }
511                 
512                 TaskDatabase RegisterDefaultTasks (string tasksFile)
513                 {
514                         Project defaultTasksProject = CreateNewProject ();
515                         TaskDatabase db;
516                         
517                         if (File.Exists (tasksFile)) {
518                                 defaultTasksProject.Load (tasksFile);
519                                 db = defaultTasksProject.TaskDatabase;
520                         } else {
521                                 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
522                                 db = new TaskDatabase ();
523                         }
524
525                         return db;
526                 }
527
528                 public string BinPath {
529                         get { return binPath; }
530                         set { binPath = value; }
531                 }
532
533                 public bool BuildEnabled {
534                         get { return buildEnabled; }
535                         set { buildEnabled = value; }
536                 }
537
538                 public static Version Version {
539                         get { return version; }
540                 }
541
542                 public static Engine GlobalEngine {
543                         get {
544                                 if (globalEngine == null)
545                                         globalEngine = new Engine ();
546                                 return globalEngine;
547                         }
548                 }
549
550                 public BuildPropertyGroup GlobalProperties {
551                         get { return global_properties; }
552                         set { global_properties = value; }
553                 }
554                 
555                 public ToolsetCollection Toolsets {
556                         get; private set;
557                 }
558
559                 public string DefaultToolsVersion {
560                         get {
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
565                                 // set!
566                                 return String.IsNullOrEmpty (defaultToolsVersion)
567                                                 ? "2.0"
568                                                 : defaultToolsVersion;
569                         }
570                         set {
571                                 if (Toolsets [value] == null)
572                                         throw new UnknownToolsVersionException (value);
573                                 defaultToolsVersion = value;
574                         }
575                 }
576                 
577                 public bool IsBuilding {
578                         get { return buildStarted; }
579                 }
580                 
581                 public bool OnlyLogCriticalEvents {
582                         get { return eventSource.OnlyLogCriticalEvents; }
583                         set { eventSource.OnlyLogCriticalEvents = value; }
584                 }
585                 
586                 internal EventSource EventSource {
587                         get { return eventSource; }
588                 }
589                 
590                 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
591                         get { return builtTargetsOutputByName; }
592                 }
593         }
594 }