5 // Atsushi Enomoto (atsushi@xamarin.com)
7 // Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
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.
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;
38 namespace Microsoft.Build.Internal
47 public BuildEngine4 (BuildSubmission submission)
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);
56 BuildSubmission submission;
57 ProjectInstance project;
58 ProjectTaskInstance current_task;
59 EventSource event_source;
61 public ProjectCollection Projects {
62 get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
66 // While we are not faced to implement those features, there are some modern task execution requirements.
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.)
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.
77 public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
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 });
84 class InternalBuildArguments
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;
96 void BuildProject (InternalBuildArguments args)
98 var request = submission.BuildRequest;
99 var parameters = submission.BuildManager.OngoingBuildParameters;
100 this.project = args.Project;
102 event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null));
104 // null targets -> success. empty targets -> success(!)
105 if (request.TargetNames == null)
106 args.Result.OverallResult = BuildResultCode.Success;
108 foreach (var targetName in request.TargetNames.Where (t => t != null))
109 args.Result.AddResultsForTarget (targetName, BuildTarget (targetName, args));
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;
114 event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success));
117 TargetResult BuildTarget (string targetName, InternalBuildArguments args)
119 var targetResult = new TargetResult ();
121 var request = submission.BuildRequest;
122 var parameters = submission.BuildManager.OngoingBuildParameters;
123 ProjectTargetInstance target;
125 // FIXME: check skip condition
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)));
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);
136 args.Result.AddResultsForTarget (dep, result);
137 if (result.ResultCode == TargetResultCode.Failure) {
138 targetResult.Failure (null);
143 event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath));
145 // Here we check cancellation (only after TargetStarted event).
146 if (args.CheckCancel ()) {
147 targetResult.Failure (new OperationCanceledException ("Build has canceled"));
151 foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
152 if (!args.Project.EvaluateCondition (c.Condition))
154 throw new NotImplementedException ();
156 foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
157 if (!args.Project.EvaluateCondition (c.Condition))
159 throw new NotImplementedException ();
161 foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
162 if (!args.Project.EvaluateCondition (c.Condition))
164 throw new NotImplementedException ();
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))
172 var factoryIdentityParameters = new Dictionary<string,string> ();
174 factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
175 factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
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)));
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);
190 throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
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);
195 if (!task.Execute ()) {
196 targetResult.Failure (null);
197 if (!ContinueOnError)
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);
206 throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", ti.Name, toItem.TaskParameter));
208 throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.Name, toItem.TaskParameter));
210 args.Project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
212 args.Project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
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) => {
218 targetResult.Success (items);
219 event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath, true));
224 object ConvertTo (string source, Type targetType)
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 ());
232 return Convert.ChangeType (source, targetType);
235 string ConvertFrom (object source)
239 var type = source.GetType ();
240 if (type.IsSubclassOf (typeof (ITaskItem)))
241 return ((ITaskItem) source).ItemSpec;
243 return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
245 return (string) Convert.ChangeType (source, typeof (string));
248 class TargetOutputTaskItem : ITaskItem2
250 #region ITaskItem2 implementation
251 public string GetMetadataValueEscaped (string metadataName)
255 public void SetMetadataValueLiteral (string metadataName, string metadataValue)
257 throw new NotSupportedException ();
259 public IDictionary CloneCustomMetadataEscaped ()
261 return new Hashtable ();
263 public string EvaluatedIncludeEscaped {
264 get { return ProjectCollection.Escape (ItemSpec); }
265 set { ItemSpec = ProjectCollection.Unescape (value); }
268 #region ITaskItem implementation
269 public IDictionary CloneCustomMetadata ()
271 return new Hashtable ();
273 public void CopyMetadataTo (ITaskItem destinationItem)
277 public string GetMetadata (string metadataName)
281 public void RemoveMetadata (string metadataName)
285 public void SetMetadata (string metadataName, string metadataValue)
287 throw new NotSupportedException ();
289 public string ItemSpec { get; set; }
290 public int MetadataCount {
293 public ICollection MetadataNames {
294 get { return new ArrayList (); }
300 #region IBuildEngine4 implementation
302 // task objects are not in use anyways though...
304 class TaskObjectRegistration
306 public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
311 AllowEarlyCollection = allowEarlyCollection;
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; }
319 List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> ();
321 public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
323 var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
324 return reg != null ? reg.Object : null;
327 public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
329 task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection));
332 public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
334 var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
336 task_objects.Remove (reg);
342 #region IBuildEngine3 implementation
344 public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs)
346 throw new NotImplementedException ();
349 public void Reacquire ()
351 throw new NotImplementedException ();
356 throw new NotImplementedException ();
361 #region IBuildEngine2 implementation
363 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
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;
377 public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)
379 throw new NotImplementedException ();
382 public bool IsRunningMultipleNodes {
384 throw new NotImplementedException ();
388 ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion)
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);
397 throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName));
398 return submission.BuildManager.GetProjectInstanceForBuild (project);
403 #region IBuildEngine implementation
405 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
407 return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion);
410 public void LogCustomEvent (CustomBuildEventArgs e)
412 event_source.FireCustomEventRaised (this, e);
415 public void LogErrorEvent (BuildErrorEventArgs e)
417 event_source.FireErrorRaised (this, e);
420 public void LogMessageEvent (BuildMessageEventArgs e)
422 event_source.FireMessageRaised (this, e);
425 public void LogWarningEvent (BuildWarningEventArgs e)
427 event_source.FireWarningRaised (this, e);
430 public int ColumnNumberOfTaskNode {
431 get { return current_task.Location != null ? current_task.Location.Column : 0; }
434 public bool ContinueOnError {
436 switch (current_task.ContinueOnError) {
437 case "WarnAndContinue":
438 case "ErrorAndContinue":
443 return project.EvaluateCondition (current_task.ContinueOnError);
447 public int LineNumberOfTaskNode {
448 get { return current_task.Location != null ? current_task.Location.Line : 0; }
451 public string ProjectFileOfTaskNode {
452 get { return current_task.FullPath; }