Add support for ToolsVersion and correctly build msbuild+xbuild assemblies
[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 Microsoft.Build.Framework;
35 using Microsoft.Build.Utilities;
36 using Mono.XBuild.Utilities;
37
38 namespace Microsoft.Build.BuildEngine {
39         public class Engine {
40                 
41                 string                  binPath;
42                 bool                    buildEnabled;
43                 Dictionary<string, TaskDatabase> defaultTasksTableByToolsVersion;
44                 const string            defaultTasksProjectName = "Microsoft.Common.tasks";
45                 EventSource             eventSource;
46                 bool                    buildStarted;
47                 ToolsetDefinitionLocations toolsetLocations;
48                 BuildPropertyGroup      global_properties;
49                 //IDictionary           importedProjects;
50                 List <ILogger>          loggers;
51                 //bool                  onlyLogCriticalEvents;
52                 Dictionary <string, Project>    projects;
53                 string defaultToolsVersion;
54
55                 // the key here represents the project+target+global_properties set
56                 Dictionary <string, ITaskItem[]> builtTargetsOutputByName;
57                 Stack<Project> currentlyBuildingProjectsStack;
58
59                 static Engine           globalEngine;
60                 static Version          version;
61
62                 static Engine ()
63                 {
64                         version = new Version ("0.1");
65                 }
66                 
67                 public Engine ()
68                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
69                 {
70                 }
71
72                 public Engine (ToolsetDefinitionLocations locations)
73                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
74                 {
75                         toolsetLocations = locations;
76                 }
77                 
78                 public Engine (BuildPropertyGroup globalProperties)
79                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
80                 {
81                         this.global_properties = globalProperties;
82                 }
83
84                 public Engine (BuildPropertyGroup globalProperties, ToolsetDefinitionLocations locations)
85                         : this (ToolLocationHelper.GetPathToDotNetFramework (TargetDotNetFrameworkVersion.Version20))
86                 {
87                         this.global_properties = globalProperties;
88                         toolsetLocations = locations;
89                 }
90
91                 // engine should be invoked with path where binary files are
92                 // to find microsoft.build.tasks
93                 public Engine (string binPath)
94                 {
95                         this.binPath = binPath;
96                         this.buildEnabled = true;
97                         this.projects = new Dictionary <string, Project> ();
98                         this.eventSource = new EventSource ();
99                         this.loggers = new List <ILogger> ();
100                         this.buildStarted = false;
101                         this.global_properties = new BuildPropertyGroup ();
102                         this.builtTargetsOutputByName = new Dictionary<string, ITaskItem[]> ();
103                         this.currentlyBuildingProjectsStack = new Stack<Project> ();
104                         this.Toolsets = new ToolsetCollection ();
105                         LoadDefaultToolsets ();
106                         defaultTasksTableByToolsVersion = new Dictionary<string, TaskDatabase> ();
107                         GetDefaultTasks (DefaultToolsVersion);
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                         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 (defaultToolsVersion != null)
169                                 // it has been explicitly set, xbuild does this..
170                                 project.ToolsVersion = defaultToolsVersion;
171                         return project.Build (targetNames, targetOutputs, buildFlags);
172                 }
173
174                 [MonoTODO]
175                 public bool BuildProjectFile (string projectFile)
176                 {
177                         return BuildProjectFile (projectFile, new string [0]);
178                 }
179                 
180                 [MonoTODO]
181                 public bool BuildProjectFile (string projectFile,
182                                               string targetName)
183                 {
184                         return BuildProjectFile (projectFile,
185                                                  targetName == null ? new string [0] : new string [] {targetName});
186                 }
187                 
188                 [MonoTODO]
189                 public bool BuildProjectFile (string projectFile,
190                                               string[] targetNames)
191                 {
192                         return BuildProjectFile (projectFile, targetNames, null);
193                 }
194                 
195                 [MonoTODO]
196                 public bool BuildProjectFile (string projectFile,
197                                               string[] targetNames,
198                                               BuildPropertyGroup globalProperties)
199                 {
200                         return BuildProjectFile (projectFile, targetNames, globalProperties, null, BuildSettings.None);
201                 }
202                 
203                 [MonoTODO]
204                 public bool BuildProjectFile (string projectFile,
205                                               string[] targetNames,
206                                               BuildPropertyGroup globalProperties,
207                                               IDictionary targetOutputs)
208                 {
209                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, BuildSettings.None);
210                 }
211                 
212                 public bool BuildProjectFile (string projectFile,
213                                               string[] targetNames,
214                                               BuildPropertyGroup globalProperties,
215                                               IDictionary targetOutputs,
216                                               BuildSettings buildFlags)
217                 {
218                         return BuildProjectFile (projectFile, targetNames, globalProperties, targetOutputs, buildFlags, null);
219                 }
220                         
221                 //FIXME: add a test for null @toolsVersion
222                 public bool BuildProjectFile (string projectFile,
223                                               string[] targetNames,
224                                               BuildPropertyGroup globalProperties,
225                                               IDictionary targetOutputs,
226                                               BuildSettings buildFlags, string toolsVersion)
227                 {
228                         Project project;
229
230                         if (projects.ContainsKey (projectFile)) {
231                                 project = (Project) projects [projectFile];
232                         } else {
233                                 project = CreateNewProject ();
234                                 project.Load (projectFile);
235                         }
236
237                         BuildPropertyGroup engine_old_grp = null;
238                         BuildPropertyGroup project_old_grp = null;
239                         if (globalProperties != null) {
240                                 engine_old_grp = GlobalProperties.Clone (true);
241                                 project_old_grp = project.GlobalProperties.Clone (true);
242
243                                 // Override project's global properties with the
244                                 // ones explicitlcur_y specified here
245                                 foreach (BuildProperty bp in globalProperties)
246                                         project.GlobalProperties.AddProperty (bp);
247                                 project.NeedToReevaluate ();
248                         }
249
250                         try {
251                                 if (String.IsNullOrEmpty (toolsVersion) && defaultToolsVersion != null)
252                                         // it has been explicitly set, xbuild does this..
253                                         //FIXME: should this be cleared after building?
254                                         project.ToolsVersion = defaultToolsVersion;
255                                 else
256                                         project.ToolsVersion = toolsVersion;
257
258                                 return project.Build (targetNames, targetOutputs, buildFlags);
259                         } finally {
260                                 if (globalProperties != null) {
261                                         GlobalProperties = engine_old_grp;
262                                         project.GlobalProperties = project_old_grp;
263                                 }
264                         }
265                 }
266
267                 void CheckBinPath ()
268                 {
269                         if (BinPath == null) {
270                                 throw new InvalidOperationException ("Before a project can be instantiated, " +
271                                         "Engine.BinPath must be set to the location on disk where MSBuild " + 
272                                         "is installed. This is used to evaluate $(MSBuildBinPath).");
273                         }
274                 }
275
276                 public Project CreateNewProject ()
277                 {
278                         return new Project (this);
279                 }
280
281                 public Project GetLoadedProject (string projectFullFileName)
282                 {
283                         if (projectFullFileName == null)
284                                 throw new ArgumentNullException ("projectFullFileName");
285                         
286                         // FIXME: test it
287                         return projects [projectFullFileName];
288                 }
289
290                 internal void RemoveLoadedProject (Project p)
291                 {
292                         if (p.FullFileName != String.Empty)
293                                 projects.Remove (p.FullFileName);
294                 }
295
296                 internal void AddLoadedProject (Project p)
297                 {
298                         if (p.FullFileName != String.Empty)
299                                 projects.Add (p.FullFileName, p);
300                 }
301         
302                 public void UnloadProject (Project project)
303                 {
304                         if (project == null)
305                                 throw new ArgumentNullException ("project");
306
307                         if (project.ParentEngine != this)
308                                 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
309                         
310                         project.CheckUnloaded ();
311                         
312                         if (project.FullFileName != String.Empty)
313                                 projects.Remove (project.FullFileName);
314                         
315                         project.Unload ();
316                 }
317
318                 public void UnloadAllProjects ()
319                 {
320                         IList<Project> values = new List<Project> (projects.Values);
321                         foreach (Project p in values)
322                                 UnloadProject (p);
323                 }
324
325                 [MonoTODO]
326                 public void RegisterLogger (ILogger logger)
327                 {
328                         if (logger == null)
329                                 throw new ArgumentNullException ("logger");
330                         
331                         logger.Initialize (eventSource);
332                         loggers.Add (logger);
333                 }
334                 
335                 [MonoTODO]
336                 public void UnregisterAllLoggers ()
337                 {
338                         // FIXME: check if build succeeded
339                         // FIXME: it shouldn't be here
340                         if (buildStarted)
341                                 LogBuildFinished (true);
342                         foreach (ILogger i in loggers) {
343                                 i.Shutdown ();
344                         }
345                         loggers.Clear ();
346                 }
347
348                 internal void StartProjectBuild (Project project, string [] target_names)
349                 {
350                         if (!buildStarted) {
351                                 LogBuildStarted ();
352                                 buildStarted = true;
353                         }
354
355                         if (currentlyBuildingProjectsStack.Count == 0 ||
356                                 String.Compare (currentlyBuildingProjectsStack.Peek ().FullFileName, project.FullFileName) != 0)
357                                         LogProjectStarted (project, target_names);
358
359                         currentlyBuildingProjectsStack.Push (project);
360                 }
361
362                 internal void EndProjectBuild (Project project, bool succeeded)
363                 {
364                         if (!buildStarted)
365                                 throw new Exception ("build isnt started currently");
366
367                         Project top_project = currentlyBuildingProjectsStack.Pop ();
368
369                         if (String.Compare (project.FullFileName, top_project.FullFileName) != 0)
370                                 throw new Exception (String.Format (
371                                                         "INTERNAL ERROR: Project finishing is not the same as the one on top " +
372                                                         "of the stack. Project: {0} Top of stack: {1}",
373                                                         project.FullFileName, top_project.FullFileName));
374
375                         if (currentlyBuildingProjectsStack.Count == 0 ||
376                                 String.Compare (top_project.FullFileName, currentlyBuildingProjectsStack.Peek ().FullFileName) != 0)
377                                 LogProjectFinished (top_project, succeeded);
378
379                         if (currentlyBuildingProjectsStack.Count == 0) {
380                                 LogBuildFinished (succeeded);
381                                 buildStarted = false;
382                         }
383                 }
384
385                 void LogProjectStarted (Project project, string [] target_names)
386                 {
387                         ProjectStartedEventArgs psea;
388                         if (target_names == null || target_names.Length == 0)
389                                 psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName,
390                                                 String.Empty, null, null);
391                         else
392                                 psea = new ProjectStartedEventArgs ("Project started.", null, project.FullFileName,
393                                                 String.Join (";", target_names), null, null);
394                         eventSource.FireProjectStarted (this, psea);
395                 }
396
397                 void LogProjectFinished (Project project, bool succeeded)
398                 {
399                         ProjectFinishedEventArgs pfea;
400                         pfea = new ProjectFinishedEventArgs ("Project started.", null, project.FullFileName, succeeded);
401                         eventSource.FireProjectFinished (this, pfea);
402                 }
403
404                 void LogBuildStarted ()
405                 {
406                         BuildStartedEventArgs bsea;
407                         bsea = new BuildStartedEventArgs ("Build started.", null);
408                         eventSource.FireBuildStarted (this, bsea);
409                 }
410                 
411                 void LogBuildFinished (bool succeeded)
412                 {
413                         BuildFinishedEventArgs bfea;
414                         bfea = new BuildFinishedEventArgs ("Build finished.", null, succeeded);
415                         eventSource.FireBuildFinished (this, bfea);
416                 }
417
418                 internal TaskDatabase GetDefaultTasks (string toolsVersion)
419                 {
420                         TaskDatabase db;
421                         if (defaultTasksTableByToolsVersion.TryGetValue (toolsVersion, out db))
422                                 return db;
423
424                         var toolset = Toolsets [toolsVersion];
425                         if (toolset == null)
426                                 throw new Exception ("Unknown toolsversion: " + toolsVersion);
427
428                         string toolsPath = toolset.ToolsPath;
429                         string tasksFile = Path.Combine (toolsPath, defaultTasksProjectName);
430                         this.LogMessage (MessageImportance.Low, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion, tasksFile);
431
432                         // set a empty taskdb here, because the project loading the tasks
433                         // file will try to get the default task db
434                         defaultTasksTableByToolsVersion [toolsVersion] = new TaskDatabase ();
435
436                         db = defaultTasksTableByToolsVersion [toolsVersion] = RegisterDefaultTasks (tasksFile);
437
438                         return db;
439                 }
440                 
441                 TaskDatabase RegisterDefaultTasks (string tasksFile)
442                 {
443                         Project defaultTasksProject = CreateNewProject ();
444                         TaskDatabase db;
445                         
446                         if (File.Exists (tasksFile)) {
447                                 defaultTasksProject.Load (tasksFile);
448                                 db = defaultTasksProject.TaskDatabase;
449                         } else {
450                                 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile);
451                                 db = new TaskDatabase ();
452                         }
453
454                         return db;
455                 }
456
457                 public string BinPath {
458                         get { return binPath; }
459                         set { binPath = value; }
460                 }
461
462                 public bool BuildEnabled {
463                         get { return buildEnabled; }
464                         set { buildEnabled = value; }
465                 }
466
467                 public static Version Version {
468                         get { return version; }
469                 }
470
471                 public static Engine GlobalEngine {
472                         get {
473                                 if (globalEngine == null)
474                                         globalEngine = new Engine ();
475                                 return globalEngine;
476                         }
477                 }
478
479                 public BuildPropertyGroup GlobalProperties {
480                         get { return global_properties; }
481                         set { global_properties = value; }
482                 }
483                 
484                 public ToolsetCollection Toolsets {
485                         get; private set;
486                 }
487
488                 public string DefaultToolsVersion {
489                         get {
490                                 if (String.IsNullOrEmpty (defaultToolsVersion))
491 #if NET_4_0
492                                         return "4.0";
493 #elif NET_3_5
494                                         return "3.5";
495 #else
496                                         return "2.0";
497 #endif
498                                 
499                                 return defaultToolsVersion;
500                         }
501                         set { defaultToolsVersion = value; }
502                 }
503                 
504                 public bool IsBuilding {
505                         get { return buildStarted; }
506                 }
507                 
508                 public bool OnlyLogCriticalEvents {
509                         get { return eventSource.OnlyLogCriticalEvents; }
510                         set { eventSource.OnlyLogCriticalEvents = value; }
511                 }
512                 
513                 internal EventSource EventSource {
514                         get { return eventSource; }
515                 }
516                 
517                 internal Dictionary<string, ITaskItem[]> BuiltTargetsOutputByName {
518                         get { return builtTargetsOutputByName; }
519                 }
520         }
521 }
522
523 #endif