Fix possible NRE on null Location.
[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 request = submission.BuildRequest;
80                         var parameters = submission.BuildManager.OngoingBuildParameters;
81                         this.project = project;
82                         var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (parameters.GetToolset (toolsVersion)), submission.BuildRequest.ProjectInstance.TaskDatabase);
83                         
84                         // null targets -> success. empty targets -> success(!)
85                         if (request.TargetNames == null)
86                                 result.OverallResult = BuildResultCode.Success;
87                         else {
88                                 foreach (var targetName in request.TargetNames.Where (t => t != null)) {
89                                         if (checkCancel ())
90                                                 break;
91
92                                         ProjectTargetInstance target;
93                                         var targetResult = new TargetResult ();
94                                         
95                                         // FIXME: check skip condition
96                                         if (false)
97                                                 targetResult.Skip ();
98                                         // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
99                                         else if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
100                                                 targetResult.Failure (null);
101                                         else {
102                                                 foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
103                                                         if (!project.EvaluateCondition (c.Condition))
104                                                                 continue;
105                                                         throw new NotImplementedException ();
106                                                 }
107                                                 foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
108                                                         if (!project.EvaluateCondition (c.Condition))
109                                                                 continue;
110                                                         throw new NotImplementedException ();
111                                                 }
112                                                 foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
113                                                         if (!project.EvaluateCondition (c.Condition))
114                                                                 continue;
115                                                         throw new NotImplementedException ();
116                                                 }
117                                                 foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
118                                                         var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, ti.Name);
119                                                         if (!project.EvaluateCondition (ti.Condition))
120                                                                 continue;
121                                                         current_task = ti;
122                                                         
123                                                         var factoryIdentityParameters = new Dictionary<string,string> ();
124 #if NET_4_5
125                                                         factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
126                                                         factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
127 #endif
128                                                         var task = buildTaskFactory.CreateTask (ti.Name, factoryIdentityParameters, this);
129                                                         task.HostObject = host;
130                                                         task.BuildEngine = this;
131                                                         // FIXME: this cannot be that simple, value has to be converted to the appropriate target type.
132                                                         var props = task.GetType ().GetProperties ()
133                                                                 .Where (p => p.CanWrite && p.GetCustomAttributes (typeof (RequiredAttribute), true).Any ());
134                                                         var missings = props.Where (p => !ti.Parameters.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
135                                                         if (missings.Any ())
136                                                                 throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}",
137                                                                         ti.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
138                                                         foreach (var p in ti.Parameters) {
139                                                                 var prop = task.GetType ().GetProperty (p.Key);
140                                                                 if (prop == null)
141                                                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
142                                                                 if (!prop.CanWrite)
143                                                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", ti.Name, p.Key));
144                                                                 prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType), null);
145                                                         }
146                                                         if (!task.Execute ()) {
147                                                                 targetResult.Failure (null);
148                                                                 if (!ContinueOnError)
149                                                                         break;
150                                                         }
151                                                         foreach (var to in ti.Outputs) {
152                                                                 var toItem = to as ProjectTaskOutputItemInstance;
153                                                                 var toProp = to as ProjectTaskOutputPropertyInstance;
154                                                                 string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
155                                                                 var pi = task.GetType ().GetProperty (taskParameter);
156                                                                 if (pi == null)
157                                                                         throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", ti.Name, toItem.TaskParameter));
158                                                                 if (!pi.CanRead)
159                                                                         throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.Name, toItem.TaskParameter));
160                                                                 if (toItem != null)
161                                                                         project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
162                                                                 else
163                                                                         project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
164                                                         }
165                                                 }
166                                                 Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
167                                                 var items = project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {});
168                                                 targetResult.Success (items);
169                                         }
170                                                 
171                                         result.AddResultsForTarget (targetName, targetResult);
172                                 }
173
174                                 // FIXME: check .NET behavior, whether cancellation always results in failure.
175                                 result.OverallResult = checkCancel () ? BuildResultCode.Failure : result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
176                         }
177                 }
178                 
179                 object ConvertTo (string source, Type targetType)
180                 {
181                         if (targetType.IsSubclassOf (typeof (ITaskItem)))
182                                 return new TargetOutputTaskItem () { ItemSpec = source };
183                         if (targetType.IsArray)
184                                 return new ArrayList (source.Split (';').Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
185                                                 .ToArray (targetType.GetElementType ());
186                         else
187                                 return Convert.ChangeType (source, targetType);
188                 }
189                 
190                 string ConvertFrom (object source)
191                 {
192                         if (source == null)
193                                 return string.Empty;
194                         var type = source.GetType ();
195                         if (type.IsSubclassOf (typeof (ITaskItem)))
196                                 return ((ITaskItem) source).ItemSpec;
197                         if (type.IsArray)
198                                 return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
199                         else
200                                 return (string) Convert.ChangeType (source, typeof (string));
201                 }
202                 
203                 class TargetOutputTaskItem : ITaskItem2
204                 {
205                         #region ITaskItem2 implementation
206                         public string GetMetadataValueEscaped (string metadataName)
207                         {
208                                 return null;
209                         }
210                         public void SetMetadataValueLiteral (string metadataName, string metadataValue)
211                         {
212                                 throw new NotSupportedException ();
213                         }
214                         public IDictionary CloneCustomMetadataEscaped ()
215                         {
216                                 return new Hashtable ();
217                         }
218                         public string EvaluatedIncludeEscaped {
219                                 get { return ProjectCollection.Escape (ItemSpec); }
220                                 set { ItemSpec = ProjectCollection.Unescape (value); }
221                         }
222                         #endregion
223                         #region ITaskItem implementation
224                         public IDictionary CloneCustomMetadata ()
225                         {
226                                 return new Hashtable ();
227                         }
228                         public void CopyMetadataTo (ITaskItem destinationItem)
229                         {
230                                 // do nothing
231                         }
232                         public string GetMetadata (string metadataName)
233                         {
234                                 return null;
235                         }
236                         public void RemoveMetadata (string metadataName)
237                         {
238                                 // do nothing
239                         }
240                         public void SetMetadata (string metadataName, string metadataValue)
241                         {
242                                 throw new NotSupportedException ();
243                         }
244                         public string ItemSpec { get; set; }
245                         public int MetadataCount {
246                                 get { return 0; }
247                         }
248                         public ICollection MetadataNames {
249                                 get { return new ArrayList (); }
250                         }
251                         #endregion
252                 }
253                 
254 #if NET_4_5
255                 #region IBuildEngine4 implementation
256                 
257                 // task objects are not in use anyways though...
258                 
259                 class TaskObjectRegistration
260                 {
261                         public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
262                         {
263                                 Key = key;
264                                 Object = obj;
265                                 Lifetime = lifetime;
266                                 AllowEarlyCollection = allowEarlyCollection;
267                         }
268                         public object Key { get; private set; }
269                         public object Object { get; private set; }
270                         public RegisteredTaskObjectLifetime Lifetime { get; private set; }
271                         public bool AllowEarlyCollection { get; private set; }
272                 }
273                 
274                 List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> ();
275
276                 public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
277                 {
278                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
279                         return reg != null ? reg.Object : null;
280                 }
281
282                 public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
283                 {
284                         task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection));
285                 }
286
287                 public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
288                 {
289                         var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
290                         if (reg != null)
291                                 task_objects.Remove (reg);
292                         return reg.Object;
293                 }
294                 #endregion
295 #endif
296
297                 #region IBuildEngine3 implementation
298
299                 public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs)
300                 {
301                         throw new NotImplementedException ();
302                 }
303
304                 public void Reacquire ()
305                 {
306                         throw new NotImplementedException ();
307                 }
308
309                 public void Yield ()
310                 {
311                         throw new NotImplementedException ();
312                 }
313
314                 #endregion
315
316                 #region IBuildEngine2 implementation
317
318                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
319                 {
320                         var proj = GetProjectInstance (projectFileName, toolsVersion);
321                         var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
322                         foreach (DictionaryEntry p in globalProperties)
323                                 globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value;
324                         var result = new BuildResult ();
325                         var outputs = new Dictionary<string, string> ();
326                         BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
327                         foreach (var p in outputs)
328                                 targetOutputs [p.Key] = p.Value;
329                         return result.OverallResult == BuildResultCode.Success;
330                 }
331
332                 public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)
333                 {
334                         throw new NotImplementedException ();
335                 }
336
337                 public bool IsRunningMultipleNodes {
338                         get {
339                                 throw new NotImplementedException ();
340                         }
341                 }
342                 
343                 ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion)
344                 {
345                         string fullPath = Path.GetFullPath (projectFileName);
346                         if (submission.BuildRequest.ProjectFullPath == fullPath)
347                                 return submission.BuildRequest.ProjectInstance;
348                         // FIXME: could also be filtered by global properties
349                         // http://msdn.microsoft.com/en-us/library/microsoft.build.evaluation.projectcollection.getloadedprojects.aspx
350                         var project = Projects.GetLoadedProjects (projectFileName).FirstOrDefault (p => p.ToolsVersion == toolsVersion);
351                         if (project == null)
352                                 throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName));
353                         return submission.BuildManager.GetProjectInstanceForBuild (project);
354                 }
355
356                 #endregion
357
358                 #region IBuildEngine implementation
359
360                 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
361                 {
362                         return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion);
363                 }
364
365                 public void LogCustomEvent (CustomBuildEventArgs e)
366                 {
367                         event_source.FireCustomEventRaised (this, e);
368                 }
369
370                 public void LogErrorEvent (BuildErrorEventArgs e)
371                 {
372                         event_source.FireErrorRaised (this, e);
373                 }
374
375                 public void LogMessageEvent (BuildMessageEventArgs e)
376                 {
377                         event_source.FireMessageRaised (this, e);
378                 }
379
380                 public void LogWarningEvent (BuildWarningEventArgs e)
381                 {
382                         event_source.FireWarningRaised (this, e);
383                 }
384
385                 public int ColumnNumberOfTaskNode {
386                         get { return current_task.Location != null ? current_task.Location.Column : 0; }
387                 }
388
389                 public bool ContinueOnError {
390                         get {
391                                 switch (current_task.ContinueOnError) {
392                                 case "WarnAndContinue":
393                                 case "ErrorAndContinue":
394                                         return true;
395                                 case "ErrorAndStop":
396                                         return false;
397                                 }
398                                 return project.EvaluateCondition (current_task.ContinueOnError);
399                         }
400                 }
401
402                 public int LineNumberOfTaskNode {
403                         get { return current_task.Location != null ? current_task.Location.Line : 0; }
404                 }
405
406                 public string ProjectFileOfTaskNode {
407                         get { return current_task.FullPath; }
408                 }
409
410                 #endregion
411         }
412 }
413