diff --git a/Directory.Build.props b/Directory.Build.props
index 1683a4c99e3..06f7144e812 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -51,7 +51,7 @@
NU5125: Arcade uses licenseUrl when doing pack, which now causes NU5125 warning. This disables that warning until arcade can switch over.
-->
- $(NoWarn);NU1603;NU5105;NU5125;1701;1702
+ $(NoWarn);NU1603;NU5105;NU5125;1701;1702;VSTHRD002;VSTHRD105;VSTHRD110;VSTHRD200
diff --git a/eng/Packages.props b/eng/Packages.props
index a7844e3b8a1..727964ed0b9 100644
--- a/eng/Packages.props
+++ b/eng/Packages.props
@@ -17,7 +17,7 @@
-
+
@@ -52,6 +52,8 @@
+
+
diff --git a/eng/Signing.props b/eng/Signing.props
index 83946d7c6e5..13912cb79a5 100644
--- a/eng/Signing.props
+++ b/eng/Signing.props
@@ -1,5 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
index 4edef43d503..4acd75a5f5f 100644
--- a/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
+++ b/ref/Microsoft.Build.Tasks.Core/net/Microsoft.Build.Tasks.Core.cs
@@ -940,6 +940,7 @@ public ResolveAssemblyReference() { }
public string TargetFrameworkVersion { get { throw null; } set { } }
public string TargetProcessorArchitecture { get { throw null; } set { } }
public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } }
+ public bool UseResolveAssemblyReferenceService { get { throw null; } set { } }
public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
diff --git a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
index cc3fb5233ef..af96c3bbc19 100644
--- a/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
+++ b/ref/Microsoft.Build.Tasks.Core/netstandard/Microsoft.Build.Tasks.Core.cs
@@ -677,6 +677,7 @@ public ResolveAssemblyReference() { }
public string TargetFrameworkVersion { get { throw null; } set { } }
public string TargetProcessorArchitecture { get { throw null; } set { } }
public bool UnresolveFrameworkAssembliesFromHigherFrameworks { get { throw null; } set { } }
+ public bool UseResolveAssemblyReferenceService { get { throw null; } set { } }
public string WarnOrErrorOnTargetArchitectureMismatch { get { throw null; } set { } }
public override bool Execute() { throw null; }
}
diff --git a/ref/Microsoft.Build/net/Microsoft.Build.cs b/ref/Microsoft.Build/net/Microsoft.Build.cs
index 8daf09c374a..870bbe7458a 100644
--- a/ref/Microsoft.Build/net/Microsoft.Build.cs
+++ b/ref/Microsoft.Build/net/Microsoft.Build.cs
@@ -1383,6 +1383,12 @@ internal ProjectTaskOutputPropertyInstance() { }
public string TaskParameter { get { throw null; } }
public override Microsoft.Build.Construction.ElementLocation TaskParameterLocation { get { throw null; } }
}
+ public sealed partial class RarNode
+ {
+ public RarNode() { }
+ public Microsoft.Build.Execution.NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out System.Exception shutdownException, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken)) { shutdownException = default(System.Exception); throw null; }
+ public Microsoft.Build.Execution.NodeEngineShutdownReason Run(out System.Exception shutdownException) { shutdownException = default(System.Exception); throw null; }
+ }
public partial class RequestedProjectState
{
public RequestedProjectState() { }
diff --git a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs
index 23825be7aba..a47f5848144 100644
--- a/ref/Microsoft.Build/netstandard/Microsoft.Build.cs
+++ b/ref/Microsoft.Build/netstandard/Microsoft.Build.cs
@@ -1377,6 +1377,12 @@ internal ProjectTaskOutputPropertyInstance() { }
public string TaskParameter { get { throw null; } }
public override Microsoft.Build.Construction.ElementLocation TaskParameterLocation { get { throw null; } }
}
+ public sealed partial class RarNode
+ {
+ public RarNode() { }
+ public Microsoft.Build.Execution.NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out System.Exception shutdownException, System.Threading.CancellationToken cancellationToken=default(System.Threading.CancellationToken)) { shutdownException = default(System.Exception); throw null; }
+ public Microsoft.Build.Execution.NodeEngineShutdownReason Run(out System.Exception shutdownException) { shutdownException = default(System.Exception); throw null; }
+ }
public partial class RequestedProjectState
{
public RequestedProjectState() { }
diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs
index 0e59f2e1fd6..a78e25f2103 100644
--- a/src/Build/BackEnd/BuildManager/BuildManager.cs
+++ b/src/Build/BackEnd/BuildManager/BuildManager.cs
@@ -2018,6 +2018,26 @@ private void PerformSchedulingActions(IEnumerable responses)
}
}
+ internal bool CreateRarNode()
+ {
+ // If the _buildParametrs is not set, we are in OutOfProc mode, so continue
+ // Else check if users specified that he want to use multiple nodes, if so use RARaaS
+ if (_buildParameters?.MaxNodeCount == 1)
+ return false;
+
+ string nodeLocation = _buildParameters?.NodeExeLocation ?? BuildEnvironmentHelper.Instance.CurrentMSBuildExePath;
+ if (string.IsNullOrEmpty(nodeLocation))
+ {
+ // Couldn't find a path to MSBuild.exe; can't create a new node.
+ return false;
+ }
+
+ bool nodeReuse = _buildParameters?.EnableNodeReuse ?? true;
+ bool lowPriority = _buildParameters?.LowPriority ?? false;
+ string commandLineArgs = $"/nologo /nodemode:3 /nodeReuse:{nodeReuse} /low:{lowPriority}";
+ return NodeProviderOutOfProcBase.LaunchNode(nodeLocation, commandLineArgs) != -1;
+ }
+
///
/// Completes a submission using the specified overall results.
///
diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs
index 132cb665bb1..221e575ac3d 100644
--- a/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeEndpointOutOfProc.cs
@@ -54,7 +54,11 @@ internal NodeEndpointOutOfProc(
///
protected override Handshake GetHandshake()
{
- return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, is64Bit: EnvironmentUtilities.Is64BitProcess, nodeReuse: _enableReuse, lowPriority: _lowPriority));
+ return new Handshake(CommunicationsUtilities.GetHandshakeOptions(
+ taskHost: false,
+ is64Bit: EnvironmentUtilities.Is64BitProcess,
+ nodeReuse: _enableReuse,
+ lowPriority: _lowPriority));
}
#region Structs
diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs
index 11ea72ba505..50ca0c90a01 100644
--- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProc.cs
@@ -63,10 +63,11 @@ public int AvailableNodes
///
/// Is reuse of build nodes allowed?
/// Is the build running at low priority?
- internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority)
+ /// /Indicates if node can not accept standard MSBuild work
+ internal static Handshake GetHandshake(bool enableNodeReuse, bool enableLowPriority, bool specialNode)
{
CommunicationsUtilities.Trace("MSBUILDNODEHANDSHAKESALT=\"{0}\", msbuildDirectory=\"{1}\", enableNodeReuse={2}, enableLowPriority={3}", Traits.MSBuildNodeHandshakeSalt, BuildEnvironmentHelper.Instance.MSBuildToolsDirectory32, enableNodeReuse, enableLowPriority);
- return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, is64Bit: EnvironmentUtilities.Is64BitProcess));
+ return new Handshake(CommunicationsUtilities.GetHandshakeOptions(taskHost: false, nodeReuse: enableNodeReuse, lowPriority: enableLowPriority, specialNode: specialNode, is64Bit: EnvironmentUtilities.Is64BitProcess));
}
///
diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
index 644d47e931d..8a681683154 100644
--- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
+++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs
@@ -121,13 +121,16 @@ protected void ShutdownAllNodes(bool nodeReuse, NodeContextTerminateDelegate ter
int timeout = 30;
// Attempt to connect to the process with the handshake without low priority.
- Stream nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, false));
+ Stream nodeStream = NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: false));
- if (nodeStream == null)
- {
- // If we couldn't connect attempt to connect to the process with the handshake including low priority.
- nodeStream = TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, true));
- }
+ // If we couldn't connect attempt to connect to the process with the handshake including low priority.
+ nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: false));
+
+ // Attempt to connect to the non-worker process
+ // Attempt to connect to the process with the handshake without low priority.
+ nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: false, specialNode: true));
+ // If we couldn't connect attempt to connect to the process with the handshake including low priority.
+ nodeStream ??= NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, timeout, NodeProviderOutOfProc.GetHandshake(nodeReuse, enableLowPriority: true, specialNode: true));
if (nodeStream != null)
{
@@ -194,7 +197,7 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
_processesToIgnore.Add(nodeLookupKey);
// Attempt to connect to each process in turn.
- Stream nodeStream = TryConnectToProcess(nodeProcess.Id, 0 /* poll, don't wait for connections */, hostHandshake);
+ Stream nodeStream = NamedPipeUtil.TryConnectToProcess(nodeProcess.Id, 0 /* poll, don't wait for connections */, hostHandshake);
if (nodeStream != null)
{
// Connection successful, use this node.
@@ -245,7 +248,7 @@ protected NodeContext GetNode(string msbuildLocation, string commandLineArgs, in
// to the debugger process. Instead, use MSBUILDDEBUGONSTART=1
// Now try to connect to it.
- Stream nodeStream = TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake);
+ Stream nodeStream = NamedPipeUtil.TryConnectToProcess(msbuildProcessId, TimeoutForNewNodeCreation, hostHandshake);
if (nodeStream != null)
{
// Connection successful, use this node.
@@ -293,100 +296,10 @@ private string GetProcessesToIgnoreKey(Handshake hostHandshake, int nodeProcessI
return hostHandshake.ToString() + "|" + nodeProcessId.ToString(CultureInfo.InvariantCulture);
}
-#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
- // This code needs to be in a separate method so that we don't try (and fail) to load the Windows-only APIs when JIT-ing the code
- // on non-Windows operating systems
- private void ValidateRemotePipeSecurityOnWindows(NamedPipeClientStream nodeStream)
- {
- SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
-#if FEATURE_PIPE_SECURITY
- PipeSecurity remoteSecurity = nodeStream.GetAccessControl();
-#else
- var remoteSecurity = new PipeSecurity(nodeStream.SafePipeHandle, System.Security.AccessControl.AccessControlSections.Access |
- System.Security.AccessControl.AccessControlSections.Owner | System.Security.AccessControl.AccessControlSections.Group);
-#endif
- IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
- if (remoteOwner != identifier)
- {
- CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value);
- throw new UnauthorizedAccessException();
- }
- }
-#endif
-
- ///
- /// Attempts to connect to the specified process.
- ///
- private Stream TryConnectToProcess(int nodeProcessId, int timeout, Handshake handshake)
- {
- // Try and connect to the process.
- string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + nodeProcessId);
-
- NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous
-#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY
- | PipeOptions.CurrentUserOnly
-#endif
- );
- CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId, pipeName, timeout);
-
- try
- {
- nodeStream.Connect(timeout);
-
-#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
- if (NativeMethodsShared.IsWindows && !NativeMethodsShared.IsMono)
- {
- // Verify that the owner of the pipe is us. This prevents a security hole where a remote node has
- // been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to
- // us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can
- // only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up
- // remote node could set the owner to something else would also let it change owners on other objects, so
- // this would be a security flaw upstream of us.
- ValidateRemotePipeSecurityOnWindows(nodeStream);
- }
-#endif
-
- int[] handshakeComponents = handshake.RetrieveHandshakeComponents();
- for (int i = 0; i < handshakeComponents.Length; i++)
- {
- CommunicationsUtilities.Trace("Writing handshake part {0} to pipe {1}", i, pipeName);
- nodeStream.WriteIntForHandshake(handshakeComponents[i]);
- }
-
- // This indicates that we have finished all the parts of our handshake; hopefully the endpoint has as well.
- nodeStream.WriteEndOfHandshakeSignal();
-
- CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName);
-
-#if NETCOREAPP2_1 || MONO
- nodeStream.ReadEndOfHandshakeSignal(true, timeout);
-#else
- nodeStream.ReadEndOfHandshakeSignal(true);
-#endif
- // We got a connection.
- CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName);
- return nodeStream;
- }
- catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
- {
- // Can be:
- // UnauthorizedAccessException -- Couldn't connect, might not be a node.
- // IOException -- Couldn't connect, already in use.
- // TimeoutException -- Couldn't connect, might not be a node.
- // InvalidOperationException – Couldn’t connect, probably a different build
- CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd());
-
- // If we don't close any stream, we might hang up the child
- nodeStream?.Dispose();
- }
-
- return null;
- }
-
///
/// Creates a new MSBuild process
///
- private int LaunchNode(string msbuildLocation, string commandLineArgs)
+ internal static int LaunchNode(string msbuildLocation, string commandLineArgs)
{
// Should always have been set already.
ErrorUtilities.VerifyThrowInternalLength(msbuildLocation, nameof(msbuildLocation));
diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
index 3e2f83c917a..074df8881d9 100644
--- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
+++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs
@@ -5,6 +5,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
+using System.IO.Pipes;
#if FEATURE_APPDOMAIN
using System.Runtime.Remoting.Lifetime;
using System.Runtime.Remoting;
@@ -23,6 +24,7 @@
using Microsoft.Build.BackEnd.Components.Caching;
using System.Reflection;
using Microsoft.Build.Eventing;
+using Microsoft.Build.Internal;
namespace Microsoft.Build.BackEnd
{
@@ -34,7 +36,7 @@ internal class TaskHost :
#if FEATURE_APPDOMAIN
MarshalByRefObject,
#endif
- IBuildEngine7
+ IBuildEngine7, IRarBuildEngine
{
///
/// True if the "secret" environment variable MSBUILDNOINPROCNODE is set.
@@ -983,5 +985,34 @@ private void VerifyActiveProxy()
{
ErrorUtilities.VerifyThrow(_activeProxy, "Attempted to use an inactive task host.");
}
+
+ ///
+ /// Initialize new RAR node
+ ///
+ bool IRarBuildEngine.CreateRarNode()
+ {
+ return BuildManager.DefaultBuildManager.CreateRarNode();
+ }
+
+ ///
+ /// Provides RAR node name for current configuration
+ ///
+ string IRarBuildEngine.GetRarPipeName()
+ {
+ BuildParameters parameters = _host.BuildParameters;
+ return CommunicationsUtilities.GetRarPipeName(parameters.EnableNodeReuse, parameters.LowPriority);
+ }
+
+ ///
+ /// Constructs
+ ///
+ NamedPipeClientStream IRarBuildEngine.GetRarClientStream(string pipeName, int timeout)
+ {
+ BuildParameters parameters = _host.BuildParameters;
+ Handshake handshake = NodeProviderOutOfProc.GetHandshake(enableNodeReuse: parameters.EnableNodeReuse,
+ enableLowPriority: parameters.LowPriority, specialNode: true);
+
+ return NamedPipeUtil.TryConnectToProcess(pipeName, timeout, handshake);
+ }
}
}
diff --git a/src/Build/BackEnd/Node/RarNode.cs b/src/Build/BackEnd/Node/RarNode.cs
new file mode 100644
index 00000000000..888c24215d8
--- /dev/null
+++ b/src/Build/BackEnd/Node/RarNode.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.CodeDom;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Pipes;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Build.BackEnd;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Internal;
+using Microsoft.Build.Shared;
+
+namespace Microsoft.Build.Execution
+{
+ public sealed class RarNode : INode
+ {
+ ///
+ /// The amount of time to wait for the client to connect to the host.
+ ///
+ private const int ClientConnectTimeout = 60000;
+
+ ///
+ /// Fully qualified name of RarController, used for providing instance to
+ ///
+ private const string RarControllerName = "Microsoft.Build.Tasks.ResolveAssemblyReferences.Server.RarController, Microsoft.Build.Tasks.Core";
+
+
+ ///
+ /// Timeout for node shutdown
+ ///
+ private static readonly TimeSpan NodeShutdownTimeout = TimeSpan.FromHours(1);
+
+ public NodeEngineShutdownReason Run(bool nodeReuse, bool lowPriority, out Exception shutdownException, CancellationToken cancellationToken = default)
+ {
+ shutdownException = null;
+ using CancellationTokenSource cts = new CancellationTokenSource();
+ string pipeName = CommunicationsUtilities.GetRarPipeName(nodeReuse, lowPriority);
+ Handshake handshake = NodeProviderOutOfProc.GetHandshake(enableNodeReuse: nodeReuse,
+ enableLowPriority: lowPriority, specialNode: true);
+
+ IRarController controller = GetController(pipeName, handshake);
+
+ Task rarTask = controller.StartAsync(cts.Token);
+
+ Task msBuildShutdown = RunShutdownCheckAsync(handshake, cts.Token);
+
+ // Timeout for node, limits lifetime of node to 1 hour
+ cts.CancelAfter(NodeShutdownTimeout);
+ int index;
+ try
+ {
+ // Wait for any of these tasks to finish:
+ // - rarTask can timeout (default is 15 minutes)
+ // - msBuildShutdown ends when it receives command to shutdown
+ // - node lifetime expires
+ index = Task.WaitAny(new Task[] { msBuildShutdown, rarTask }, cts.Token);
+ }
+ catch (OperationCanceledException e)
+ {
+ shutdownException = e;
+ return NodeEngineShutdownReason.Error;
+ }
+
+ cts.Cancel();
+
+ if (index == 0)
+ {
+ // We know that the task completed, so we can get Result without waiting for it.
+ return msBuildShutdown.Result;
+ }
+ else
+ {
+ // RAR task can only exit with connection error or timeout
+ return NodeEngineShutdownReason.ConnectionFailed;
+ }
+ }
+
+ private static IRarController GetController(string pipeName, Handshake handshake)
+ {
+ Type rarControllerType = Type.GetType(RarControllerName);
+
+ Func streamFactory = NamedPipeUtil.CreateNamedPipeServer;
+ Func validateCallback = NamedPipeUtil.ValidateHandshake;
+ IRarController controller = Activator.CreateInstance(rarControllerType, pipeName, handshake, streamFactory, validateCallback, null) as IRarController;
+
+ ErrorUtilities.VerifyThrow(controller != null, ResourceUtilities.GetResourceString("RarControllerReflectionError"), RarControllerName);
+ return controller;
+ }
+
+ public NodeEngineShutdownReason Run(out Exception shutdownException)
+ {
+ return Run(false, false, out shutdownException);
+ }
+
+ private async Task RunShutdownCheckAsync(Handshake handshake, CancellationToken cancellationToken = default)
+ {
+ string pipeName = NamedPipeUtil.GetPipeNameOrPath("MSBuild" + Process.GetCurrentProcess().Id);
+
+ static async Task ReadAsync(Stream stream, byte[] buffer, int bytesToRead)
+ {
+ int totalBytesRead = 0;
+ while (totalBytesRead < bytesToRead)
+ {
+ int bytesRead = await stream.ReadAsync(buffer, totalBytesRead, bytesToRead - totalBytesRead);
+ if (bytesRead == 0)
+ {
+ return totalBytesRead;
+ }
+ totalBytesRead += bytesRead;
+ }
+ return totalBytesRead;
+ }
+
+ // Most common path in this while loop in long run will be over the continue statement.
+ // This is happening because the MSBuild when starting new nodes is trying in some cases to reuse nodes (see nodeReuse switch).
+ // It is done by listing the MSBuild processes and then connecting to them and validating the handshake.
+ // In most cases for this loop it will fail, which will lead to hitting the continue statement.
+ // If we get over that, the MSBuild should send NodeBuildComplete packet, which will indicate that the engine is requesting to shutdown this node.
+ while (true)
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return NodeEngineShutdownReason.BuildComplete;
+
+ using NamedPipeServerStream serverStream = NamedPipeUtil.CreateNamedPipeServer(pipeName, maxNumberOfServerInstances: NamedPipeServerStream.MaxAllowedServerInstances);
+ await serverStream.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);
+
+ bool connected = NamedPipeUtil.ValidateHandshake(handshake, serverStream, ClientConnectTimeout);
+ if (!connected)
+ continue;
+
+ // Header consists of:
+ // 1 byte - Packet type
+ // 4 bytes - packet length
+ byte[] header = new byte[5];
+ int bytesRead = await ReadAsync(serverStream, header, header.Length).ConfigureAwait(false);
+ if (bytesRead != header.Length)
+ {
+ continue;
+ }
+
+ NodePacketType packetType = (NodePacketType)Enum.ToObject(typeof(NodePacketType), header[0]);
+ // Packet type sent by Shutdown
+ if (packetType == NodePacketType.NodeBuildComplete)
+ {
+ // Body of NodeBuildComplete contains only one boolean (= 1 byte)
+ byte[] packetBody = new byte[sizeof(bool)];
+ await serverStream.ReadAsync(packetBody, 0, packetBody.Length, cancellationToken).ConfigureAwait(false);
+ bool nodeReuse = Convert.ToBoolean(packetBody[0]);
+
+ return nodeReuse ? NodeEngineShutdownReason.BuildCompleteReuse : NodeEngineShutdownReason.BuildComplete;
+ }
+ }
+ }
+ }
+}
diff --git a/src/Build/Microsoft.Build.csproj b/src/Build/Microsoft.Build.csproj
index 524046ab620..2f136f3ebf0 100644
--- a/src/Build/Microsoft.Build.csproj
+++ b/src/Build/Microsoft.Build.csproj
@@ -1,4 +1,4 @@
-
+
@@ -157,6 +157,7 @@
+
diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx
index c2708c387c5..55a89a1db36 100644
--- a/src/Build/Resources/Strings.resx
+++ b/src/Build/Resources/Strings.resx
@@ -1834,4 +1834,7 @@ Utilization: {0} Average Utilization: {1:###.0}
"EvaluationContext objects created with SharingPolicy.Isolated do not support being passed an MSBuildFileSystemBase file system."
+
+ Couldn't create an instance of IRarController for '{0}' type
+
diff --git a/src/Build/Resources/xlf/Strings.cs.xlf b/src/Build/Resources/xlf/Strings.cs.xlf
index ee4d13472fd..5991f465065 100644
--- a/src/Build/Resources/xlf/Strings.cs.xlf
+++ b/src/Build/Resources/xlf/Strings.cs.xlf
@@ -178,6 +178,11 @@
Počáteční hodnota vlastnosti: $({0})={1} Zdroj: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: Projekt {0} přeskočil omezení izolace grafu v odkazovaném projektu {1}.
diff --git a/src/Build/Resources/xlf/Strings.de.xlf b/src/Build/Resources/xlf/Strings.de.xlf
index 44b17444d81..662d0f59824 100644
--- a/src/Build/Resources/xlf/Strings.de.xlf
+++ b/src/Build/Resources/xlf/Strings.de.xlf
@@ -178,6 +178,11 @@
Anfangswert der Eigenschaft: $({0})="{1}", Quelle: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: Das Projekt "{0}" hat Graphisolationseinschränkungen für das referenzierte Projekt "{1}" übersprungen.
diff --git a/src/Build/Resources/xlf/Strings.en.xlf b/src/Build/Resources/xlf/Strings.en.xlf
index 6f48da26a49..9558c861f7b 100644
--- a/src/Build/Resources/xlf/Strings.en.xlf
+++ b/src/Build/Resources/xlf/Strings.en.xlf
@@ -178,6 +178,11 @@
Property initial value: $({0})="{1}" Source: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: Project "{0}" skipped graph isolation constraints on referenced project "{1}"
diff --git a/src/Build/Resources/xlf/Strings.es.xlf b/src/Build/Resources/xlf/Strings.es.xlf
index ff6b7df796d..09e2180e0a3 100644
--- a/src/Build/Resources/xlf/Strings.es.xlf
+++ b/src/Build/Resources/xlf/Strings.es.xlf
@@ -178,6 +178,11 @@
Valor inicial de la propiedad: $({0})="{1}" Origen: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: El proyecto "{0}" ha omitido las restricciones de aislamiento de gráficos en el proyecto "{1}" al que se hace referencia.
diff --git a/src/Build/Resources/xlf/Strings.fr.xlf b/src/Build/Resources/xlf/Strings.fr.xlf
index 531f580e15a..f578b5ce53b 100644
--- a/src/Build/Resources/xlf/Strings.fr.xlf
+++ b/src/Build/Resources/xlf/Strings.fr.xlf
@@ -178,6 +178,11 @@
Valeur initiale de la propriété : $({0})="{1}" Source : {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: le projet "{0}" a ignoré les contraintes d'isolement de graphe dans le projet référencé "{1}"
diff --git a/src/Build/Resources/xlf/Strings.it.xlf b/src/Build/Resources/xlf/Strings.it.xlf
index 1a694c9d92c..e583fca423e 100644
--- a/src/Build/Resources/xlf/Strings.it.xlf
+++ b/src/Build/Resources/xlf/Strings.it.xlf
@@ -178,6 +178,11 @@
Valore iniziale della proprietà: $({0})="{1}". Origine: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: il progetto "{0}" ha ignorato i vincoli di isolamento del grafico nel progetto di riferimento "{1}"
diff --git a/src/Build/Resources/xlf/Strings.ja.xlf b/src/Build/Resources/xlf/Strings.ja.xlf
index 9ac0fcd13c4..f25b31b5d56 100644
--- a/src/Build/Resources/xlf/Strings.ja.xlf
+++ b/src/Build/Resources/xlf/Strings.ja.xlf
@@ -178,6 +178,11 @@
プロパティの初期値: $({0})="{1}" ソース: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: プロジェクト "{0}" は、参照先のプロジェクト "{1}" で、グラフの分離制約をスキップしました
diff --git a/src/Build/Resources/xlf/Strings.ko.xlf b/src/Build/Resources/xlf/Strings.ko.xlf
index 6b4bb9305d7..79ffea47246 100644
--- a/src/Build/Resources/xlf/Strings.ko.xlf
+++ b/src/Build/Resources/xlf/Strings.ko.xlf
@@ -178,6 +178,11 @@
속성 초기 값: $({0})="{1}" 소스: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: 프로젝트 "{0}"에서 참조된 프로젝트 "{1}"의 그래프 격리 제약 조건을 건너뛰었습니다.
diff --git a/src/Build/Resources/xlf/Strings.pl.xlf b/src/Build/Resources/xlf/Strings.pl.xlf
index afb3ebf2e36..107873c75b5 100644
--- a/src/Build/Resources/xlf/Strings.pl.xlf
+++ b/src/Build/Resources/xlf/Strings.pl.xlf
@@ -178,6 +178,11 @@
Wartość początkowa właściwości: $({0})=„{1}” Źródło: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: W przypadku projektu „{0}” pominięto ograniczenia izolacji grafu dla przywoływanego projektu „{1}”
diff --git a/src/Build/Resources/xlf/Strings.pt-BR.xlf b/src/Build/Resources/xlf/Strings.pt-BR.xlf
index 0af53bc16a2..dcd153df703 100644
--- a/src/Build/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Build/Resources/xlf/Strings.pt-BR.xlf
@@ -178,6 +178,11 @@
Valor inicial da propriedade: $({0})="{1}" Origem: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: o projeto "{0}" ignorou as restrições de isolamento do gráfico no projeto referenciado "{1}"
diff --git a/src/Build/Resources/xlf/Strings.ru.xlf b/src/Build/Resources/xlf/Strings.ru.xlf
index 1fd04905eb0..eeb54ff9174 100644
--- a/src/Build/Resources/xlf/Strings.ru.xlf
+++ b/src/Build/Resources/xlf/Strings.ru.xlf
@@ -178,6 +178,11 @@
Начальное значение свойства: $({0})="{1}" Источник: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: проект "{0}" пропустил ограничения изоляции графа в проекте "{1}", на который указывает ссылка.
diff --git a/src/Build/Resources/xlf/Strings.tr.xlf b/src/Build/Resources/xlf/Strings.tr.xlf
index d65c72420e7..9854cc7ddfa 100644
--- a/src/Build/Resources/xlf/Strings.tr.xlf
+++ b/src/Build/Resources/xlf/Strings.tr.xlf
@@ -178,6 +178,11 @@
Özellik başlangıç değeri: $({0})="{1}" Kaynak: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: "{0}" projesi, başvurulan "{1}" projesindeki graf yalıtımı kısıtlamalarını atladı
diff --git a/src/Build/Resources/xlf/Strings.zh-Hans.xlf b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
index be7e4e06f42..e2ca02b06c7 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hans.xlf
@@ -178,6 +178,11 @@
属性初始值: $({0})=“{1}”,源: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: 项目“{0}”已跳过所引用的项目“{1}”上的图形隔离约束
diff --git a/src/Build/Resources/xlf/Strings.zh-Hant.xlf b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
index eb53e039bba..15c7df1bd3c 100644
--- a/src/Build/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Build/Resources/xlf/Strings.zh-Hant.xlf
@@ -178,6 +178,11 @@
屬性初始值: $({0})="{1}" 來源: {2}
+
+
+ Couldn't create an instance of IRarController for '{0}' type
+
+ MSB4260: 專案 "{0}" 已跳過參考專案 "{1}" 上的圖形隔離條件約束
diff --git a/src/Framework/IRarBuildEngine.cs b/src/Framework/IRarBuildEngine.cs
new file mode 100644
index 00000000000..b5178887550
--- /dev/null
+++ b/src/Framework/IRarBuildEngine.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO.Pipes;
+
+namespace Microsoft.Build.Framework
+{
+ ///
+ /// This interface provides necessary functionality from to RAR as a service functionality.
+ ///
+ internal interface IRarBuildEngine
+ {
+ ///
+ /// Inialize new RAR node
+ ///
+ internal bool CreateRarNode();
+
+ ///
+ /// Provides RAR node name for current configuration
+ ///
+ internal string GetRarPipeName();
+
+ ///
+ /// Constructs
+ ///
+ internal NamedPipeClientStream GetRarClientStream(string pipeName, int timeout);
+ }
+}
diff --git a/src/Framework/IRarController.cs b/src/Framework/IRarController.cs
new file mode 100644
index 00000000000..639daafb75a
--- /dev/null
+++ b/src/Framework/IRarController.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.IO.Pipes;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Microsoft.Build.Framework
+{
+ ///
+ /// API for controller of ResolveAssemblyReference node
+ ///
+ internal interface IRarController
+ {
+ Task StartAsync(CancellationToken token);
+ }
+}
diff --git a/src/Framework/MSBuildEventSource.cs b/src/Framework/MSBuildEventSource.cs
index 5d73b6aa153..ae45efb429e 100644
--- a/src/Framework/MSBuildEventSource.cs
+++ b/src/Framework/MSBuildEventSource.cs
@@ -411,6 +411,18 @@ public void PacketReadSize(int size)
{
WriteEvent(55, size);
}
+
+ [Event(56)]
+ public void ResolveAssemblyReferenceNodeConnectStart()
+ {
+ WriteEvent(56);
+ }
+
+ [Event(57)]
+ public void ResolveAssemblyReferenceNodeConnectStop()
+ {
+ WriteEvent(57);
+ }
#endregion
}
}
diff --git a/src/MSBuild/MSBuild.csproj b/src/MSBuild/MSBuild.csproj
index 3bf42162617..74b1c3e0d35 100644
--- a/src/MSBuild/MSBuild.csproj
+++ b/src/MSBuild/MSBuild.csproj
@@ -141,6 +141,7 @@
+ true
diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs
index c7220881a5c..0640d2d95e2 100644
--- a/src/MSBuild/XMake.cs
+++ b/src/MSBuild/XMake.cs
@@ -2642,6 +2642,16 @@ private static void StartLocalNode(CommandLineSwitches commandLineSwitches)
OutOfProcTaskHostNode node = new OutOfProcTaskHostNode();
shutdownReason = node.Run(out nodeException);
}
+ else if (nodeModeNumber == 3)
+ {
+ var node = new RarNode();
+ // If FEATURE_NODE_REUSE is OFF, just validates that the switch is OK, and always returns False
+ bool nodeReuse = ProcessNodeReuseSwitch(commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.NodeReuse]);
+ string[] lowPriorityInput = commandLineSwitches[CommandLineSwitches.ParameterizedSwitch.LowPriority];
+ bool lowPriority = lowPriorityInput.Length > 0 && string.Equals(lowPriorityInput[0], bool.TrueString, StringComparison.OrdinalIgnoreCase);
+
+ shutdownReason = node.Run(nodeReuse, lowPriority, out nodeException, s_buildCancellationSource.Token);
+ }
else
{
CommandLineSwitchException.Throw("InvalidNodeNumberValue", nodeModeNumber.ToString());
diff --git a/src/MSBuildTaskHost/MSBuildTaskHost.csproj b/src/MSBuildTaskHost/MSBuildTaskHost.csproj
index 615488880be..577b7b4c1d4 100644
--- a/src/MSBuildTaskHost/MSBuildTaskHost.csproj
+++ b/src/MSBuildTaskHost/MSBuildTaskHost.csproj
@@ -61,6 +61,7 @@
CopyOnWriteDictionary.cs
+
ErrorUtilities.cs
diff --git a/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec b/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec
index f2132af28e6..6e3cd15181e 100644
--- a/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec
+++ b/src/Package/MSBuild.Engine.Corext/MsBuild.Engine.Corext.nuspec
@@ -40,6 +40,13 @@
+
+
+
+
+
+
+
@@ -94,6 +101,13 @@
+
+
+
+
+
+
+
diff --git a/src/Package/MSBuild.VSSetup/files.swr b/src/Package/MSBuild.VSSetup/files.swr
index 44277b783c7..d813b8deb3d 100644
--- a/src/Package/MSBuild.VSSetup/files.swr
+++ b/src/Package/MSBuild.VSSetup/files.swr
@@ -48,6 +48,13 @@ folder InstallDir:\MSBuild\Current\Bin
file source=$(X86BinPath)System.Runtime.CompilerServices.Unsafe.dll vs.file.ngenArchitecture=all
file source=$(X86BinPath)System.Threading.Tasks.Dataflow.dll vs.file.ngenArchitecture=all vs.file.ngenPriority=1
file source=$(X86BinPath)System.Collections.Immutable.dll vs.file.ngenArchitecture=all vs.file.ngenPriority=1
+ file source=$(X86BinPath)StreamJsonRpc.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Nerdbank.Streams.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)System.IO.Pipelines.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Microsoft.VisualStudio.Threading.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Microsoft.VisualStudio.Validation.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)MessagePack.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)MessagePack.Annotations.dll vs.file.ngenArchitecture=all
file source=$(X86BinPath)Microsoft.Common.CurrentVersion.targets
file source=$(X86BinPath)Microsoft.Common.CrossTargeting.targets
file source=$(X86BinPath)Microsoft.Common.overridetasks
@@ -197,6 +204,13 @@ folder InstallDir:\MSBuild\Current\Bin\amd64
file source=$(X86BinPath)System.Runtime.CompilerServices.Unsafe.dll vs.file.ngenArchitecture=all
file source=$(X86BinPath)System.Threading.Tasks.Dataflow.dll vs.file.ngenArchitecture=all
file source=$(X86BinPath)System.Collections.Immutable.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)StreamJsonRpc.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Nerdbank.Streams.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)System.IO.Pipelines.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Microsoft.VisualStudio.Threading.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)Microsoft.VisualStudio.Validation.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)MessagePack.dll vs.file.ngenArchitecture=all
+ file source=$(X86BinPath)MessagePack.Annotations.dll vs.file.ngenArchitecture=all
file source=$(X86BinPath)Microsoft.Common.CurrentVersion.targets
file source=$(X86BinPath)Microsoft.Common.CrossTargeting.targets
file source=$(X86BinPath)Microsoft.Common.overridetasks
diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs
index cd238bd5540..6ef1da20e51 100644
--- a/src/Shared/CommunicationsUtilities.cs
+++ b/src/Shared/CommunicationsUtilities.cs
@@ -7,12 +7,12 @@
using System.Globalization;
using System.IO;
using System.IO.Pipes;
+using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Threading;
using Microsoft.Build.Shared;
-using System.Reflection;
#if !FEATURE_APM
using System.Threading.Tasks;
@@ -56,7 +56,12 @@ internal enum HandshakeOptions
///
/// Building with administrator privileges
///
- Administrator = 32
+ Administrator = 32,
+
+ ///
+ /// Is a special node. Special nodes cannot accept normal build requests.
+ ///
+ Special = 64
}
internal readonly struct Handshake
@@ -154,6 +159,22 @@ static internal int NodeConnectionTimeout
get { return GetIntegerVariableOrDefault("MSBUILDNODECONNECTIONTIMEOUT", DefaultNodeConnectionTimeout); }
}
+ private static int? _clrVersion = null;
+ ///
+ /// Provides cached value of current CLR version
+ ///
+ private static int ClrVersion
+ {
+ get
+ {
+ if (!_clrVersion.HasValue)
+ {
+ _clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major;
+ }
+ return _clrVersion.Value;
+ }
+ }
+
///
/// Get environment block
///
@@ -459,7 +480,7 @@ internal static async Task ReadAsync(Stream stream, byte[] buffer, int byte
///
/// Given the appropriate information, return the equivalent HandshakeOptions.
///
- internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit = false, bool nodeReuse = false, bool lowPriority = false, IDictionary taskHostParameters = null)
+ internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit = false, bool nodeReuse = false, bool lowPriority = false, bool specialNode = false, IDictionary taskHostParameters = null)
{
HandshakeOptions context = taskHost ? HandshakeOptions.TaskHost : HandshakeOptions.None;
@@ -471,7 +492,7 @@ internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit
// Take the current TaskHost context
if (taskHostParameters == null)
{
- clrVersion = typeof(bool).GetTypeInfo().Assembly.GetName().Version.Major;
+ clrVersion = ClrVersion;
is64Bit = XMakeAttributes.GetCurrentMSBuildArchitecture().Equals(XMakeAttributes.MSBuildArchitectureValues.x64);
}
else
@@ -510,6 +531,10 @@ internal static HandshakeOptions GetHandshakeOptions(bool taskHost, bool is64Bit
context |= HandshakeOptions.Administrator;
}
#endif
+ if (specialNode)
+ {
+ context |= HandshakeOptions.Special;
+ }
return context;
}
@@ -628,5 +653,12 @@ internal static int AvoidEndOfHandshakeSignal(int x)
{
return x == EndOfHandshakeSignal ? ~x : x;
}
+
+ internal static string GetRarPipeName(bool nodeReuse, bool lowPriority)
+ {
+ var context = GetHandshakeOptions(taskHost: true, nodeReuse: nodeReuse, lowPriority: lowPriority, specialNode: true);
+ var userName = $"{Environment.UserDomainName}.{Environment.UserName}";
+ return $"MSBuild.RAR.{userName}.{(int)context}";
+ }
}
}
diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs
index a7f8913e7d7..6daf5d24058 100644
--- a/src/Shared/NamedPipeUtil.cs
+++ b/src/Shared/NamedPipeUtil.cs
@@ -3,11 +3,21 @@
using System;
using System.IO;
+using System.IO.Pipes;
+using System.Security.AccessControl;
+using System.Security.Principal;
+using System.Threading;
+using Microsoft.Build.Internal;
namespace Microsoft.Build.Shared
{
internal static class NamedPipeUtil
{
+ ///
+ /// The size of the buffers to use for named pipes
+ ///
+ private const int PipeBufferSize = 131072;
+
internal static string GetPipeNameOrPath(string pipeName)
{
if (NativeMethodsShared.IsUnixLike)
@@ -25,5 +35,242 @@ internal static string GetPipeNameOrPath(string pipeName)
return pipeName;
}
}
+
+#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
+ // This code needs to be in a separate method so that we don't try (and fail) to load the Windows-only APIs when JIT-ing the code
+ // on non-Windows operating systems
+ private static void ValidateRemotePipeSecurityOnWindows(NamedPipeClientStream nodeStream)
+ {
+ SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
+#if FEATURE_PIPE_SECURITY
+ PipeSecurity remoteSecurity = nodeStream.GetAccessControl();
+#else
+ var remoteSecurity = new PipeSecurity(nodeStream.SafePipeHandle, System.Security.AccessControl.AccessControlSections.Access |
+ System.Security.AccessControl.AccessControlSections.Owner | System.Security.AccessControl.AccessControlSections.Group);
+#endif
+ IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier));
+ if (remoteOwner != identifier)
+ {
+ CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value);
+ throw new UnauthorizedAccessException();
+ }
+ }
+#endif
+
+ ///
+ /// Attempts to connect to the specified process.
+ ///
+ internal static NamedPipeClientStream TryConnectToProcess(int nodeProcessId, int timeout, Handshake handshake)
+ {
+ string pipeName = GetPipeNameOrPath("MSBuild" + nodeProcessId);
+ return TryConnectToProcess(pipeName, nodeProcessId, timeout, handshake);
+ }
+
+ ///
+ /// Attempts to connect to the specified process.
+ ///
+ internal static NamedPipeClientStream TryConnectToProcess(string pipeName, int timeout, Handshake handshake)
+ {
+ return TryConnectToProcess(pipeName, null, timeout, handshake);
+ }
+
+ private static NamedPipeClientStream TryConnectToProcess(string pipeName, int? nodeProcessId, int timeout, Handshake handshake)
+ {
+ NamedPipeClientStream nodeStream = new NamedPipeClientStream(".", pipeName, PipeDirection.InOut, PipeOptions.Asynchronous
+#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY
+ | PipeOptions.CurrentUserOnly
+#endif
+ );
+ CommunicationsUtilities.Trace("Attempting connect to PID {0} with pipe {1} with timeout {2} ms", nodeProcessId.HasValue ? nodeProcessId.Value.ToString() : pipeName, pipeName, timeout);
+
+ try
+ {
+ nodeStream.Connect(timeout);
+
+#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY
+ if (NativeMethodsShared.IsWindows && !NativeMethodsShared.IsMono)
+ {
+ // Verify that the owner of the pipe is us. This prevents a security hole where a remote node has
+ // been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to
+ // us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can
+ // only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up
+ // remote node could set the owner to something else would also let it change owners on other objects, so
+ // this would be a security flaw upstream of us.
+ ValidateRemotePipeSecurityOnWindows(nodeStream);
+ }
+#endif
+
+ int[] handshakeComponents = handshake.RetrieveHandshakeComponents();
+ for (int i = 0; i < handshakeComponents.Length; i++)
+ {
+ CommunicationsUtilities.Trace("Writing handshake part {0} to pipe {1}", i, pipeName);
+ nodeStream.WriteIntForHandshake(handshakeComponents[i]);
+ }
+
+ // This indicates that we have finished all the parts of our handshake; hopefully the endpoint has as well.
+ nodeStream.WriteEndOfHandshakeSignal();
+
+ CommunicationsUtilities.Trace("Reading handshake from pipe {0}", pipeName);
+
+#if NETCOREAPP2_1 || MONO
+ nodeStream.ReadEndOfHandshakeSignal(true, timeout);
+#else
+ nodeStream.ReadEndOfHandshakeSignal(true);
+#endif
+
+ // We got a connection.
+ CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", pipeName);
+ return nodeStream;
+ }
+ catch (Exception e) when (!ExceptionHandling.IsCriticalException(e))
+ {
+ // Can be:
+ // UnauthorizedAccessException -- Couldn't connect, might not be a node.
+ // IOException -- Couldn't connect, already in use.
+ // TimeoutException -- Couldn't connect, might not be a node.
+ // InvalidOperationException – Couldn’t connect, probably a different build
+ CommunicationsUtilities.Trace("Failed to connect to pipe {0}. {1}", pipeName, e.Message.TrimEnd());
+
+ // If we don't close any stream, we might hang up the child
+ nodeStream?.Dispose();
+ }
+
+ return null;
+ }
+
+ internal static bool ValidateHandshake(Handshake handshake, NamedPipeServerStream serverStream, int clientConnectTimeout)
+ {
+ // The handshake protocol is a series of int exchanges. The host sends us a each component, and we
+ // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind.
+ // Once the handshake is complete, both sides can be assured the other is ready to accept data.
+
+ bool gotValidConnection = true;
+ try
+ {
+ int[] handshakeComponents = handshake.RetrieveHandshakeComponents();
+ for (int i = 0; i < handshakeComponents.Length; i++)
+ {
+ int handshakePart = serverStream.ReadIntForHandshake(i == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */
+#if NETCOREAPP2_1 || MONO
+ , clientConnectTimeout /* wait a long time for the handshake from this side */
+#endif
+ );
+
+ if (handshakePart != handshakeComponents[i])
+ {
+ CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, handshakeComponents[i]);
+ serverStream.WriteIntForHandshake(i + 1);
+ gotValidConnection = false;
+ break;
+ }
+ }
+
+ if (gotValidConnection)
+ {
+ // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS.
+#if NETCOREAPP2_1 || MONO
+ serverStream.ReadEndOfHandshakeSignal(false, clientConnectTimeout); /* wait a long time for the handshake from this side */
+#else
+ serverStream.ReadEndOfHandshakeSignal(false);
+#endif
+ CommunicationsUtilities.Trace("Successfully connected to parent.");
+ serverStream.WriteEndOfHandshakeSignal();
+
+#if FEATURE_SECURITY_PERMISSIONS
+ // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they
+ // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the
+ // user we were started by.
+ WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
+ WindowsIdentity clientIdentity = null;
+ serverStream.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); });
+
+ if (clientIdentity == null || !string.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name);
+ return false;
+ }
+#endif
+ }
+ }
+ catch (IOException e)
+ {
+ // We will get here when:
+ // 1. The host (OOP main node) connects to us, it immediately checks for user privileges
+ // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake
+ // 2. The host is too old sending us bits we automatically reject in the handshake
+ // 3. We expected to read the EndOfHandshake signal, but we received something else
+ CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message);
+
+ gotValidConnection = false;
+ }
+ catch (InvalidOperationException)
+ {
+ gotValidConnection = false;
+ }
+
+ if (!gotValidConnection)
+ {
+ if (serverStream.IsConnected)
+ {
+ serverStream.Disconnect();
+ }
+ return false;
+ }
+
+ return true;
+ }
+
+ internal static NamedPipeServerStream CreateNamedPipeServer(string pipeName, int? inputBufferSize = null, int? outputBufferSize = null, int maxNumberOfServerInstances = 1, bool allowNewInstances = false)
+ {
+ inputBufferSize ??= PipeBufferSize;
+ outputBufferSize ??= PipeBufferSize;
+
+#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR
+ if (!NativeMethodsShared.IsMono)
+ {
+ SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
+ PipeSecurity security = new PipeSecurity();
+
+ // Restrict access to just this account. We set the owner specifically here, and on the
+ // pipe client side they will check the owner against this one - they must have identical
+ // SIDs or the client will reject this server. This is used to avoid attacks where a
+ // hacked server creates a less restricted pipe in an attempt to lure us into using it and
+ // then sending build requests to the real pipe client (which is the MSBuild Build Manager.)
+
+ PipeAccessRights rights = PipeAccessRights.ReadWrite;
+ if (allowNewInstances)
+ {
+ rights |= PipeAccessRights.CreateNewInstance;
+ }
+
+ PipeAccessRule rule = new PipeAccessRule(identifier, rights, AccessControlType.Allow);
+ security.AddAccessRule(rule);
+ security.SetOwner(identifier);
+
+ return new NamedPipeServerStream
+ (
+ pipeName,
+ PipeDirection.InOut,
+ maxNumberOfServerInstances, // Only allow one connection at a time.
+ PipeTransmissionMode.Byte,
+ PipeOptions.Asynchronous | PipeOptions.WriteThrough,
+ inputBufferSize.Value, // Default input buffer
+ outputBufferSize.Value, // Default output buffer
+ security,
+ HandleInheritability.None
+ );
+ }
+#endif
+ return new NamedPipeServerStream
+ (
+ pipeName,
+ PipeDirection.InOut,
+ maxNumberOfServerInstances, // Only allow one connection at a time.
+ PipeTransmissionMode.Byte,
+ PipeOptions.Asynchronous | PipeOptions.WriteThrough,
+ inputBufferSize.Value, // Default input buffer
+ outputBufferSize.Value // Default output buffer
+ );
+ }
}
}
diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs
index d634e02563c..9d75da4dbab 100644
--- a/src/Shared/NodeEndpointOutOfProcBase.cs
+++ b/src/Shared/NodeEndpointOutOfProcBase.cs
@@ -31,13 +31,10 @@ namespace Microsoft.Build.BackEnd
internal abstract class NodeEndpointOutOfProcBase : INodeEndpoint
{
#region Private Data
-
-#if NETCOREAPP2_1 || MONO
///
/// The amount of time to wait for the client to connect to the host.
///
private const int ClientConnectTimeout = 60000;
-#endif // NETCOREAPP2_1 || MONO
///
/// The size of the buffers to use for named pipes
@@ -189,49 +186,7 @@ internal void InternalConstruct(string pipeName)
_status = LinkStatus.Inactive;
_asyncDataMonitor = new object();
_sharedReadBuffer = InterningBinaryReader.CreateSharedBuffer();
-
-#if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR
- if (!NativeMethodsShared.IsMono)
- {
- SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner;
- PipeSecurity security = new PipeSecurity();
-
- // Restrict access to just this account. We set the owner specifically here, and on the
- // pipe client side they will check the owner against this one - they must have identical
- // SIDs or the client will reject this server. This is used to avoid attacks where a
- // hacked server creates a less restricted pipe in an attempt to lure us into using it and
- // then sending build requests to the real pipe client (which is the MSBuild Build Manager.)
- PipeAccessRule rule = new PipeAccessRule(identifier, PipeAccessRights.ReadWrite, AccessControlType.Allow);
- security.AddAccessRule(rule);
- security.SetOwner(identifier);
-
- _pipeServer = new NamedPipeServerStream
- (
- pipeName,
- PipeDirection.InOut,
- 1, // Only allow one connection at a time.
- PipeTransmissionMode.Byte,
- PipeOptions.Asynchronous | PipeOptions.WriteThrough,
- PipeBufferSize, // Default input buffer
- PipeBufferSize, // Default output buffer
- security,
- HandleInheritability.None
- );
- }
- else
-#endif
- {
- _pipeServer = new NamedPipeServerStream
- (
- pipeName,
- PipeDirection.InOut,
- 1, // Only allow one connection at a time.
- PipeTransmissionMode.Byte,
- PipeOptions.Asynchronous | PipeOptions.WriteThrough,
- PipeBufferSize, // Default input buffer
- PipeBufferSize // Default output buffer
- );
- }
+ _pipeServer = NamedPipeUtil.CreateNamedPipeServer(pipeName, PipeBufferSize, PipeBufferSize);
}
#endregion
@@ -364,81 +319,12 @@ private void PacketPumpProc()
localPipeServer.EndWaitForConnection(resultForConnection);
#endif
- // The handshake protocol is a series of int exchanges. The host sends us a each component, and we
- // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind.
- // Once the handshake is complete, both sides can be assured the other is ready to accept data.
- Handshake handshake = GetHandshake();
- try
- {
- int[] handshakeComponents = handshake.RetrieveHandshakeComponents();
- for (int i = 0; i < handshakeComponents.Length; i++)
- {
- int handshakePart = _pipeServer.ReadIntForHandshake(i == 0 ? (byte?)CommunicationsUtilities.handshakeVersion : null /* this will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard */
-#if NETCOREAPP2_1 || MONO
- , ClientConnectTimeout /* wait a long time for the handshake from this side */
-#endif
- );
-
- if (handshakePart != handshakeComponents[i])
- {
- CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, handshakeComponents[i]);
- _pipeServer.WriteIntForHandshake(i + 1);
- gotValidConnection = false;
- break;
- }
- }
-
- if (gotValidConnection)
- {
- // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS.
-#if NETCOREAPP2_1 || MONO
- _pipeServer.ReadEndOfHandshakeSignal(false, ClientConnectTimeout); /* wait a long time for the handshake from this side */
-#else
- _pipeServer.ReadEndOfHandshakeSignal(false);
-#endif
- CommunicationsUtilities.Trace("Successfully connected to parent.");
- _pipeServer.WriteEndOfHandshakeSignal();
-
-#if FEATURE_SECURITY_PERMISSIONS
- // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they
- // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the
- // user we were started by.
- WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent();
- WindowsIdentity clientIdentity = null;
- localPipeServer.RunAsClient(delegate () { clientIdentity = WindowsIdentity.GetCurrent(true); });
-
- if (clientIdentity == null || !String.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase))
- {
- CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name);
- gotValidConnection = false;
- continue;
- }
-#endif
- }
- }
- catch (IOException e)
- {
- // We will get here when:
- // 1. The host (OOP main node) connects to us, it immediately checks for user privileges
- // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake
- // 2. The host is too old sending us bits we automatically reject in the handshake
- // 3. We expected to read the EndOfHandshake signal, but we received something else
- CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message);
-
- gotValidConnection = false;
- }
- catch (InvalidOperationException)
- {
- gotValidConnection = false;
- }
+ Handshake handshake = GetHandshake();
+ gotValidConnection = NamedPipeUtil.ValidateHandshake(handshake, _pipeServer, ClientConnectTimeout);
if (!gotValidConnection)
{
- if (localPipeServer.IsConnected)
- {
- localPipeServer.Disconnect();
- }
- continue;
+ return;
}
ChangeLinkStatus(LinkStatus.Active);
diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
index 029dc8ba6d2..0f88509dcc1 100644
--- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
+++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs
@@ -15,6 +15,7 @@
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Microsoft.Build.Tasks.AssemblyDependency;
+using Microsoft.Build.Tasks.ResolveAssemblyReferences.Client;
using Microsoft.Build.Utilities;
using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName;
@@ -203,6 +204,11 @@ public bool IgnoreTargetFrameworkAttributeVersionMismatch
}
}
+ ///
+ /// Indicates if ResolveAssemblyReference task should be run in its own node or not.
+ ///
+ public bool UseResolveAssemblyReferenceService { get; set; }
+
///
/// Force dependencies to be walked even when a reference is marked with ExternallyResolved=true
/// metadata.
@@ -2973,6 +2979,37 @@ private string GetAssemblyPathInGac(AssemblyNameExtension assemblyName, SystemPr
/// True if there was success.
public override bool Execute()
{
+ if (UseResolveAssemblyReferenceService && BuildEngine is IRarBuildEngine rarBuildEngine)
+ {
+ using var client = new RarClient(rarBuildEngine);
+
+ var connected = client.Connect();
+ if (!connected)
+ {
+ Log.LogMessageFromResources(MessageImportance.Low, "RarCouldntConnect");
+ bool nodeCreated = false;
+ try
+ {
+ nodeCreated = client.CreateNode();
+ }
+ catch (Exception e)
+ {
+ Log.LogWarningFromException(e);
+ }
+
+ if (nodeCreated)
+ {
+ connected = client.Connect(5000);
+ }
+ }
+
+ if (connected)
+ {
+ // Client is connected to the RAR node, we can execute RAR task remotely
+ // return client.Execute(); // TODO: Let it do something.
+ }
+ }
+
return Execute
(
new FileExists(p => FileUtilities.FileExistsNoThrow(p)),
diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj
index b194c847366..68ef82202f4 100644
--- a/src/Tasks/Microsoft.Build.Tasks.csproj
+++ b/src/Tasks/Microsoft.Build.Tasks.csproj
@@ -215,6 +215,12 @@
true
+
+ true
+
+
+ true
+ true
@@ -342,6 +348,12 @@
true
+
+
+
+
+
+
@@ -982,6 +994,8 @@
+
+
@@ -1005,6 +1019,7 @@
+
diff --git a/src/Tasks/Microsoft.Common.CurrentVersion.targets b/src/Tasks/Microsoft.Common.CurrentVersion.targets
index f77486801a8..1b0aa167c3c 100644
--- a/src/Tasks/Microsoft.Common.CurrentVersion.targets
+++ b/src/Tasks/Microsoft.Common.CurrentVersion.targets
@@ -2158,6 +2158,7 @@ Copyright (C) Microsoft Corporation. All rights reserved.
IgnoreTargetFrameworkAttributeVersionMismatch ="$(ResolveAssemblyReferenceIgnoreTargetFrameworkAttributeVersionMismatch)"
FindDependenciesOfExternallyResolvedReferences="$(FindDependenciesOfExternallyResolvedReferences)"
ContinueOnError="$(ContinueOnError)"
+ UseResolveAssemblyReferenceService="$(UseResolveAssemblyReferenceService)"
Condition="'@(Reference)'!='' or '@(_ResolvedProjectReferencePaths)'!='' or '@(_ExplicitReference)' != ''"
>
diff --git a/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs b/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs
new file mode 100644
index 00000000000..f35dc0f61b4
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/Client/RarClient.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO.Pipes;
+using Microsoft.Build.Eventing;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Shared;
+using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract;
+using StreamJsonRpc;
+
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Client
+{
+ internal sealed class RarClient : IDisposable
+ {
+ ///
+ /// Default connection timeout for connection to the pipe. Timeout is in millisecond.
+ ///
+ private const int DefaultConnectionTimeout = 300;
+ private readonly IRarBuildEngine _rarBuildEngine;
+ private NamedPipeClientStream _clientStream;
+
+ public RarClient(IRarBuildEngine rarBuildEngine)
+ {
+ _rarBuildEngine = rarBuildEngine;
+ }
+
+ internal bool Connect() => Connect(DefaultConnectionTimeout);
+
+ internal bool Connect(int timeout)
+ {
+ if (_clientStream != null)
+ return true;
+
+ string pipeName = _rarBuildEngine.GetRarPipeName();
+
+ MSBuildEventSource.Log.ResolveAssemblyReferenceNodeConnectStart();
+ NamedPipeClientStream stream = _rarBuildEngine.GetRarClientStream(pipeName, timeout);
+ MSBuildEventSource.Log.ResolveAssemblyReferenceNodeConnectStop();
+
+ if (stream == null)
+ return false; // We couldn't connect
+
+ _clientStream = stream;
+ return true;
+ }
+
+ internal bool CreateNode()
+ {
+ return _rarBuildEngine.CreateRarNode();
+ }
+
+ internal object Execute()
+ {
+ throw new NotImplementedException();
+ //using IResolveAssemblyReferenceTaskHandler client = GetRpcClient();
+
+ // TODO: Find out if there is any possibility of awaiting it.
+ //return client.GetNumber(parameter).GetAwaiter().GetResult();
+ }
+
+ private IResolveAssemblyReferenceTaskHandler GetRpcClient()
+ {
+ ErrorUtilities.VerifyThrowInternalErrorUnreachable(_clientStream != null);
+
+ IJsonRpcMessageHandler handler = RpcUtils.GetRarMessageHandler(_clientStream);
+ return JsonRpc.Attach(handler);
+ }
+
+ public void Dispose()
+ {
+ _clientStream?.Dispose();
+ }
+ }
+}
diff --git a/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs b/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs
new file mode 100644
index 00000000000..0b6387394f1
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/Contract/IResolveAssemblyReferenceTaskHandler.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract
+{
+ internal interface IResolveAssemblyReferenceTaskHandler : IDisposable
+ {
+ }
+}
diff --git a/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs b/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs
new file mode 100644
index 00000000000..e8d2d17e995
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/RpcUtils.cs
@@ -0,0 +1,19 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.IO;
+
+using Nerdbank.Streams;
+using StreamJsonRpc;
+
+#nullable enable
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences
+{
+ internal static class RpcUtils
+ {
+ internal static IJsonRpcMessageHandler GetRarMessageHandler(Stream stream)
+ {
+ return new LengthHeaderMessageHandler(stream.UsePipe(), new MessagePackFormatter());
+ }
+ }
+}
diff --git a/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs b/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs
new file mode 100644
index 00000000000..2852a7d620f
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/Server/RarController.cs
@@ -0,0 +1,171 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.IO;
+using System.IO.Pipes;
+using System.Threading;
+using System.Threading.Tasks;
+
+using Microsoft.Build.Framework;
+using Microsoft.Build.Internal;
+using Microsoft.Build.Shared;
+using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract;
+using Microsoft.Build.Tasks.ResolveAssemblyReferences.Services;
+using Microsoft.VisualStudio.Threading;
+
+using StreamJsonRpc;
+
+#nullable enable
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Server
+{
+ internal sealed class RarController : IRarController
+ {
+ ///
+ /// The amount of time to wait for the validation of connection.
+ ///
+ private const int ValidationTimeout = 60000;
+
+ ///
+ /// Name of
+ ///
+ private readonly string _pipeName;
+
+ ///
+ /// Handshake used for validation of incoming connections
+ ///
+ private readonly Handshake _handshake;
+
+
+ ///
+ /// Factory callback to NamedPipeUtils.CreateNamedPipeServer
+ /// 1. arg: pipe name
+ /// 2. arg: input buffer size
+ /// 3. arg: input buffer size
+ /// 4. arg. number of allow clients
+ /// 5. arg. add right to CreateNewInstance
+ ///
+ private readonly Func _namedPipeServerFactory;
+
+ ///
+ /// Callback to validate the handshake.
+ /// 1. arg: expected handshake
+ /// 2. arg: named pipe over which we should validate the handshake
+ /// 3. arg: timeout for validation
+ ///
+ private readonly Func _validateHandshakeCallback;
+
+ ///
+ /// Handler for all incoming tasks
+ ///
+ private readonly IResolveAssemblyReferenceTaskHandler _resolveAssemblyReferenceTaskHandler;
+
+ ///
+ /// Timeout for incoming connections
+ ///
+ private readonly TimeSpan Timeout = TimeSpan.FromMinutes(15);
+
+ public RarController(
+ string pipeName,
+ Handshake handshake,
+ Func namedPipeServerFactory,
+ Func validateHandshakeCallback,
+ TimeSpan? timeout = null)
+ : this(pipeName,
+ handshake,
+ namedPipeServerFactory,
+ validateHandshakeCallback,
+ timeout: timeout,
+ resolveAssemblyReferenceTaskHandler: new RarTaskHandler())
+ {
+ }
+
+ internal RarController(
+ string pipeName,
+ Handshake handshake,
+ Func namedPipeServerFactory,
+ Func validateHandshakeCallback,
+ IResolveAssemblyReferenceTaskHandler resolveAssemblyReferenceTaskHandler,
+ TimeSpan? timeout = null)
+ {
+ _pipeName = pipeName;
+ _handshake = handshake;
+ _namedPipeServerFactory = namedPipeServerFactory;
+ _validateHandshakeCallback = validateHandshakeCallback;
+ _resolveAssemblyReferenceTaskHandler = resolveAssemblyReferenceTaskHandler;
+
+ if (timeout.HasValue)
+ {
+ Timeout = timeout.Value;
+ }
+ }
+
+ public async Task StartAsync(CancellationToken cancellationToken = default)
+ {
+
+ using ServerMutex mutex = new ServerMutex(_pipeName);
+ if (!mutex.IsLocked)
+ {
+ return 1;
+ }
+
+ using CancellationTokenSource cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
+ CancellationToken token = cancellationTokenSource.Token;
+
+ while (!token.IsCancellationRequested)
+ {
+ // server will dispose stream too.
+ NamedPipeServerStream serverStream = GetStream(_pipeName);
+ await serverStream.WaitForConnectionAsync(token).ConfigureAwait(false);
+
+ if (!_validateHandshakeCallback(_handshake, serverStream, ValidationTimeout))
+ continue;
+
+ // Connected! Refresh timeout for incoming request
+ cancellationTokenSource.CancelAfter(Timeout);
+
+ _ = HandleClientAsync(serverStream, token).ConfigureAwait(false);
+ }
+
+ return 0;
+ }
+
+ private async Task HandleClientAsync(Stream serverStream, CancellationToken cancellationToken = default)
+ {
+ using JsonRpc server = GetRpcServer(serverStream, _resolveAssemblyReferenceTaskHandler);
+ server.StartListening();
+
+ try
+ {
+ await server.Completion.WithCancellation(cancellationToken).ConfigureAwait(false);
+ }
+ catch (ConnectionLostException)
+ {
+ // Some problem with connection, let's ignore it.
+ // All other exceptions are issue though
+ }
+ }
+
+ private JsonRpc GetRpcServer(Stream stream, IResolveAssemblyReferenceTaskHandler handler)
+ {
+ IJsonRpcMessageHandler serverHandler = RpcUtils.GetRarMessageHandler(stream);
+ JsonRpc rpc = new JsonRpc(serverHandler, handler);
+ return rpc;
+ }
+
+ ///
+ /// Instantiates an endpoint to act as a client
+ ///
+ /// The name of the pipe to which we should connect.
+ private NamedPipeServerStream GetStream(string pipeName)
+ {
+ ErrorUtilities.VerifyThrow(_namedPipeServerFactory != null, "Stream factory is not set");
+
+ return _namedPipeServerFactory!(pipeName,
+ null, // Use default size
+ null, // Use default size
+ NamedPipeServerStream.MaxAllowedServerInstances,
+ true);
+ }
+ }
+}
diff --git a/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs b/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs
new file mode 100644
index 00000000000..081904175c3
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/Server/ServerMutex.cs
@@ -0,0 +1,46 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading;
+
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Server
+{
+ internal sealed class ServerMutex : IDisposable
+ {
+ private readonly Mutex _mutex;
+ public bool IsLocked { get; private set; }
+ public bool IsDisposed { get; private set; }
+
+ public ServerMutex(string name)
+ {
+ _mutex = new Mutex(true, name, out bool createdNew);
+ IsLocked = createdNew;
+ }
+
+ public bool Wait(int timeout)
+ {
+ return _mutex.WaitOne(timeout);
+ }
+
+
+ public void Dispose()
+ {
+
+ if (IsDisposed)
+ return;
+ IsDisposed = true;
+
+ try
+ {
+ if (IsLocked)
+ _mutex.ReleaseMutex();
+ }
+ finally
+ {
+ _mutex.Dispose();
+ IsLocked = false;
+ }
+ }
+ }
+}
diff --git a/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs b/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs
new file mode 100644
index 00000000000..90e9ca795de
--- /dev/null
+++ b/src/Tasks/ResolveAssemblyReferences/Services/RarTaskHandler.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System;
+using System.Threading.Tasks;
+
+using Microsoft.Build.Tasks.ResolveAssemblyReferences.Contract;
+
+namespace Microsoft.Build.Tasks.ResolveAssemblyReferences.Services
+{
+ internal class RarTaskHandler : IResolveAssemblyReferenceTaskHandler
+ {
+ public void Dispose()
+ {
+ // For RPC dispose
+ }
+ }
+}
diff --git a/src/Tasks/Resources/Strings.resx b/src/Tasks/Resources/Strings.resx
index cc1e2699443..b4f042b78b1 100644
--- a/src/Tasks/Resources/Strings.resx
+++ b/src/Tasks/Resources/Strings.resx
@@ -2931,4 +2931,7 @@
error codes, because those exceptions should ideally be caught at a higher scope and logged with a wrapper message that
DOES have an error code.
-->
+
+ Couldn't connect to the RAR node, starting a new one.
+
diff --git a/src/Tasks/Resources/xlf/Strings.cs.xlf b/src/Tasks/Resources/xlf/Strings.cs.xlf
index 9598c9d6157..07dc426327a 100644
--- a/src/Tasks/Resources/xlf/Strings.cs.xlf
+++ b/src/Tasks/Resources/xlf/Strings.cs.xlf
@@ -1412,6 +1412,11 @@
Vlastnost StopOnFirstFailure nebude mít žádný účinek, jsou-li splněny všechny následující podmínky: 1) systém pracuje v režimu více procesů, 2) vlastnost BuildInParallel má hodnotu True, 3) vlastnost RunEachTargetSeparately má hodnotu False.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Nelze číst řádky ze souboru {0}. {1}
diff --git a/src/Tasks/Resources/xlf/Strings.de.xlf b/src/Tasks/Resources/xlf/Strings.de.xlf
index 44b9fbe1ba8..dbd4ddf4d4a 100644
--- a/src/Tasks/Resources/xlf/Strings.de.xlf
+++ b/src/Tasks/Resources/xlf/Strings.de.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure hat keine Auswirkungen, wenn die folgenden Bedingungen alle gelten: 1) Das System wird im Mehrprozessmodus ausgeführt. 2) Die BuildInParallel-Eigenschaft ist TRUE. 3) Die RunEachTargetSeparately-Eigenschaft ist FALSE.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Die Zeilen aus der Datei "{0}" konnten nicht gelesen werden. {1}
diff --git a/src/Tasks/Resources/xlf/Strings.en.xlf b/src/Tasks/Resources/xlf/Strings.en.xlf
index c9d6885f10c..3d7361d1f1a 100644
--- a/src/Tasks/Resources/xlf/Strings.en.xlf
+++ b/src/Tasks/Resources/xlf/Strings.en.xlf
@@ -1457,6 +1457,11 @@
StopOnFirstFailure will have no effect when the following conditions are all present: 1) The system is running in multiple process mode 2) The BuildInParallel property is true. 3) The RunEachTargetSeparately property is false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Could not read lines from file "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.es.xlf b/src/Tasks/Resources/xlf/Strings.es.xlf
index e34aa053c3e..b60d06690f4 100644
--- a/src/Tasks/Resources/xlf/Strings.es.xlf
+++ b/src/Tasks/Resources/xlf/Strings.es.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure no surtirá efecto si se dan todas las condiciones siguientes: 1) El sistema se ejecuta en modo de proceso múltiple. 2) La propiedad BuildInParallel es true. 3) La propiedad RunEachTargetSeparately es false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: No se pudieron leer las líneas del archivo "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.fr.xlf b/src/Tasks/Resources/xlf/Strings.fr.xlf
index 9720b69bf5f..75f298b0fc9 100644
--- a/src/Tasks/Resources/xlf/Strings.fr.xlf
+++ b/src/Tasks/Resources/xlf/Strings.fr.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure reste sans effet dans les conditions suivantes : 1) Le système s'exécute en mode multiprocessus. 2) La propriété BuildInParallel a la valeur true. 3) La propriété RunEachTargetSeparately a la valeur false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Impossible de lire les lignes dans le fichier "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.it.xlf b/src/Tasks/Resources/xlf/Strings.it.xlf
index 7ce334457fc..3cb72b5dacf 100644
--- a/src/Tasks/Resources/xlf/Strings.it.xlf
+++ b/src/Tasks/Resources/xlf/Strings.it.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure non avrà alcun effetto in presenza di tutte le condizioni seguenti: 1) il sistema è in esecuzione in modalità a più processi 2) la proprietà BuildInParallel è true. 3) la proprietà RunEachTargetSeparately è false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: non è stato possibile leggere le righe dal file "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.ja.xlf b/src/Tasks/Resources/xlf/Strings.ja.xlf
index 52d2825682c..e78fb6b8d75 100644
--- a/src/Tasks/Resources/xlf/Strings.ja.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ja.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure は、次のすべての条件に該当する場合に無効となります。1) システムがマルチプロセッサ モードで実行されている。2) BuildInParallel プロパティが true に設定されている。3) RunEachTargetSeparately プロパティが false に設定されている。LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: ファイル "{0}" からの行を読み取れませんでした。{1}
diff --git a/src/Tasks/Resources/xlf/Strings.ko.xlf b/src/Tasks/Resources/xlf/Strings.ko.xlf
index 2e70e167585..e3dfd3ff47c 100644
--- a/src/Tasks/Resources/xlf/Strings.ko.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ko.xlf
@@ -1412,6 +1412,11 @@
다음 조건이 모두 충족되면 StopOnFirstFailure가 효과가 없습니다. 1) 시스템이 다중 프로세스 모드에서 실행 중입니다. 2) BuildInParallel 속성이 true입니다. 3) RunEachTargetSeparately 속성이 false입니다.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: "{0}" 파일에서 줄을 읽을 수 없습니다. {1}
diff --git a/src/Tasks/Resources/xlf/Strings.pl.xlf b/src/Tasks/Resources/xlf/Strings.pl.xlf
index 6b53dc24fb7..deed816c0a7 100644
--- a/src/Tasks/Resources/xlf/Strings.pl.xlf
+++ b/src/Tasks/Resources/xlf/Strings.pl.xlf
@@ -1412,6 +1412,11 @@
Działanie funkcji StopOnFirstFailure nie przyniesie efektu, jeśli będą spełnione wszystkie następujące warunki: 1) System działa w trybie wieloprocesowym. 2) Właściwość BuildInParallel ma wartość true. 3) Właściwość RunEachTargetSeparately ma wartość false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Nie można odczytać wierszy z pliku „{0}”. {1}
diff --git a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
index 0d31dafd981..5a8373c8e40 100644
--- a/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
+++ b/src/Tasks/Resources/xlf/Strings.pt-BR.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure não terá efeito quando estas condições existirem: 1) O sistema estiver sendo executado no modo de processamento múltiplo 2) A propriedade BuildInParallel for true. 3) A propriedade RunEachTargetSeparately for false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Não foi possível ler linhas do arquivo "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.ru.xlf b/src/Tasks/Resources/xlf/Strings.ru.xlf
index 29fe42402d0..8c9da6b1782 100644
--- a/src/Tasks/Resources/xlf/Strings.ru.xlf
+++ b/src/Tasks/Resources/xlf/Strings.ru.xlf
@@ -1412,6 +1412,11 @@
StopOnFirstFailure будет игнорироваться при одновременном соблюдении следующих условий: 1) система выполняется в режиме с несколькими процессами; 2) свойство BuildInParallel имеет значение true; 3) свойство RunEachTargetSeparately имеет значение false.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: Не удалось прочесть строки из файла "{0}". {1}
diff --git a/src/Tasks/Resources/xlf/Strings.tr.xlf b/src/Tasks/Resources/xlf/Strings.tr.xlf
index 5076d987e41..97e1112b975 100644
--- a/src/Tasks/Resources/xlf/Strings.tr.xlf
+++ b/src/Tasks/Resources/xlf/Strings.tr.xlf
@@ -1412,6 +1412,11 @@
Şu koşulların tümü doğru olduğunda StopOnFirstFailure etkisiz olur: 1) Sistem çoklu işlem modunda çalışıyorsa. 2) BuildInParallel özelliği true ise. 3) RunEachTargetSeparately özelliği false ise.LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: "{0}" dosyasındaki satırlar okunamadı. {1}
diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
index 2797e21cd0b..cbe8fdeb67a 100644
--- a/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
+++ b/src/Tasks/Resources/xlf/Strings.zh-Hans.xlf
@@ -1412,6 +1412,11 @@
如果满足以下所有条件,StopOnFirstFailure 将不起任何作用: 1) 系统在多进程模式下运行。2) BuildInParallel 属性为 true。3) RunEachTargetSeparately 属性为 false。LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: 未能从文件“{0}”读取命令行。{1}
diff --git a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
index 05c4f11672b..42739e5841a 100644
--- a/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
+++ b/src/Tasks/Resources/xlf/Strings.zh-Hant.xlf
@@ -1412,6 +1412,11 @@
當下列條件全部成立時,StopOnFirstFailure 將沒有作用: 1) 系統正在多處理程序模式中執行 2) BuildInParallel 屬性為 true。3) RunEachTargetSeparately 屬性為 false。LOCALIZATION: Do not localize the words "RunEachTargetSeparately", "BuildingInParallel", or "StopOnFirstFailure".
+
+
+ Couldn't connect to the RAR node, starting a new one.
+
+ MSB3501: 無法從檔案 "{0}" 讀取行。{1}