2 // Engine.cs: Main engine of XBuild.
5 // Marek Sieradzki (marek.sieradzki@gmail.com)
7 // (C) 2005 Marek Sieradzki
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
31 using System
.Collections
;
32 using System
.Collections
.Generic
;
34 using Microsoft
.Build
.Framework
;
35 using Microsoft
.Build
.Utilities
;
36 using Mono
.XBuild
.Utilities
;
38 namespace Microsoft
.Build
.BuildEngine
{
43 Dictionary
<string, TaskDatabase
> defaultTasksTableByToolsVersion
;
44 const string defaultTasksProjectName
= "Microsoft.Common.tasks";
45 EventSource eventSource
;
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
;
55 // the key here represents the project+target+global_properties set
56 Dictionary
<string, ITaskItem
[]> builtTargetsOutputByName
;
57 Stack
<Project
> currentlyBuildingProjectsStack
;
59 static Engine globalEngine
;
60 static Version version
;
64 version
= new Version ("0.1");
68 : this (ToolLocationHelper
.GetPathToDotNetFramework (TargetDotNetFrameworkVersion
.Version20
))
72 public Engine (ToolsetDefinitionLocations locations
)
73 : this (ToolLocationHelper
.GetPathToDotNetFramework (TargetDotNetFrameworkVersion
.Version20
))
75 toolsetLocations
= locations
;
78 public Engine (BuildPropertyGroup globalProperties
)
79 : this (ToolLocationHelper
.GetPathToDotNetFramework (TargetDotNetFrameworkVersion
.Version20
))
81 this.global_properties
= globalProperties
;
84 public Engine (BuildPropertyGroup globalProperties
, ToolsetDefinitionLocations locations
)
85 : this (ToolLocationHelper
.GetPathToDotNetFramework (TargetDotNetFrameworkVersion
.Version20
))
87 this.global_properties
= globalProperties
;
88 toolsetLocations
= locations
;
91 // engine should be invoked with path where binary files are
92 // to find microsoft.build.tasks
93 public Engine (string binPath
)
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
);
110 //FIXME: should be loaded from config file
111 void LoadDefaultToolsets ()
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
)));
120 Toolsets
.Add (new Toolset ("4.0",
121 ToolLocationHelper
.GetPathToDotNetFramework (TargetDotNetFrameworkVersion
.Version40
)));
126 public bool BuildProject (Project project
)
129 throw new ArgumentException ("project");
130 return project
.Build ();
134 public bool BuildProject (Project project
, string targetName
)
137 throw new ArgumentException ("project");
138 if (targetName
== null)
141 return BuildProject (project
, new string[] { targetName}
, null, BuildSettings
.None
);
145 public bool BuildProject (Project project
, string[] targetNames
)
147 return BuildProject (project
, targetNames
, null, BuildSettings
.None
);
151 public bool BuildProject (Project project
,
152 string[] targetNames
,
153 IDictionary targetOutputs
)
155 return BuildProject (project
, targetNames
, targetOutputs
, BuildSettings
.None
);
158 public bool BuildProject (Project project
,
159 string[] targetNames
,
160 IDictionary targetOutputs
,
161 BuildSettings buildFlags
)
164 throw new ArgumentException ("project");
165 if (targetNames
== null)
168 if (defaultToolsVersion
!= null)
169 // it has been explicitly set, xbuild does this..
170 project
.ToolsVersion
= defaultToolsVersion
;
171 return project
.Build (targetNames
, targetOutputs
, buildFlags
);
175 public bool BuildProjectFile (string projectFile
)
177 return BuildProjectFile (projectFile
, new string [0]);
181 public bool BuildProjectFile (string projectFile
,
184 return BuildProjectFile (projectFile
,
185 targetName
== null ? new string [0] : new string [] {targetName}
);
189 public bool BuildProjectFile (string projectFile
,
190 string[] targetNames
)
192 return BuildProjectFile (projectFile
, targetNames
, null);
196 public bool BuildProjectFile (string projectFile
,
197 string[] targetNames
,
198 BuildPropertyGroup globalProperties
)
200 return BuildProjectFile (projectFile
, targetNames
, globalProperties
, null, BuildSettings
.None
);
204 public bool BuildProjectFile (string projectFile
,
205 string[] targetNames
,
206 BuildPropertyGroup globalProperties
,
207 IDictionary targetOutputs
)
209 return BuildProjectFile (projectFile
, targetNames
, globalProperties
, targetOutputs
, BuildSettings
.None
);
212 public bool BuildProjectFile (string projectFile
,
213 string[] targetNames
,
214 BuildPropertyGroup globalProperties
,
215 IDictionary targetOutputs
,
216 BuildSettings buildFlags
)
218 return BuildProjectFile (projectFile
, targetNames
, globalProperties
, targetOutputs
, buildFlags
, null);
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
)
230 if (projects
.ContainsKey (projectFile
)) {
231 project
= (Project
) projects
[projectFile
];
233 project
= CreateNewProject ();
234 project
.Load (projectFile
);
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);
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 ();
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
;
256 project
.ToolsVersion
= toolsVersion
;
258 return project
.Build (targetNames
, targetOutputs
, buildFlags
);
260 if (globalProperties
!= null) {
261 GlobalProperties
= engine_old_grp
;
262 project
.GlobalProperties
= project_old_grp
;
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).");
276 public Project
CreateNewProject ()
278 return new Project (this);
281 public Project
GetLoadedProject (string projectFullFileName
)
283 if (projectFullFileName
== null)
284 throw new ArgumentNullException ("projectFullFileName");
287 return projects
[projectFullFileName
];
290 internal void RemoveLoadedProject (Project p
)
292 if (p
.FullFileName
!= String
.Empty
)
293 projects
.Remove (p
.FullFileName
);
296 internal void AddLoadedProject (Project p
)
298 if (p
.FullFileName
!= String
.Empty
)
299 projects
.Add (p
.FullFileName
, p
);
302 public void UnloadProject (Project project
)
305 throw new ArgumentNullException ("project");
307 if (project
.ParentEngine
!= this)
308 throw new InvalidOperationException ("The \"Project\" object specified does not belong to the correct \"Engine\" object.");
310 project
.CheckUnloaded ();
312 if (project
.FullFileName
!= String
.Empty
)
313 projects
.Remove (project
.FullFileName
);
318 public void UnloadAllProjects ()
320 IList
<Project
> values
= new List
<Project
> (projects
.Values
);
321 foreach (Project p
in values
)
326 public void RegisterLogger (ILogger logger
)
329 throw new ArgumentNullException ("logger");
331 logger
.Initialize (eventSource
);
332 loggers
.Add (logger
);
336 public void UnregisterAllLoggers ()
338 // FIXME: check if build succeeded
339 // FIXME: it shouldn't be here
341 LogBuildFinished (true);
342 foreach (ILogger i
in loggers
) {
348 internal void StartProjectBuild (Project project
, string [] target_names
)
355 if (currentlyBuildingProjectsStack
.Count
== 0 ||
356 String
.Compare (currentlyBuildingProjectsStack
.Peek ().FullFileName
, project
.FullFileName
) != 0)
357 LogProjectStarted (project
, target_names
);
359 currentlyBuildingProjectsStack
.Push (project
);
362 internal void EndProjectBuild (Project project
, bool succeeded
)
365 throw new Exception ("build isnt started currently");
367 Project top_project
= currentlyBuildingProjectsStack
.Pop ();
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
));
375 if (currentlyBuildingProjectsStack
.Count
== 0 ||
376 String
.Compare (top_project
.FullFileName
, currentlyBuildingProjectsStack
.Peek ().FullFileName
) != 0)
377 LogProjectFinished (top_project
, succeeded
);
379 if (currentlyBuildingProjectsStack
.Count
== 0) {
380 LogBuildFinished (succeeded
);
381 buildStarted
= false;
385 void LogProjectStarted (Project project
, string [] target_names
)
388 if (target_names
== null || target_names
.Length
== 0)
389 targets
= String
.Empty
;
391 targets
= String
.Join (";", target_names
);
393 ProjectStartedEventArgs psea
= new ProjectStartedEventArgs ("Project started.", null, project
.FullFileName
, targets
,
394 project
.EvaluatedPropertiesAsDictionaryEntries
, project
.EvaluatedItemsByNameAsDictionaryEntries
);
396 eventSource
.FireProjectStarted (this, psea
);
399 void LogProjectFinished (Project project
, bool succeeded
)
401 ProjectFinishedEventArgs pfea
;
402 pfea
= new ProjectFinishedEventArgs ("Project started.", null, project
.FullFileName
, succeeded
);
403 eventSource
.FireProjectFinished (this, pfea
);
406 void LogBuildStarted ()
408 BuildStartedEventArgs bsea
;
409 bsea
= new BuildStartedEventArgs ("Build started.", null);
410 eventSource
.FireBuildStarted (this, bsea
);
413 void LogBuildFinished (bool succeeded
)
415 BuildFinishedEventArgs bfea
;
416 bfea
= new BuildFinishedEventArgs ("Build finished.", null, succeeded
);
417 eventSource
.FireBuildFinished (this, bfea
);
420 internal TaskDatabase
GetDefaultTasks (string toolsVersion
)
423 if (defaultTasksTableByToolsVersion
.TryGetValue (toolsVersion
, out db
))
426 var toolset
= Toolsets
[toolsVersion
];
428 throw new Exception ("Unknown toolsversion: " + toolsVersion
);
430 string toolsPath
= toolset
.ToolsPath
;
431 string tasksFile
= Path
.Combine (toolsPath
, defaultTasksProjectName
);
432 this.LogMessage (MessageImportance
.Low
, "Loading default tasks for ToolsVersion: {0} from {1}", toolsVersion
, tasksFile
);
434 // set a empty taskdb here, because the project loading the tasks
435 // file will try to get the default task db
436 defaultTasksTableByToolsVersion
[toolsVersion
] = new TaskDatabase ();
438 db
= defaultTasksTableByToolsVersion
[toolsVersion
] = RegisterDefaultTasks (tasksFile
);
443 TaskDatabase
RegisterDefaultTasks (string tasksFile
)
445 Project defaultTasksProject
= CreateNewProject ();
448 if (File
.Exists (tasksFile
)) {
449 defaultTasksProject
.Load (tasksFile
);
450 db
= defaultTasksProject
.TaskDatabase
;
452 this.LogWarning ("Default tasks file {0} not found, ignoring.", tasksFile
);
453 db
= new TaskDatabase ();
459 public string BinPath
{
460 get { return binPath; }
461 set { binPath = value; }
464 public bool BuildEnabled
{
465 get { return buildEnabled; }
466 set { buildEnabled = value; }
469 public static Version Version
{
470 get { return version; }
473 public static Engine GlobalEngine
{
475 if (globalEngine
== null)
476 globalEngine
= new Engine ();
481 public BuildPropertyGroup GlobalProperties
{
482 get { return global_properties; }
483 set { global_properties = value; }
486 public ToolsetCollection Toolsets
{
490 public string DefaultToolsVersion
{
492 if (String
.IsNullOrEmpty (defaultToolsVersion
))
501 return defaultToolsVersion
;
503 set { defaultToolsVersion = value; }
506 public bool IsBuilding
{
507 get { return buildStarted; }
510 public bool OnlyLogCriticalEvents
{
511 get { return eventSource.OnlyLogCriticalEvents; }
512 set { eventSource.OnlyLogCriticalEvents = value; }
515 internal EventSource EventSource
{
516 get { return eventSource; }
519 internal Dictionary
<string, ITaskItem
[]> BuiltTargetsOutputByName
{
520 get { return builtTargetsOutputByName; }