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