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