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