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 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);
84 // null targets -> success. empty targets -> success(!)
85 if (request.TargetNames == null)
86 result.OverallResult = BuildResultCode.Success;
88 foreach (var targetName in request.TargetNames.Where (t => t != null)) {
92 ProjectTargetInstance target;
93 var targetResult = new TargetResult ();
95 // FIXME: check skip condition
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);
102 foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
103 if (!project.EvaluateCondition (c.Condition))
105 throw new NotImplementedException ();
107 foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
108 if (!project.EvaluateCondition (c.Condition))
110 throw new NotImplementedException ();
112 foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
113 if (!project.EvaluateCondition (c.Condition))
115 throw new NotImplementedException ();
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))
123 var factoryIdentityParameters = new Dictionary<string,string> ();
125 factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
126 factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
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)));
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);
141 throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
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);
146 if (!task.Execute ()) {
147 targetResult.Failure (null);
148 if (!ContinueOnError)
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);
157 throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", ti.Name, toItem.TaskParameter));
159 throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.Name, toItem.TaskParameter));
161 project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
163 project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
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);
171 result.AddResultsForTarget (targetName, targetResult);
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;
179 object ConvertTo (string source, Type targetType)
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 ());
187 return Convert.ChangeType (source, targetType);
190 string ConvertFrom (object source)
194 var type = source.GetType ();
195 if (type.IsSubclassOf (typeof (ITaskItem)))
196 return ((ITaskItem) source).ItemSpec;
198 return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
200 return (string) Convert.ChangeType (source, typeof (string));
203 class TargetOutputTaskItem : ITaskItem2
205 #region ITaskItem2 implementation
206 public string GetMetadataValueEscaped (string metadataName)
210 public void SetMetadataValueLiteral (string metadataName, string metadataValue)
212 throw new NotSupportedException ();
214 public IDictionary CloneCustomMetadataEscaped ()
216 return new Hashtable ();
218 public string EvaluatedIncludeEscaped {
219 get { return ProjectCollection.Escape (ItemSpec); }
220 set { ItemSpec = ProjectCollection.Unescape (value); }
223 #region ITaskItem implementation
224 public IDictionary CloneCustomMetadata ()
226 return new Hashtable ();
228 public void CopyMetadataTo (ITaskItem destinationItem)
232 public string GetMetadata (string metadataName)
236 public void RemoveMetadata (string metadataName)
240 public void SetMetadata (string metadataName, string metadataValue)
242 throw new NotSupportedException ();
244 public string ItemSpec { get; set; }
245 public int MetadataCount {
248 public ICollection MetadataNames {
249 get { return new ArrayList (); }
255 #region IBuildEngine4 implementation
257 // task objects are not in use anyways though...
259 class TaskObjectRegistration
261 public TaskObjectRegistration (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
266 AllowEarlyCollection = allowEarlyCollection;
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; }
274 List<TaskObjectRegistration> task_objects = new List<TaskObjectRegistration> ();
276 public object GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
278 var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
279 return reg != null ? reg.Object : null;
282 public void RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection)
284 task_objects.Add (new TaskObjectRegistration (key, obj, lifetime, allowEarlyCollection));
287 public object UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime)
289 var reg = task_objects.FirstOrDefault (t => t.Key == key && t.Lifetime == lifetime);
291 task_objects.Remove (reg);
297 #region IBuildEngine3 implementation
299 public BuildEngineResult BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IList<string>[] removeGlobalProperties, string[] toolsVersion, bool returnTargetOutputs)
301 throw new NotImplementedException ();
304 public void Reacquire ()
306 throw new NotImplementedException ();
311 throw new NotImplementedException ();
316 #region IBuildEngine2 implementation
318 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
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;
332 public bool BuildProjectFilesInParallel (string[] projectFileNames, string[] targetNames, IDictionary[] globalProperties, IDictionary[] targetOutputsPerProject, string[] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion)
334 throw new NotImplementedException ();
337 public bool IsRunningMultipleNodes {
339 throw new NotImplementedException ();
343 ProjectInstance GetProjectInstance (string projectFileName, string toolsVersion)
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);
352 throw new InvalidOperationException (string.Format ("Project '{0}' is not loaded", projectFileName));
353 return submission.BuildManager.GetProjectInstanceForBuild (project);
358 #region IBuildEngine implementation
360 public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
362 return BuildProjectFile (projectFileName, targetNames, globalProperties, targetOutputs, Projects.DefaultToolsVersion);
365 public void LogCustomEvent (CustomBuildEventArgs e)
367 event_source.FireCustomEventRaised (this, e);
370 public void LogErrorEvent (BuildErrorEventArgs e)
372 event_source.FireErrorRaised (this, e);
375 public void LogMessageEvent (BuildMessageEventArgs e)
377 event_source.FireMessageRaised (this, e);
380 public void LogWarningEvent (BuildWarningEventArgs e)
382 event_source.FireWarningRaised (this, e);
385 public int ColumnNumberOfTaskNode {
386 get { return current_task.Location != null ? current_task.Location.Column : 0; }
389 public bool ContinueOnError {
391 switch (current_task.ContinueOnError) {
392 case "WarnAndContinue":
393 case "ErrorAndContinue":
398 return project.EvaluateCondition (current_task.ContinueOnError);
402 public int LineNumberOfTaskNode {
403 get { return current_task.Location != null ? current_task.Location.Line : 0; }
406 public string ProjectFileOfTaskNode {
407 get { return current_task.FullPath; }