using Microsoft.Build.Evaluation;
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
using System.Threading;
using Microsoft.Build.Internal;
using System.Linq;
{
public class BuildManager
{
+ static BuildManager default_manager = new BuildManager ();
+
+ public static BuildManager DefaultBuildManager {
+ get { return default_manager; }
+ }
+
public BuildManager ()
{
+ build_node_manager = new BuildNodeManager (this);
}
public BuildManager (string hostName)
public void Dispose ()
{
+ WaitHandle.WaitAll (submissions.Select (s => s.WaitHandle).ToArray ());
+ BuildNodeManager.Stop ();
}
~BuildManager ()
{
- // maybe HostServices related cleanup is expected.
+ // maybe processes created by out-of-process nodes should be signaled.
+ BuildNodeManager.Stop ();
}
- readonly TaskFactory<BuildResult> task_factory = new TaskFactory<BuildResult> ();
- List<BuildSubmission> submissions = new List<BuildSubmission> ();
+ readonly List<BuildSubmission> submissions = new List<BuildSubmission> ();
BuildParameters ongoing_build_parameters;
-
+
internal BuildParameters OngoingBuildParameters {
get { return ongoing_build_parameters; }
}
{
if (ongoing_build_parameters != null)
throw new InvalidOperationException ("There is already ongoing build");
- ongoing_build_parameters = parameters;
-
- // FIXME: apply build parameters to this build manager instance.
+ ongoing_build_parameters = parameters.Clone ();
}
public BuildResult Build (BuildParameters parameters, BuildRequestData requestData)
public void CancelAllSubmissions ()
{
- foreach (var sub in submissions)
- sub.Cancel ();
+ foreach (var sub in submissions) {
+ try {
+ if (!sub.IsCompleted)
+ sub.Cancel ();
+ } catch (InvalidOperationException) {
+ // some submissions could be already done during this iteration. Ignore that.
+ }
+ }
submissions.Clear ();
}
{
if (ongoing_build_parameters == null)
throw new InvalidOperationException ("Build has not started");
+//Console.Error.WriteLine ("{0} EndBuild waiting for subs: " + submissions.Count, GetHashCode ());
if (submissions.Count > 0)
WaitHandle.WaitAll (submissions.Select (s => s.WaitHandle).ToArray ());
+//Console.Error.WriteLine ("{0} EndBuild waiting for NodeManager", GetHashCode ());
+ BuildNodeManager.Stop ();
+//Console.Error.WriteLine ("{0} EndBuild done", GetHashCode ());
ongoing_build_parameters = null;
}
public void ResetCaches ()
{
- throw new NotImplementedException ();
+ if (OngoingBuildParameters != null)
+ throw new InvalidOperationException ("Cannot reset caches while builds are in progress.");
+
+ build_node_manager.ResetCaches ();
+ build_task_factory.ResetCaches ();
}
BuildTaskFactory build_task_factory = new BuildTaskFactory ();
internal BuildTaskFactory BuildTaskFactory {
get { return build_task_factory; }
}
-
- internal TaskFactory<BuildResult> ThreadTaskFactory {
- get { return task_factory; }
- }
- static BuildManager default_manager = new BuildManager ();
-
- public static BuildManager DefaultBuildManager {
- get { return default_manager; }
+ BuildNodeManager build_node_manager;
+
+ internal BuildNodeManager BuildNodeManager {
+ get { return build_node_manager; }
}
}
}
-
using System.Globalization;
using System.Linq;
using System.Threading;
+using System.Collections;
namespace Microsoft.Build.Execution
{
if (projectCollection == null)
throw new ArgumentNullException ("projectCollection");
projects = projectCollection;
+
+ EnableNodeReuse = true;
+ Culture = CultureInfo.CurrentCulture;
+ UICulture = CultureInfo.CurrentUICulture;
// these properties are copied, while some members (such as Loggers) are not.
this.DefaultToolsVersion = projectCollection.DefaultToolsVersion;
this.ToolsetDefinitionLocations = projectCollection.ToolsetLocations;
this.GlobalProperties = projectCollection.GlobalProperties;
+ environment_properties = new Dictionary<string,string> ();
+ foreach (DictionaryEntry p in Environment.GetEnvironmentVariables ())
+ environment_properties [(string) p.Key] = (string) p.Value;
}
readonly ProjectCollection projects;
+ Dictionary<string,string> environment_properties;
internal ProjectCollection ProjectCollection {
get { return projects; }
public BuildParameters Clone ()
{
var ret = (BuildParameters) MemberwiseClone ();
- ret.ForwardingLoggers = ret.ForwardingLoggers.ToArray ();
- ret.GlobalProperties = ret.GlobalProperties.ToDictionary (p => p.Key, p => p.Value);
- ret.Loggers = ret.Loggers == null ? null : ret.Loggers.ToArray ();
+ ret.ForwardingLoggers = ForwardingLoggers == null ? null : ForwardingLoggers.ToArray ();
+ ret.GlobalProperties = GlobalProperties == null ? null : GlobalProperties.ToDictionary (p => p.Key, p => p.Value);
+ ret.Loggers = Loggers == null ? null : new List<ILogger> (Loggers);
+ ret.environment_properties = new Dictionary<string, string> (environment_properties);
return ret;
}
[MonoTODO]
public bool DetailedSummary { get; set; }
- [MonoTODO]
public bool EnableNodeReuse { get; set; }
[MonoTODO]
public IDictionary<string, string> EnvironmentProperties {
- get { throw new NotImplementedException (); }
+ get { return environment_properties; }
}
[MonoTODO]
[MonoTODO]
public IDictionary<string, string> GlobalProperties { get; set; }
- [MonoTODO]
public HostServices HostServices { get; set; }
[MonoTODO]
BuildRequestData request;
BuildSubmissionCompleteCallback callback;
bool is_started, is_completed, is_canceled;
- ManualResetEvent wait_handle = new ManualResetEvent (false);
+ ManualResetEvent wait_handle = new ManualResetEvent (true);
public object AsyncContext { get; private set; }
public BuildManager BuildManager { get; private set; }
}
public int SubmissionId { get; private set; }
public WaitHandle WaitHandle {
- get {
- if (!is_started)
- throw new InvalidOperationException ("Build has not started yet");
- return wait_handle;
- }
+ get { return wait_handle; }
}
internal BuildRequestData BuildRequest {
public BuildResult Execute ()
{
- var engine = new BuildEngine4 (this);
- string toolsVersion = request.ExplicitlySpecifiedToolsVersion ?? request.ProjectInstance.ToolsVersion ?? BuildManager.OngoingBuildParameters.DefaultToolsVersion;
- var outputs = new Dictionary<string,string> ();
- BuildResult = engine.BuildProject (() => is_canceled, request.ProjectInstance, request.TargetNames, BuildManager.OngoingBuildParameters.GlobalProperties, outputs, toolsVersion);
+ BuildResult = new BuildResult () { SubmissionId = SubmissionId };
+ try {
+ var engine = new BuildEngine4 (this);
+ string toolsVersion = request.ExplicitlySpecifiedToolsVersion ?? request.ProjectInstance.ToolsVersion ?? BuildManager.OngoingBuildParameters.DefaultToolsVersion;
+ var outputs = new Dictionary<string,string> ();
+ engine.BuildProject (() => is_canceled, BuildResult, request.ProjectInstance, request.TargetNames, BuildManager.OngoingBuildParameters.GlobalProperties, outputs, toolsVersion);
+ } catch (Exception ex) {
+ BuildResult.Exception = ex;
+ BuildResult.OverallResult = BuildResultCode.Failure;
+ }
wait_handle.Set ();
if (callback != null)
callback (this);
is_started = true;
this.AsyncContext = context;
this.callback = callback;
+ wait_handle.Reset ();
- BuildManager.ThreadTaskFactory.StartNew (Execute);
+ BuildManager.BuildNodeManager.Enqueue (this);
}
}
}
}
BuildSubmission submission;
- ProjectInstance current_project;
+ ProjectInstance project;
ProjectTaskInstance current_task;
EventSource event_source;
// (which is most likely derived from AppDomainIsolatedBuildTask) that marks a task to run
// in separate AppDomain.
//
- public BuildResult BuildProject (Func<bool> checkCancel, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
+ public void BuildProject (Func<bool> checkCancel, BuildResult result, ProjectInstance project, IEnumerable<string> targetNames, IDictionary<string,string> globalProperties, IDictionary<string,string> targetOutputs, string toolsVersion)
{
- var result = new BuildResult () { SubmissionId = submission.SubmissionId };
var request = submission.BuildRequest;
var parameters = submission.BuildManager.OngoingBuildParameters;
+ this.project = project;
// null targets -> success. empty targets -> success(!)
if (request.TargetNames == null)
result.AddResultsForTarget (targetName, new TargetResult (new ITaskItem [0], TargetResultCode.Failure));
else {
foreach (var c in target.Children.OfType<ProjectPropertyGroupTaskInstance> ()) {
- if (!current_project.EvaluateCondition (c.Condition))
+ if (!project.EvaluateCondition (c.Condition))
continue;
throw new NotImplementedException ();
}
foreach (var c in target.Children.OfType<ProjectItemGroupTaskInstance> ()) {
- if (!current_project.EvaluateCondition (c.Condition))
+ if (!project.EvaluateCondition (c.Condition))
continue;
throw new NotImplementedException ();
}
foreach (var c in target.Children.OfType<ProjectOnErrorInstance> ()) {
- if (!current_project.EvaluateCondition (c.Condition))
+ if (!project.EvaluateCondition (c.Condition))
continue;
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);
- if (!current_project.EvaluateCondition (c.Condition))
+ if (!project.EvaluateCondition (c.Condition))
continue;
current_task = c;
// MSBuildArchitecture and MSBuildRuntime attributes cannot be suppored.
// 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;
}
- return result;
}
#region IBuildEngine4 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;
--- /dev/null
+//
+// 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.Generic;
+using System.Linq;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace Microsoft.Build.Internal
+{
+ class BuildNodeManager
+ {
+ public BuildNodeManager (BuildManager buildManager)
+ {
+ BuildManager = buildManager;
+ task_factory.StartNew (RunLoop);
+ }
+
+ public BuildManager BuildManager { get; private set; }
+
+ List<BuildNode> in_proc_nodes = new List<BuildNode> ();
+ List<BuildNode> out_proc_nodes = new List<BuildNode> ();
+ AutoResetEvent queue_wait_handle = new AutoResetEvent (false);
+ Queue<BuildSubmission> queued_builds = new Queue<BuildSubmission> ();
+ Dictionary<BuildSubmission,Task> ongoing_builds = new Dictionary<BuildSubmission, Task> ();
+ bool run_loop = true;
+
+ readonly TaskFactory task_factory = new TaskFactory ();
+ internal TaskFactory ThreadTaskFactory {
+ get { return task_factory; }
+ }
+
+ void RunLoop ()
+ {
+ while (run_loop) {
+ if (queued_builds.Count == 0) {
+//Console.Error.WriteLine ("!!!! {0} waiting for build queue...", BuildManager.GetHashCode ());
+ queue_wait_handle.WaitOne ();
+ }
+ if (!run_loop)
+ break;
+ if (!queued_builds.Any ())
+ continue;
+ var build = queued_builds.Dequeue ();
+ Execute (build);
+ }
+ }
+
+ public void Stop ()
+ {
+ run_loop = false;
+ queue_wait_handle.Set ();
+ }
+
+ public void ResetCaches ()
+ {
+ in_proc_nodes.Clear ();
+ out_proc_nodes.Clear ();
+ }
+
+ public void Enqueue (BuildSubmission build)
+ {
+ queued_builds.Enqueue (build);
+ queue_wait_handle.Set ();
+ }
+
+ void Execute (BuildSubmission build)
+ {
+//Console.Error.WriteLine ("!!!! {0} BuildNodeManager.Execute", BuildManager.GetHashCode ());
+ var node = TakeNode (build);
+ ongoing_builds [build] = task_factory.StartNew (node.Execute);
+ }
+
+ // FIXME: take max nodes into account here, and get throttling working.
+ BuildNode TakeNode (BuildSubmission build)
+ {
+ var host = BuildManager.OngoingBuildParameters.HostServices;
+ NodeAffinity affinity;
+ if (host == null)
+ affinity = NodeAffinity.Any;
+ else
+ affinity = host.GetNodeAffinity (build.BuildRequest.ProjectFullPath);
+ BuildNode n = GetReusableNode (affinity);
+ if (n != null)
+ n.Assign (build);
+ else {
+ n = new BuildNode (this, affinity == NodeAffinity.Any ? NodeAffinity.InProc : affinity);
+ n.Assign (build);
+ if (n.Affinity == NodeAffinity.InProc)
+ in_proc_nodes.Add (n);
+ else
+ out_proc_nodes.Add (n);
+ }
+ return n;
+ }
+
+ BuildNode GetReusableNode (NodeAffinity affinity)
+ {
+ if (!BuildManager.OngoingBuildParameters.EnableNodeReuse)
+ return null;
+
+ if (affinity != NodeAffinity.OutOfProc)
+ foreach (var n in in_proc_nodes)
+ if (n.IsAvailable && (n.Affinity & affinity) != 0)
+ return n;
+ if (affinity != NodeAffinity.InProc)
+ foreach (var n in out_proc_nodes)
+ if (n.IsAvailable && (n.Affinity & affinity) != 0)
+ return n;
+ return null;
+ }
+
+ internal class BuildNode
+ {
+ static Random rnd = new Random ();
+
+ public BuildNode (BuildNodeManager manager, NodeAffinity affinity)
+ {
+ Manager = manager;
+ Affinity = affinity;
+ Id = rnd.Next ();
+ }
+
+ public bool IsAvailable { get; private set; }
+ public int Id { get; private set; }
+ public BuildNodeManager Manager { get; set; }
+ public NodeAffinity Affinity { get; private set; }
+ public BuildSubmission Build { get; private set; }
+
+ public void Assign (BuildSubmission build)
+ {
+ IsAvailable = false;
+ Build = build;
+ }
+
+ public void Execute ()
+ {
+ // FIXME: depending on NodeAffinity, build it through another MSBuild process.
+ if (Affinity == NodeAffinity.OutOfProc)
+ throw new NotImplementedException ();
+//Console.Error.WriteLine ("!!!! {0} BuildSubmission.Execute start", Manager.BuildManager.GetHashCode ());
+ Build.Execute ();
+//Console.Error.WriteLine ("!!!! {0} BuildSubmission.Execute done", Manager.BuildManager.GetHashCode ());
+ }
+ }
+ }
+}
List<Assembly> assemblies = new List<Assembly> ();
List<ITaskFactory> task_factories = new List<ITaskFactory> ();
+ public void ResetCaches ()
+ {
+ assemblies.Clear ();
+ task_factories.Clear ();
+ }
+
public ITask GetTask (string name)
{
throw new NotImplementedException ();
Microsoft.Build.Execution/TargetResult.cs
Microsoft.Build.Execution/TargetResultCode.cs
Microsoft.Build.Internal/BuildEngine4.cs
+Microsoft.Build.Internal/BuildNodeManager.cs
Microsoft.Build.Internal/BuildTaskFactory.cs
Microsoft.Build.Internal/CollectionFromEnumerable.cs
Microsoft.Build.Internal/ExpressionConstructs.cs
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using NUnit.Framework;
+using System.Collections.Generic;
+using System.Linq;
namespace MonoTests.Microsoft.Build.Execution
{
var proj = new ProjectInstance (root);
new BuildManager ().PendBuildRequest (new BuildRequestData (proj, new string [0]));
}
+
+ [Test]
+ [ExpectedException (typeof (InvalidOperationException))]
+ public void ResetCachesDuringBuildIsInvalid ()
+ {
+ string project_xml = @"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+ <Target Name='Wait1Sec'>
+ <Exec Command='ping 10.1.1.1 -n 1 -w 1' />
+ </Target>
+</Project>";
+ var xml = XmlReader.Create (new StringReader (project_xml));
+ var root = ProjectRootElement.Create (xml);
+ var proj = new ProjectInstance (root);
+ var bm = new BuildManager ();
+ bm.BeginBuild (new BuildParameters ());
+ var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait5Sec" }));
+ sub.ExecuteAsync (delegate {}, null);
+ try {
+ bm.ResetCaches ();
+ } finally {
+ bm.EndBuild (); // yes, it should work even after invalid ResetCaches call... at least on .NET it does.
+ }
+ }
+
+ [Test]
+ public void BasicManualParallelBuilds ()
+ {
+ string project_xml = @"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
+ <Target Name='Wait1Sec'>
+ <Exec Command='ping 10.1.1.1 -n 1 -w 1' />
+ </Target>
+</Project>";
+ var xml = XmlReader.Create (new StringReader (project_xml));
+ var root = ProjectRootElement.Create (xml);
+ var proj = new ProjectInstance (root);
+ var bm = new BuildManager ();
+ bm.BeginBuild (new BuildParameters ());
+ DateTime waitDone = DateTime.MinValue;
+ DateTime beforeExec = DateTime.Now;
+ var l = new List<BuildSubmission> ();
+ for (int i = 0; i < 10; i++) {
+ var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait5Sec" }));
+ l.Add (sub);
+ sub.ExecuteAsync (delegate { waitDone = DateTime.Now; }, null);
+ }
+ bm.EndBuild ();
+ Assert.IsTrue (l.All (s => s.BuildResult.OverallResult == BuildResultCode.Success), "#1");
+ DateTime endBuildDone = DateTime.Now;
+ Assert.IsTrue (endBuildDone - beforeExec >= TimeSpan.FromSeconds (1), "#2");
+ Assert.IsTrue (endBuildDone > waitDone, "#3");
+ }
}
}
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
+using System.Globalization;
using System.IO;
using System.Linq;
-using System.Xml;
-using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Execution;
using NUnit.Framework;
// They are NOT equal, because ProjectCollection seems to be different.
Assert.AreNotEqual (ProjectCollection.GlobalProjectCollection.Toolsets.First (t => t.ToolsVersion == "2.0"), ts, "#2");
}
+
+ [Test]
+ public void PropertiesDefault ()
+ {
+ var bp = new BuildParameters ();
+ Assert.IsTrue (bp.EnableNodeReuse, "#1");
+ Assert.IsTrue (bp.EnvironmentProperties.Count > 0, "#2");
+ Assert.AreEqual (CultureInfo.CurrentCulture, bp.Culture, "#3");
+ }
}
}
-
{
string project_xml = @"<Project DefaultTargets='Wait1Sec' xmlns='http://schemas.microsoft.com/developer/msbuild/2003'>
<Target Name='Wait1Sec'>
- <Exec Command='ping 10.1.1.1 -n 1 -w 1000' />
+ <Exec Command='ping 10.1.1.1 -n 1 -w 1' />
</Target>
</Project>";
var xml = XmlReader.Create (new StringReader (project_xml));
bm.BeginBuild (new BuildParameters ());
DateTime waitDone = DateTime.MinValue;
DateTime beforeExec = DateTime.Now;
- var l = new List<BuildSubmission> ();
- for (int i = 0; i < 10; i++) {
- var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait5Sec" }));
- l.Add (sub);
- sub.ExecuteAsync (delegate { waitDone = DateTime.Now; }, null);
- }
+ var sub = bm.PendBuildRequest (new BuildRequestData (proj, new string [] { "Wait5Sec" }));
+ sub.ExecuteAsync (delegate { waitDone = DateTime.Now; }, null);
bm.EndBuild ();
- Assert.IsTrue (l.All (s => s.BuildResult.OverallResult == BuildResultCode.Success), "#1");
+ Assert.IsTrue (sub.BuildResult.OverallResult == BuildResultCode.Success, "#1");
DateTime endBuildDone = DateTime.Now;
Assert.IsTrue (endBuildDone - beforeExec >= TimeSpan.FromSeconds (1), "#2");
Assert.IsTrue (endBuildDone > waitDone, "#3");