+//
+// BuildEngine4.cs
+//
+// Author:
+// Atsushi Enomoto (atsushi@xamarin.com)
+//
+// Copyright (C) 2013 Xamarin Inc. (http://www.xamarin.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
using System;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.Build.Internal
{
- class BuildEngine4 : IBuildEngine4
+ class BuildEngine4
+#if NET_4_5
+ : IBuildEngine4
+#else
+ : IBuildEngine3
+#endif
{
public BuildEngine4 (BuildSubmission submission)
{
this.submission = submission;
event_source = new EventSource ();
+ if (submission.BuildManager.OngoingBuildParameters.Loggers != null)
+ foreach (var l in submission.BuildManager.OngoingBuildParameters.Loggers)
+ l.Initialize (event_source);
}
BuildSubmission submission;
- ProjectInstance current_project;
+ ProjectInstance project;
ProjectTaskInstance current_task;
EventSource event_source;
public ProjectCollection Projects {
get { return submission.BuildManager.OngoingBuildParameters.ProjectCollection; }
}
-
- class BuildTask : ITask
+
+ // FIXME:
+ // While we are not faced to implement those features, there are some modern task execution requirements.
+ //
+ // This will have to be available for "out of process" nodes (see NodeAffinity).
+ // NodeAffinity is set per project file at BuildManager.HostServices.
+ // When NodeAffinity is set to OutOfProc, it should probably launch different build host
+ // that runs separate build tasks. (.NET has MSBuildTaskHost.exe which I guess is about that.)
+ //
+ // Also note that the complete implementation has to support LoadInSeparateAppDomainAttribute
+ // (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
+ // in separate AppDomain.
+ //
+ public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
{
- public IBuildEngine BuildEngine { get; set; }
- public ITaskHost HostObject { get; set; }
+ var parameters = submission.BuildManager.OngoingBuildParameters;
+ var buildTaskFactory = new BuildTaskFactory (BuildTaskDatabase.GetDefaultTaskDatabase (parameters.GetToolset (toolsVersion)), submission.BuildRequest.ProjectInstance.TaskDatabase);
+ BuildProject (new InternalBuildArguments () { CheckCancel = checkCancel, Result = result, Project = project, TargetNames = targetNames, GlobalProperties = globalProperties, TargetOutputs = targetOutputs, ToolsVersion = toolsVersion, BuildTaskFactory = buildTaskFactory });
}
- public BuildResult BuildProject (Func<bool> checkCancel, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
+ class InternalBuildArguments
+ {
+ public Func<bool> CheckCancel;
+ public BuildResult Result;
+ public ProjectInstance Project;
+ public IEnumerable<string> TargetNames;
+ public IDictionary<string,string> GlobalProperties;
+ public IDictionary<string,string> TargetOutputs;
+ public string ToolsVersion;
+ public BuildTaskFactory BuildTaskFactory;
+ }
+
+ void BuildProject (InternalBuildArguments args)
{
- var result = new BuildResult () { SubmissionId = submission.SubmissionId };
var request = submission.BuildRequest;
var parameters = submission.BuildManager.OngoingBuildParameters;
+ this.project = args.Project;
+ event_source.FireBuildStarted (this, new BuildStartedEventArgs ("Build Started", null));
+
// null targets -> success. empty targets -> success(!)
if (request.TargetNames == null)
- result.OverallResult = BuildResultCode.Success;
+ args.Result.OverallResult = BuildResultCode.Success;
else {
- foreach (var targetName in request.TargetNames.Where (t => t != null)) {
- if (checkCancel ())
- break;
-
- ProjectTargetInstance target;
- // null key is allowed and regarded as blind success(!)
- if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
- result.AddResultsForTarget (targetName, new TargetResult (new ITaskItem [0], TargetResultCode.Failure));
- else {
- foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ())
- throw new NotImplementedException ();
- foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ())
- throw new NotImplementedException ();
- foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ())
- throw new NotImplementedException ();
- foreach (var c in target.Children.OfType<ProjectTaskInstance> ()) {
- var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, c.Name);
- var task = new BuildTask () { BuildEngine = this, HostObject = host };
-
- throw new NotImplementedException ();
- }
+ foreach (var targetName in request.TargetNames.Where (t => t != null))
+ args.Result.AddResultsForTarget (targetName, BuildTarget (targetName, args));
+
+ // FIXME: check .NET behavior, whether cancellation always results in failure.
+ args.Result.OverallResult = args.CheckCancel () ? BuildResultCode.Failure : args.Result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
+ }
+ event_source.FireBuildFinished (this, new BuildFinishedEventArgs ("Build Finished.", null, args.Result.OverallResult == BuildResultCode.Success));
+ }
+
+ TargetResult BuildTarget (string targetName, InternalBuildArguments args)
+ {
+ var targetResult = new TargetResult ();
+
+ var request = submission.BuildRequest;
+ var parameters = submission.BuildManager.OngoingBuildParameters;
+ ProjectTargetInstance target;
+
+ // FIXME: check skip condition
+ if (false)
+ targetResult.Skip ();
+ // null key is allowed and regarded as blind success(!) (as long as it could retrieve target)
+ else if (!request.ProjectInstance.Targets.TryGetValue (targetName, out target))
+ targetResult.Failure (new InvalidOperationException (string.Format ("target '{0}' was not found in project '{1}'", targetName, project.FullPath)));
+ else {
+ // process DependsOnTargets first.
+ foreach (var dep in project.ExpandString (target.DependsOnTargets).Split (';').Where (s => !string.IsNullOrEmpty (s))) {
+ var result = BuildTarget (dep, args);
+ args.Result.AddResultsForTarget (dep, result);
+ if (result.ResultCode == TargetResultCode.Failure) {
+ targetResult.Failure (null);
+ return targetResult;
}
}
- // FIXME: check .NET behavior, whether cancellation always results in failure.
- result.OverallResult = checkCancel () ? BuildResultCode.Failure : result.ResultsByTarget.Select (p => p.Value).Any (r => r.ResultCode == TargetResultCode.Failure) ? BuildResultCode.Failure : BuildResultCode.Success;
+ event_source.FireTargetStarted (this, new TargetStartedEventArgs ("Target Started", null, targetName, project.FullPath, target.FullPath));
+
+ // Here we check cancellation (only after TargetStarted event).
+ if (args.CheckCancel ()) {
+ targetResult.Failure (new OperationCanceledException ("Build has canceled"));
+ return targetResult;
+ }
+
+ foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
+ if (!args.Project.EvaluateCondition (c.Condition))
+ continue;
+ throw new NotImplementedException ();
+ }
+ foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
+ if (!args.Project.EvaluateCondition (c.Condition))
+ continue;
+ throw new NotImplementedException ();
+ }
+ foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
+ if (!args.Project.EvaluateCondition (c.Condition))
+ continue;
+ throw new NotImplementedException ();
+ }
+ foreach (var ti in target.Children.OfType<ProjectTaskInstance> ()) {
+ var host = request.HostServices == null ? null : request.HostServices.GetHostObject (request.ProjectFullPath, targetName, ti.Name);
+ if (!args.Project.EvaluateCondition (ti.Condition))
+ continue;
+ current_task = ti;
+
+ var factoryIdentityParameters = new Dictionary<string,string> ();
+ #if NET_4_5
+ factoryIdentityParameters ["MSBuildRuntime"] = ti.MSBuildRuntime;
+ factoryIdentityParameters ["MSBuildArchitecture"] = ti.MSBuildArchitecture;
+ #endif
+ var task = args.BuildTaskFactory.CreateTask (ti.Name, factoryIdentityParameters, this);
+ task.HostObject = host;
+ task.BuildEngine = this;
+ // FIXME: this cannot be that simple, value has to be converted to the appropriate target type.
+ var props = task.GetType ().GetProperties ()
+ .Where (p => p.CanWrite && p.GetCustomAttributes (typeof(RequiredAttribute), true).Any ());
+ var missings = props.Where (p => !ti.Parameters.Any (tp => tp.Key.Equals (p.Name, StringComparison.OrdinalIgnoreCase)));
+ if (missings.Any ())
+ throw new InvalidOperationException (string.Format ("Task {0} of type {1} is used without specifying mandatory property: {2}",
+ ti.Name, task.GetType (), string.Join (", ", missings.Select (p => p.Name).ToArray ())));
+ foreach (var p in ti.Parameters) {
+ var prop = task.GetType ().GetProperty (p.Key);
+ if (prop == null)
+ throw new InvalidOperationException (string.Format ("Task {0} does not have property {1}", ti.Name, p.Key));
+ if (!prop.CanWrite)
+ throw new InvalidOperationException (string.Format ("Task {0} has property {1} but it is read-only.", ti.Name, p.Key));
+ prop.SetValue (task, ConvertTo (p.Value, prop.PropertyType), null);
+ }
+ event_source.FireTaskStarted (this, new TaskStartedEventArgs ("Task Started", null, project.FullPath, ti.FullPath, ti.Name));
+ if (!task.Execute ()) {
+ event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, ti.FullPath, ti.Name, false));
+ targetResult.Failure (null);
+ if (!ContinueOnError) {
+ event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Failed", null, targetName, project.FullPath, target.FullPath, false));
+ return targetResult;
+ }
+ } else {
+ event_source.FireTaskFinished (this, new TaskFinishedEventArgs ("Task Finished", null, project.FullPath, ti.FullPath, ti.Name, true));
+ foreach (var to in ti.Outputs) {
+ var toItem = to as ProjectTaskOutputItemInstance;
+ var toProp = to as ProjectTaskOutputPropertyInstance;
+ string taskParameter = toItem != null ? toItem.TaskParameter : toProp.TaskParameter;
+ var pi = task.GetType ().GetProperty (taskParameter);
+ if (pi == null)
+ throw new InvalidOperationException (string.Format ("Task {0} does not have property {1} specified as TaskParameter", ti.Name, toItem.TaskParameter));
+ if (!pi.CanRead)
+ throw new InvalidOperationException (string.Format ("Task {0} has property {1} specified as TaskParameter, but it is write-only", ti.Name, toItem.TaskParameter));
+ if (toItem != null)
+ args.Project.AddItem (toItem.ItemType, ConvertFrom (pi.GetValue (task, null)));
+ else
+ args.Project.SetProperty (toProp.PropertyName, ConvertFrom (pi.GetValue (task, null)));
+ }
+ }
+ }
+ Func<string,ITaskItem> creator = s => new TargetOutputTaskItem () { ItemSpec = s };
+ var items = args.Project.GetAllItems (target.Outputs, string.Empty, creator, creator, s => true, (t, s) => {
+ });
+ targetResult.Success (items);
+ event_source.FireTargetFinished (this, new TargetFinishedEventArgs ("Target Finished", null, targetName, project.FullPath, target.FullPath, true));
}
- return result;
+ return targetResult;
}
+ object ConvertTo (string source, Type targetType)
+ {
+ if (targetType.IsSubclassOf (typeof (ITaskItem)))
+ return new TargetOutputTaskItem () { ItemSpec = source };
+ if (targetType.IsArray)
+ return new ArrayList (source.Split (';').Select (s => ConvertTo (s, targetType.GetElementType ())).ToArray ())
+ .ToArray (targetType.GetElementType ());
+ else
+ return Convert.ChangeType (source, targetType);
+ }
+
+ string ConvertFrom (object source)
+ {
+ if (source == null)
+ return string.Empty;
+ var type = source.GetType ();
+ if (type.IsSubclassOf (typeof (ITaskItem)))
+ return ((ITaskItem) source).ItemSpec;
+ if (type.IsArray)
+ return string.Join (":", ((Array) source).Cast<object> ().Select (o => ConvertFrom (o)).ToArray ());
+ else
+ return (string) Convert.ChangeType (source, typeof (string));
+ }
+
+ class TargetOutputTaskItem : ITaskItem2
+ {
+ #region ITaskItem2 implementation
+ public string GetMetadataValueEscaped (string metadataName)
+ {
+ return null;
+ }
+ public void SetMetadataValueLiteral (string metadataName, string metadataValue)
+ {
+ throw new NotSupportedException ();
+ }
+ public IDictionary CloneCustomMetadataEscaped ()
+ {
+ return new Hashtable ();
+ }
+ public string EvaluatedIncludeEscaped {
+ get { return ProjectCollection.Escape (ItemSpec); }
+ set { ItemSpec = ProjectCollection.Unescape (value); }
+ }
+ #endregion
+ #region ITaskItem implementation
+ public IDictionary CloneCustomMetadata ()
+ {
+ return new Hashtable ();
+ }
+ public void CopyMetadataTo (ITaskItem destinationItem)
+ {
+ // do nothing
+ }
+ public string GetMetadata (string metadataName)
+ {
+ return null;
+ }
+ public void RemoveMetadata (string metadataName)
+ {
+ // do nothing
+ }
+ public void SetMetadata (string metadataName, string metadataValue)
+ {
+ throw new NotSupportedException ();
+ }
+ public string ItemSpec { get; set; }
+ public int MetadataCount {
+ get { return 0; }
+ }
+ public ICollection MetadataNames {
+ get { return new ArrayList (); }
+ }
+ #endregion
+ }
+
+#if NET_4_5
#region IBuildEngine4 implementation
// task objects are not in use anyways though...
task_objects.Remove (reg);
return reg.Object;
}
-
#endregion
+#endif
#region IBuildEngine3 implementation
public bool BuildProjectFile (string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion)
{
- var project = GetProjectInstance (projectFileName, toolsVersion);
+ var proj = GetProjectInstance (projectFileName, toolsVersion);
var globalPropertiesThatMakeSense = new Dictionary<string,string> ();
foreach (DictionaryEntry p in globalProperties)
globalPropertiesThatMakeSense [(string) p.Key] = (string) p.Value;
+ var result = new BuildResult ();
var outputs = new Dictionary<string, string> ();
- var result = BuildProject (() => false, project, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
+ BuildProject (() => false, result, proj, targetNames, globalPropertiesThatMakeSense, outputs, toolsVersion);
foreach (var p in outputs)
targetOutputs [p.Key] = p.Value;
return result.OverallResult == BuildResultCode.Success;
}
public int ColumnNumberOfTaskNode {
- get { return current_task.Location.Column; }
+ get { return current_task.Location != null ? current_task.Location.Column : 0; }
}
public bool ContinueOnError {
- get { return current_project.EvaluateCondition (current_task.ContinueOnError); }
+ get {
+ switch (current_task.ContinueOnError) {
+ case "WarnAndContinue":
+ case "ErrorAndContinue":
+ return true;
+ case "ErrorAndStop":
+ return false;
+ }
+ return !string.IsNullOrEmpty (current_task.ContinueOnError) && project.EvaluateCondition (current_task.ContinueOnError);
+ }
}
public int LineNumberOfTaskNode {
- get { return current_task.Location.Line; }
+ get { return current_task.Location != null ? current_task.Location.Line : 0; }
}
public string ProjectFileOfTaskNode {