Merge pull request #819 from brendanzagaeski/patch-1
[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 XBUILD_12
122                         Toolsets.Add (new Toolset ("12.0", ToolLocationHelper.GetPathToBuildTools ("12.0")));
123 #endif
124                 }
125                 
126                 [MonoTODO]
127                 public bool BuildProject (Project project)
128                 {
129                         if (project == null)
130                                 throw new ArgumentException ("project");
131                         builtTargetsOutputByName.Clear ();
132                         return project.Build ();
133                 }
134                 
135                 [MonoTODO]
136                 public bool BuildProject (Project project, string targetName)
137                 {
138                         if (project == null)
139                                 throw new ArgumentException ("project");
140                         if (targetName == null)
141                                 return false;
142
143                         return BuildProject (project, new string[] { targetName}, null, BuildSettings.None);
144                 }
145                 
146                 [MonoTODO]
147                 public bool BuildProject (Project project, string[] targetNames)
148                 {
149                         return BuildProject (project, targetNames, null, BuildSettings.None);
150                 }
151
152                 [MonoTODO]
153                 public bool BuildProject (Project project,
154                                           string[] targetNames,
155                                           IDictionary targetOutputs)
156                 {
157                         return BuildProject (project, targetNames, targetOutputs, BuildSettings.None);
158                 }
159                 
160                 public bool BuildProject (Project project,
161                                           string[] targetNames,
162                                           IDictionary targetOutputs,
163                                           BuildSettings buildFlags)
164                 {
165                         if (project == null)
166                                 throw new ArgumentException ("project");
167                         if (targetNames == null)
168                                 return false;
169
170                         if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
171                                 builtTargetsOutputByName.Clear ();
172
173                         if (defaultToolsVersion != null)
174                                 // it has been explicitly set, xbuild does this..
175                                 project.ToolsVersion = defaultToolsVersion;
176                         return project.Build (targetNames, targetOutputs, buildFlags);
177                 }
178
179                 [MonoTODO]
180                 public bool BuildProjectFile (string projectFile)
181                 {
182                         return BuildProjectFile (projectFile, new string [0]);
183                 }
184                 
185                 [MonoTODO]
186                 public bool BuildProjectFile (string projectFile,
187                                               string targetName)
188                 {
189                         return BuildProjectFile (projectFile,
190                                                  targetName == null ? new string [0] : new string [] {targetName});
191                 }
192                 
193                 [MonoTODO]
194                 public bool BuildProjectFile (string projectFile,
195                                               string[] targetNames)
196                 {
197                         return BuildProjectFile (projectFile, targetNames, null);
198                 }
199                 
200                 [MonoTODO]
201                 public bool BuildProjectFile (string projectFile,
202                                               string[] targetNames,
203                                               BuildPropertyGroup globalProperties)
204                 {
205                         return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
206                 }
207                 
208                 [MonoTODO]
209                 public bool BuildProjectFile (string projectFile,
210                                               string[] targetNames,
211                                               BuildPropertyGroup globalProperties,
212                                               IDictionary targetOutputs)
213                 {
214                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
215                 }
216                 
217                 public bool BuildProjectFile (string projectFile,
218                                               string[] targetNames,
219                                               BuildPropertyGroup globalProperties,
220                                               IDictionary targetOutputs,
221                                               BuildSettings buildFlags)
222                 {
223                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
224                 }
225                         
226                 //FIXME: add a test for null @toolsVersion
227                 public bool BuildProjectFile (string projectFile,
228                                               string[] targetNames,
229                                               BuildPropertyGroup globalProperties,
230                                               IDictionary targetOutputs,
231                                               BuildSettings buildFlags, string toolsVersion)
232                 {
233                         bool result = false;
234                         try {
235                                 StartEngineBuild ();
236                                 result = BuildProjectFileInternal (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, toolsVersion);
237                                 return result;
238                         } catch (InvalidProjectFileException ie) {
239                                 this.LogErrorWithFilename (projectFile, ie.Message);
240                                 this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, ie.ToString ()));
241                                 return false;
242                         } catch (Exception e) {
243                                 if (buildStarted) {
244                                         this.LogErrorWithFilename (projectFile, e.Message);
245                                         this.LogMessage (MessageImportance.Low, String.Format ("{0}: {1}", projectFile, e.ToString ()));
246                                 }
247                                 throw;
248                         } finally {
249                                 EndEngineBuild (result);
250                         }
251                 }
252
253                 bool BuildProjectFileInternal (string projectFile,
254                                               string[] targetNames,
255                                               BuildPropertyGroup globalProperties,
256                                               IDictionary targetOutputs,
257                                               BuildSettings buildFlags, string toolsVersion)
258                 {
259
260                         if ((buildFlags & BuildSettings.DoNotResetPreviouslyBuiltTargets) != BuildSettings.DoNotResetPreviouslyBuiltTargets)
261                                 builtTargetsOutputByName.Clear ();
262
263                         Project project;
264
265                         bool newProject = false;
266                         if (!projects.TryGetValue (projectFile, out project)) {
267                                 project = CreateNewProject ();
268                                 newProject = true;
269                         }
270
271                         BuildPropertyGroup engine_old_grp = null;
272                         BuildPropertyGroup project_old_grp = null;
273                         if (globalProperties != null) {
274                                 engine_old_grp = GlobalProperties.Clone (true);
275                                 project_old_grp = project.GlobalProperties.Clone (true);
276
277                                 // Override project's global properties with the
278                                 // ones explicitlcur_y specified here
279                                 foreach (BuildProperty bp in globalProperties)
280                                         project.GlobalProperties.AddProperty (bp);
281
282                                 if (!newProject)
283                                         project.NeedToReevaluate ();
284                         }
285
286                         if (newProject)
287                                 project.Load (projectFile);
288
289                         try {
290                                 string oldProjectToolsVersion = project.ToolsVersion;
291                                 if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
292                                         // no tv specified, let the project inherit it from the
293                                         // engine. 'defaultToolsVersion' will be effective only
294                                         // it has been overridden. Otherwise, the project's own
295                                         // tv will be used.
296                                         project.ToolsVersion = defaultToolsVersion;
297                                 else
298                                         project.ToolsVersion = toolsVersion;
299
300                                 try {
301                                         return project.Build (targetNames, targetOutputs, buildFlags);
302                                 } finally {
303                                         project.ToolsVersion = oldProjectToolsVersion;
304                                 }
305                         } finally {
306                                 if (globalProperties != null) {
307                                         GlobalProperties = engine_old_grp;
308                                         project.GlobalProperties = project_old_grp;
309                                 }
310                         }
311                 }
312
313                 void CheckBinPath ()
314                 {
315                         if (BinPath == null) {
316                                 throw new InvalidOperationException ("Before a project can be instantiated, " +
317                                         "Engine.BinPath must be set to the location on disk where MSBuild " + 
318                                         "is installed. This is used to evaluate $(MSBuildBinPath).");
319                         }
320                 }
321
322                 public Project CreateNewProject ()
323                 {
324                         return new Project (this);
325                 }
326
327                 public Project GetLoadedProject (string projectFullFileName)
328                 {
329                         if (projectFullFileName == null)
330                                 throw new ArgumentNullException ("projectFullFileName");
331                         
332                         Project project;
333                         projects.TryGetValue (projectFullFileName, out project);
334
335                         return project;
336                 }
337
338                 internal void RemoveLoadedProject (Project p)
339                 {
340                         if (!String.IsNullOrEmpty (p.FullFileName)) {
341                                 ClearBuiltTargetsForProject (p);
342                                 projects.Remove (p.FullFileName);
343                         }
344                 }
345
346                 internal void AddLoadedProject (Project p)
347                 {
348                         if (p.FullFileName != String.Empty)
349                                 projects.Add (p.FullFileName, p);
350                 }
351         
352                 public void UnloadProject (Project project)
353                 {
354                         if (project == null)
355                                 throw new ArgumentNullException ("project");
356
357                         if (project.ParentEngine != this)
358                                 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
359                         
360                         project.CheckUnloaded ();
361                         
362                         RemoveLoadedProject (project);
363                         
364                         project.Unload ();
365                 }
366
367                 public void UnloadAllProjects ()
368                 {
369                         IList<Project> values = new List<Project> (projects.Values);
370                         foreach (Project p in values)
371                                 UnloadProject (p);
372                 }
373
374                 [MonoTODO]
375                 public void RegisterLogger (ILogger logger)
376                 {
377                         if (logger == null)
378                                 throw new ArgumentNullException ("logger");
379                         
380                         logger.Initialize (eventSource);
381                         loggers.Add (logger);
382                 }
383                 
384                 [MonoTODO]
385                 public void UnregisterAllLoggers ()
386                 {
387                         // FIXME: check if build succeeded
388                         // FIXME: it shouldn't be here
389                         if (buildStarted)
390                                 LogBuildFinished (true);
391                         foreach (ILogger i in loggers) {
392                                 i.Shutdown ();
393                         }
394                         loggers.Clear ();
395                 }
396
397                 void StartEngineBuild ()
398                 {
399                         if (!buildStarted) {
400                                 LogBuildStarted ();
401                                 buildStarted = true;
402                         }
403                 }
404
405                 void EndEngineBuild (bool succeeded)
406                 {
407                         if (buildStarted && currentlyBuildingProjectsStack.Count == 0) {
408                                 LogBuildFinished (succeeded);
409                                 buildStarted = false;
410                         }
411                 }
412
413                 internal void StartProjectBuild (Project project, string [] target_names)
414                 {
415                         StartEngineBuild ();
416
417                         if (currentlyBuildingProjectsStack.Count == 0 ||
418                                 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
419                                         LogProjectStarted (project, target_names);
420
421                         currentlyBuildingProjectsStack.Push (project);
422                 }
423
424                 internal void EndProjectBuild (Project project, bool succeeded)
425                 {
426                         if (!buildStarted)
427                                 throw new Exception ("build isnt started currently");
428
429                         Project top_project = currentlyBuildingProjectsStack.Pop ();
430
431                         if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
432                                 throw new Exception (String.Format (
433                                                         "INTERNAL ERROR: Project finishing is not the same as the one on top " +
434                                                         "of the stack. Project: {0} Top of stack: {1}",
435                                                         project.FullFileName, top_project.FullFileName));
436
437                         if (currentlyBuildingProjectsStack.Count == 0 ||
438                                 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
439                                 LogProjectFinished (top_project, succeeded);
440
441                         EndEngineBuild (succeeded);
442                 }
443
444                 internal void ClearBuiltTargetsForProject (Project project)
445                 {
446                         string project_key = project.GetKeyForTarget (String.Empty, false);
447                         var to_remove_keys = BuiltTargetsOutputByName.Keys.Where (key => key.StartsWith (project_key)).ToList ();
448                         foreach (string to_remove_key in to_remove_keys)
449                                 BuiltTargetsOutputByName.Remove (to_remove_key);
450                 }
451
452                 void LogProjectStarted (Project project, string [] target_names)
453                 {
454                         string targets;
455                         if (target_names == null || target_names.Length == 0)
456                                 targets = String.Empty;
457                         else
458                                 targets = String.Join (";", target_names);
459
460                         ProjectStartedEventArgs psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName, targets,
461                                         project.EvaluatedPropertiesAsDictionaryEntries, project.EvaluatedItemsByNameAsDictionaryEntries);
462
463                         eventSource.FireProjectStarted (this, psea);
464                 }
465
466                 void LogProjectFinished (Project project, bool succeeded)
467                 {
468                         ProjectFinishedEventArgs pfea;
469                         pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
470                         eventSource.FireProjectFinished (this, pfea);
471                 }
472
473                 void LogBuildStarted ()
474                 {
475                         BuildStartedEventArgs bsea;
476                         bsea = new BuildStartedEventArgs ("Build started.", null);
477                         eventSource.FireBuildStarted (this, bsea);
478                 }
479                 
480                 void LogBuildFinished (bool succeeded)
481                 {
482                         BuildFinishedEventArgs bfea;
483                         bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
484                         eventSource.FireBuildFinished (this, bfea);
485                 }
486
487                 internal TaskDatabase GetDefaultTasks (string toolsVersion)
488                 {
489                         TaskDatabase db;
490                         if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
491                                 return db;
492
493                         var toolset = Toolsets [toolsVersion];
494                         if (toolset == null)
495                                 throw new UnknownToolsVersionException (toolsVersion);
496
497                         string toolsPath = toolset.ToolsPath;
498                         string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
499                         this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
500
501                         // set a empty taskdb here, because the project loading the tasks
502                         // file will try to get the default task db
503                         defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
504
505                         db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
506
507                         return db;
508                 }
509                 
510                 TaskDatabase RegisterDefaultTasks (string tasksFile)
511                 {
512                         Project defaultTasksProject = CreateNewProject ();
513                         TaskDatabase db;
514                         
515                         if (File.Exists (tasksFile)) {
516                                 defaultTasksProject.Load (tasksFile);
517                                 db = defaultTasksProject.TaskDatabase;
518                         } else {
519                                 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
520                                 db = new TaskDatabase ();
521                         }
522
523                         return db;
524                 }
525
526                 public string BinPath {
527                         get { return binPath; }
528                         set { binPath = value; }
529                 }
530
531                 public bool BuildEnabled {
532                         get { return buildEnabled; }
533                         set { buildEnabled = value; }
534                 }
535
536                 public static Version Version {
537                         get { return version; }
538                 }
539
540                 public static Engine GlobalEngine {
541                         get {
542                                 if (globalEngine == null)
543                                         globalEngine = new Engine ();
544                                 return globalEngine;
545                         }
546                 }
547
548                 public BuildPropertyGroup GlobalProperties {
549                         get { return global_properties; }
550                         set { global_properties = value; }
551                 }
552                 
553                 public ToolsetCollection Toolsets {
554                         get; private set;
555                 }
556
557                 public string DefaultToolsVersion {
558                         get {
559                                 // This is used as the fall back version if the
560                                 // project can't find a version to use
561                                 // Hard-coded to 2.0, so it allows even vs2005 projects
562                                 // to build correctly, as they won't have a ToolsVersion
563                                 // set!
564                                 return String.IsNullOrEmpty (defaultToolsVersion)
565                                                 ? "2.0"
566                                                 : defaultToolsVersion;
567                         }
568                         set {
569                                 if (Toolsets [value] == null)
570                                         throw new UnknownToolsVersionException (value);
571                                 defaultToolsVersion = value;
572                         }
573                 }
574                 
575                 public bool IsBuilding {
576                         get { return buildStarted; }
577                 }
578                 
579                 public bool OnlyLogCriticalEvents {
580                         get { return eventSource.OnlyLogCriticalEvents; }
581                         set { eventSource.OnlyLogCriticalEvents = value; }
582                 }
583                 
584                 internal EventSource EventSource {
585                         get { return eventSource; }
586                 }
587                 
588                 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
589                         get { return builtTargetsOutputByName; }
590                 }
591         }
592 }