handle Target/@DependsOnTargets. To make it possible, some code reorganization.
[mono.git] / mcs / class / Microsoft.Build / Microsoft.Build.Internal / BuildEngine4.cs
1 //
2 // BuildEngine4.cs
3 //
4 // Author:
5 //   Atsushi Enomoto (atsushi@xamarin.com)
6 //
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
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 Microsoft.Build.BuildEngine;
32 using Microsoft.Build.Execution;
33 using Microsoft.Build.Framework;
34 using Microsoft.Build.Evaluation;
35 using System.Linq;
36 using System.IO;
37
38 namespace Microsoft.Build.Internal
39 {
40         class BuildEngine4
41 #if NET_4_5
42                 : IBuildEngine4
43 #else
44                 : IBuildEngine3
45 #endif
46         {
47                 public BuildEngine4 (BuildSubmission submission)
48                 {
49                         this.submission = submission;
50                         event_source = new EventSource ();
51                         if (submission.BuildManager.OngoingBuildParameters.Loggers != null)
52                                 foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers)
53                                         l.Initialize (event_source);
54                 }
55
56                 BuildSubmission submission;
57                 ProjectInstance project;
58                 ProjectTaskInstance current_task;
59                 EventSource event_source;
60                 
61                 public ProjectCollection Projects {
62                         get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
63                 }
64
65                 // FIXME:
66                 // While we are not faced to implement those features, there are some modern task execution requirements.
67                 //
68                 // This will have to be available for "out of process" nodes (see NodeAffinity).
69                 // NodeAffinity is set per project file at BuildManager.HostServices.
70                 // When NodeAffinity is set to OutOfProc, it should probably launch different build host
71                 // that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.)
72                 //
73                 // Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute
74                 // (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
75                 // in separate AppDomain.
76                 //
77                 public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
78                 {
79                         var parameters = submission.BuildManager.OngoingBuildParameters;
80                         var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (parameters.GetToolset (toolsVersion)), submission.BuildRequest.ProjectInstance.TaskDatabase);
81                         BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
82                 }
83
84                 class InternalBuildArguments
85                 {
86                         public Func<bool> CheckCancel;
87                         public BuildResult Result;
88                         public ProjectInstance Project;
89                         public IEnumerable<string> TargetNames;
90                         public IDictionary<string,string> GlobalProperties;
91                         public IDictionary<string,string> TargetOutputs;
92                         public string ToolsVersion;
93                         public BuildTaskFactory BuildTaskFactory;
94                 }
95                 
96                 void BuildProject (InternalBuildArguments args)
97                 {
98                         var request = submission.BuildRequest;
99                         var parameters = submission.BuildManager.OngoingBuildParameters;
100                         this.project = args.Project;
101                         
102                         event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null));
103                                                 
104                         // null targets -> success. empty targets -> success(!)
105                         if (request.TargetNames == null)
106                                 args.Result.OverallResult = BuildResultCode.Success;
107                         else {
108                                 foreach (var targetName in request.TargetNames.Where (t => t != null))
109                                         args.Result.AddResultsForTarget (targetName, BuildTarget (targetName, args));
110                 
111                                 // FIXME: check .NET behavior, whether cancellation always results in failure.
112                                 args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
113                         }                       
114                         event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success));
115                 }
116                 
117                 TargetResult BuildTarget (string targetName, InternalBuildArguments args)
118                 {
119                         var targetResult = new TargetResult ();
120
121                         var request = submission.BuildRequest;
122                         var parameters = submission.BuildManager.OngoingBuildParameters;
123                         ProjectTargetInstance target;
124                         
125                         // FIXME: check skip condition
126                         if (false)
127                                 targetResult.Skip ();
128                         // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
129                         else if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
130                                 targetResult.Failure (new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath)));
131                         else {
132                                 // process DependsOnTargets first.
133                                 foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Where (s => !string.IsNullOrEmpty (s))) {
134                                         var result = BuildTarget (dep, args);
135                                         if (result != null)
136                                         args.Result.AddResultsForTarget (dep, result);
137                                         if (result.ResultCode == TargetResultCode.Failure) {
138                                                 targetResult.Failure (null);
139                                                 return targetResult;
140                                         }
141                                 }
142                                 
143                                 event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath));
144                                 
145                                 // Here we check cancellation (only after TargetStarted event).
146                                 if (args.CheckCancel ()) {
147                                         targetResult.Failure (new OperationCanceledException ("Build has canceled"));
148                                         return targetResult;
149                                 }
150                                 
151                                 foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
152                                         if (!args.Project.EvaluateCondition (c.Condition))
153                                                 continue;
154                                         throw new NotImplementedException ();
155                                 }
156                                 foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
157                                         if (!args.Project.EvaluateCondition (c.Condition))
158                                                 continue;
159                                         throw new NotImplementedException ();
160                                 }
161                                 foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
162                                         if (!args.Project.EvaluateCondition (c.Condition))
163                                                 continue;
164                                         throw new NotImplementedException ();
165                                 }
166                                 foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
167                                         var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, ti.Name);
168                                         if (!args.Project.EvaluateCondition (ti.Condition))
169                                                 continue;
170                                         current_task = ti;
171                                         
172                                         var factoryIdentityParameters = new Dictionary<string,string> ();
173                                         #if NET_4_5
174                                         factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
175                                         factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
176                                         #endif
177                                         var task = args.BuildTaskFactory.CreateTask (ti.Name, factoryIdentityParameters, this);
178                                         task.HostObject = host;
179                                         task.BuildEngine = this;
180                                         // FIXME: this cannot be that simple, value has to be converted to the appropriate target type.
181                                         var props = task.GetType ().GetProperties ()
182                                                 .Where (p => p.CanWrite && p.GetCustomAttributes (typeof(RequiredAttribute), true).Any ());
183                                         var missings = props.Where (p => !ti.Parameters.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
184                                         if (missings.Any ())
185                                                 throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}",
186                                                         ti.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
187                                         foreach (var p in ti.Parameters) {
188                                                 var prop = task.GetType ().GetProperty (p.Key);
189                                                 if (prop == null)
190                                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
191                                                 if (!prop.CanWrite)
192                                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", ti.Name, p.Key));
193                                                 prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType), null);
194                                         }
195                                         if (!task.Execute ()) {
196                                                 targetResult.Failure (null);
197                                                 if (!ContinueOnError)
198                                                         break;
199                                         }
200                                         foreach (var to in ti.Outputs) {
201                                                 var toItem = to as ProjectTaskOutputItemInstance;
202                                                 var toProp = to as ProjectTaskOutputPropertyInstance;
203                                                 string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
204                                                 var pi = task.GetType ().GetProperty (taskParameter);
205                                                 if (pi == null)
206                                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", ti.Name, toItem.TaskParameter));
207                                                 if (!pi.CanRead)
208                                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.Name, toItem.TaskParameter));
209                                                 if (toItem != null)
210                                                         args.Project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
211                                                 else
212                                                         args.Project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
213                                         }
214                                 }
215                                 Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
216                                 var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
217                                 });
218                                 targetResult.Success (items);
219                                 event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath, true));
220                         }
221                         return targetResult;
222                 }
223                 
224                 object ConvertTo (string source, Type targetType)
225                 {
226                         if (targetType.IsSubclassOf (typeof (ITaskItem)))
227                                 return new TargetOutputTaskItem () { ItemSpec = source };
228                         if (targetType.IsArray)
229                                 return new ArrayList (source.Split (';').Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
230                                                 .ToArray (targetType.GetElementType ());
231                         else
232                                 return Convert.ChangeType (source, targetType);
233                 }
234                 
235                 string ConvertFrom (object source)
236                 {
237                         if (source == null)
238                                 return string.Empty;
239                         var type = source.GetType ();
240                         if (type.IsSubclassOf (typeof (ITaskItem)))
241                                 return ((ITaskItem) source).ItemSpec;
242                         if (type.IsArray)
243                                 return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
244                         else
245                                 return (string) Convert.ChangeType (source, typeof (string));
246                 }
247                 
248                 class TargetOutputTaskItem : ITaskItem2
249                 {
250                         #region ITaskItem2 implementation
251                         public string GetMetadataValueEscaped (string metadataName)
252                         {
253                                 return null;
254                         }
255                         public void SetMetadataValueLiteral (string metadataName, string metadataValue)
256                         {
257                                 throw new NotSupportedException ();
258                         }
259                         public IDictionary CloneCustomMetadataEscaped ()
260                         {
261                                 return new Hashtable ();
262                         }
263                         public string EvaluatedIncludeEscaped {
264                                 get { return ProjectCollection.Escape (ItemSpec); }
265                                 set { ItemSpec = ProjectCollection.Unescape (value); }
266                         }
267                         #endregion
268                         #region ITaskItem implementation
269                         public IDictionary CloneCustomMetadata ()
270                         {
271                                 return new Hashtable ();
272                         }
273                         public void CopyMetadataTo (ITaskItem destinationItem)
274                         {
275                                 // do nothing
276                         }
277                         public string GetMetadata (string metadataName)
278                         {
279                                 return null;
280                         }
281                         public void RemoveMetadata (string metadataName)
282                         {
283                                 // do nothing
284                         }
285                         public void SetMetadata (string metadataName, string metadataValue)
286                         {
287                                 throw new NotSupportedException ();
288                         }
289                         public string ItemSpec { get; set; }
290                         public int MetadataCount {
291                                 get { return 0; }
292                         }
293                         public ICollection MetadataNames {
294                                 get { return new ArrayList (); }
295                         }
296                         #endregion
297                 }
298                 
299 #if NET_4_5
300                 #region IBuildEngine4 implementation
301                 
302                 // task objects are not in use anyways though...
303                 
304                 class TaskObjectRegistration
305                 {
306                         public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
307                         {
308                                 Key = key;
309                                 Object = obj;
310                                 Lifetime = lifetime;
311                                 AllowEarlyCollection = allowEarlyCollection;
312                         }
313                         public object Key { get; private set; }
314                         public object Object { get; private set; }
315                         public RegisteredTaskObjectLifetime Lifetime { get; private set; }
316                         public bool AllowEarlyCollection { get; private set; }
317                 }
318                 
319                 List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> ();
320
321                 public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
322                 {
323                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
324                         return reg != null ? reg.Object : null;
325                 }
326
327                 public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
328                 {
329                         task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection));
330                 }
331
332                 public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
333                 {
334                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
335                         if (reg != null)
336                                 task_objects.Remove (reg);
337                         return reg.Object;
338                 }
339                 #endregion
340 #endif
341
342                 #region IBuildEngine3 implementation
343
344                 public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs)
345                 {
346                         throw new NotImplementedException ();
347                 }
348
349                 public void Reacquire ()
350                 {
351                         throw new NotImplementedException ();
352                 }
353
354                 public void Yield ()
355                 {
356                         throw new NotImplementedException ();
357                 }
358
359                 #endregion
360
361                 #region IBuildEngine2 implementation
362
363                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
364                 {
365                         var proj = GetProjectInstance (projectFileName, toolsVersion);
366                         var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
367                         foreach (DictionaryEntry p in globalProperties)
368                                 globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value;
369                         var result = new BuildResult ();
370                         var outputs = new Dictionary<string, string> ();
371                         BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
372                         foreach (var p in outputs)
373                                 targetOutputs [p.Key] = p.Value;
374                         return result.OverallResult == BuildResultCode.Success;
375                 }
376
377                 public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)
378                 {
379                         throw new NotImplementedException ();
380                 }
381
382                 public bool IsRunningMultipleNodes {
383                         get {
384                                 throw new NotImplementedException ();
385                         }
386                 }
387                 
388                 ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion)
389                 {
390                         string fullPath = Path.GetFullPath (projectFileName);
391                         if (submission.BuildRequest.ProjectFullPath == fullPath)
392                                 return submission.BuildRequest.ProjectInstance;
393                         // FIXME: could also be filtered by global properties
394                         // http://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.projectcollection.getloadedprojects.aspx
395                         var project = Projects.GetLoadedProjects (projectFileName).FirstOrDefault (p => p.ToolsVersion == toolsVersion);
396                         if (project == null)
397                                 throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName));
398                         return submission.BuildManager.GetProjectInstanceForBuild (project);
399                 }
400
401                 #endregion
402
403                 #region IBuildEngine implementation
404
405                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
406                 {
407                         return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion);
408                 }
409
410                 public void LogCustomEvent (CustomBuildEventArgs e)
411                 {
412                         event_source.FireCustomEventRaised (this, e);
413                 }
414
415                 public void LogErrorEvent (BuildErrorEventArgs e)
416                 {
417                         event_source.FireErrorRaised (this, e);
418                 }
419
420                 public void LogMessageEvent (BuildMessageEventArgs e)
421                 {
422                         event_source.FireMessageRaised (this, e);
423                 }
424
425                 public void LogWarningEvent (BuildWarningEventArgs e)
426                 {
427                         event_source.FireWarningRaised (this, e);
428                 }
429
430                 public int ColumnNumberOfTaskNode {
431                         get { return current_task.Location != null ? current_task.Location.Column : 0; }
432                 }
433
434                 public bool ContinueOnError {
435                         get {
436                                 switch (current_task.ContinueOnError) {
437                                 case "WarnAndContinue":
438                                 case "ErrorAndContinue":
439                                         return true;
440                                 case "ErrorAndStop":
441                                         return false;
442                                 }
443                                 return project.EvaluateCondition (current_task.ContinueOnError);
444                         }
445                 }
446
447                 public int LineNumberOfTaskNode {
448                         get { return current_task.Location != null ? current_task.Location.Line : 0; }
449                 }
450
451                 public string ProjectFileOfTaskNode {
452                         get { return current_task.FullPath; }
453                 }
454
455                 #endregion
456         }
457 }
458