Skip to content

Enable TaskHostFactory for .NET (Core) MSBuild#6994

Merged
Forgind merged 11 commits intodotnet:mainfrom
rainersigwald:core-taskhost
Nov 15, 2021
Merged

Enable TaskHostFactory for .NET (Core) MSBuild#6994
Forgind merged 11 commits intodotnet:mainfrom
rainersigwald:core-taskhost

Conversation

@rainersigwald
Copy link
Copy Markdown
Member

@rainersigwald rainersigwald commented Oct 25, 2021

Fixes #5158

Context

A task author can force any task to run out of process by specifying that it must run with TaskFactory="TaskHostFactory". But we never made this work on .NET Core; it fails with an error like

S:\msbuild\foo.proj(8,5): error MSB4216: Could not run the "Message" task because MSBuild could not create or connect to a task host with runtime "CLR4" and architecture "x64".  Please ensure that (1) the requested runtime and/or architecture are available on the machine, and (2) that the required executable "S:\msbuild\.dotnet\sdk\6.0.100-rc.1.21458.32\MSBuild.exe" exists and can be run.

That's because the "default" task host runtime was hardcoded to be CLR4 (even when on .NET 6.0+).

Changes Made

Created GetCurrentMSBuildRuntime() and used it; plumbed the new value around and checked for it in a few places. Eliminated FEATURE_TASKHOST since it's on everywhere now. Unified the named-pipe-name computation code (before it was using a different pipe name for taskhost pipes on UNIX).

Testing

Re-enabled tests for this behavior that have been disabled on .NET Core. Extended some to account for the NET runtime.

@rainersigwald rainersigwald added .NET Core Area: Engine Issues impacting the core execution of targets and tasks. labels Oct 25, 2021
@rainersigwald rainersigwald added this to the MSBuild 17.1 milestone Oct 25, 2021
@rainersigwald rainersigwald marked this pull request as ready for review November 3, 2021 19:42
internal const string clr2 = "CLR2";
internal const string clr4 = "CLR4";
internal const string currentRuntime = "CurrentRuntime";
internal const string net = "NET";
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@baronfel I'd like your input on this new name please. We need to add an entry to the list that indicates ".NET Core or .NET 5.0+" and is distinguishable from the longstanding CLR2 and CLR4. We should probably get buyoff from somebody in .NET Runtime PM about it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reaching out to some folks for discussion 👍

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where are these values exposed? Presumably you want input on the literal itself, not the name of the constant field, right?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. They are exposed to MSBuild task authors in the UsingTask element or "phantom parameters" on task invocations within targets (docs on existing CLR2/CLR4 options).

The existing options are "CLR2" which means "run on .NET Framework 3.5" and "CLR4" which means "run on .NET Framework 4.7.2+". We need one for "run on .NET 6.0+". If we'd done this before .NET 5.0, I would have selected CORE which I still kinda like, but since it's not .NET Core any more that's probably not the best choice.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I lean towards "NET" now, personally. There's a long internal email thread where I think we've settled on this nomenclature.

Copy link
Copy Markdown
Member

@benvillalobos benvillalobos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks mostly fine. My main concern is variable/property naming in the long term. I feel like NET as a value is not explicitly clear that we're in a "net core" case and it could be confused for net48 or net framework


reservedProperties.Add(ProjectPropertyInstance.Create(ReservedPropertyNames.msbuildRuntimeType,
#if RUNTIME_TYPE_NETCORE
"Core",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The new handshake option is NET which refers to a net core build. Here the runtime is being set to Core if it's on net core. For consistency should they have the same name? When I first saw the handshake option I felt like it should be named NETCORE or something.

if ((runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase)) ||
(runtimeA.Equals(MSBuildRuntimeValues.clr4, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase)))
if ((runtimeA.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(GetCurrentMSBuildRuntime(), StringComparison.OrdinalIgnoreCase)) ||
(runtimeA.Equals(GetCurrentMSBuildRuntime(), StringComparison.OrdinalIgnoreCase) && runtimeB.Equals(MSBuildRuntimeValues.currentRuntime, StringComparison.OrdinalIgnoreCase)))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grumbling out loud: funny how we ended up in a scenario where MSBuildRuntimeValues.currentRuntime and GetCurrentMSBuildRuntime() are two different things. Which lead to the variable name on line 221 string actualCurrentRuntime 😅

{
clrVersion = 4;
}
else if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: checking clr2 first is more intuitive. Or is this ordering intentional?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a straight port of the existing logic but I agree that putting it in order makes sense.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do the ordering based on which is expected to occur most often, so I'd even bump MSBuildRuntimeValue.net to the top.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. That's an optimization that PGO should be great at and with small effect, so I don't think it's worth a readability hit without proof (and this isn't a super hot loop or anything).
  2. Why do you think net would happen most often?

// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.Build.Shared;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit whitespace

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually my preferred style:

  1. Block for System. / BCL usings
  2. Block for local-repo usings
  3. Block for third-party usings

#if NET40_OR_GREATER
return MSBuildRuntimeValues.clr4;
#else
return MSBuildRuntimeValues.net;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking this should be netcore or core to be extra clear.

Copy link
Copy Markdown
Contributor

@Forgind Forgind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good. Mostly little things

}

#if FEATURE_TASKHOST && !NO_MSBUILDTASKHOST
#if !NO_MSBUILDTASKHOST
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the comment below still accurate? I'm wondering when we wouldn't have task hosts available.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have the 3.5 task host on .NET Core or Mono builds.

#if RUNTIME_TYPE_NETCORE
"Core",
#elif MONO
NativeMethodsShared.IsMono ? "Mono" : "Full");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would it be MONO but not IsMono?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MSBuild compiled for mono but running on .NET Framework was a possible thing for a while. I don't think it's super relevant any more but this is copypasta from existing code and I'd rather have consistent behavior.

{
clrVersion = 4;
}
else if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should do the ordering based on which is expected to occur most often, so I'd even bump MSBuildRuntimeValue.net to the top.

internal static class NamedPipeUtil
{
internal static string GetPipeNameOrPath(string pipeName)
internal static string GetPipeNameOrPath(int? processId = null)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we're using this more. I'm curious if it will show up as extra MSBuild# string allocations.


[Fact]
public void TestMergeRuntimeValues()
[Theory]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit:
This test and the previous one are similar in content but different in style. Switch to same style?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the juice is worth the squeeze on this.

<UsingTask TaskName="Microsoft.Build.Tasks.RegisterAssembly" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Condition="('$(MSBuildAssemblyVersion)' != '') and '$(DisableOutOfProcTaskHost)' != ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.RegisterAssembly" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Runtime="CLR4" Condition="('$(MSBuildAssemblyVersion)' != '') and '$(DisableOutOfProcTaskHost)' == ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.RegisterAssembly" AssemblyName="Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Runtime="CLR2" Condition="('$(MSBuildAssemblyVersion)' != '') and '$(DisableOutOfProcTaskHost)' == ''" />
<UsingTask TaskName="Microsoft.Build.Tasks.RegisterAssembly" AssemblyName="Microsoft.Build.Tasks.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" Runtime="CLR4" Condition="('$(MSBuildAssemblyVersion)' != '') and '$(DisableOutOfProcTaskHost)' == '' and '$(MSBuildRuntimeType)' != 'Core'" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a core version of RegisterAssembly? Not sure if it's been ported. Same for UnregisterAssembly.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

COM build operations in general aren't functional on Core today. This doesn't change that but does potentially make it slightly clearer.

@Forgind
Copy link
Copy Markdown
Contributor

Forgind commented Nov 5, 2021

Oh, and I'm curious if there are any other issues (disabled tests) we can close with this PR. Have you checked/just know there aren't?

Copy link
Copy Markdown
Member Author

@rainersigwald rainersigwald left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accidentally didn't send the comment I made promptly 🤦🏻

{
clrVersion = 4;
}
else if (runtimeVersion.Equals(XMakeAttributes.MSBuildRuntimeValues.clr2, StringComparison.OrdinalIgnoreCase))
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a straight port of the existing logic but I agree that putting it in order makes sense.

internal const string clr2 = "CLR2";
internal const string clr4 = "CLR4";
internal const string currentRuntime = "CurrentRuntime";
internal const string net = "NET";
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. They are exposed to MSBuild task authors in the UsingTask element or "phantom parameters" on task invocations within targets (docs on existing CLR2/CLR4 options).

The existing options are "CLR2" which means "run on .NET Framework 3.5" and "CLR4" which means "run on .NET Framework 4.7.2+". We need one for "run on .NET 6.0+". If we'd done this before .NET 5.0, I would have selected CORE which I still kinda like, but since it's not .NET Core any more that's probably not the best choice.

string architecture = null;

if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out runtime))
if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out _))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
if (!mergedParameters.TryGetValue(XMakeAttributes.runtime, out _))
if (!mergedParameters.ContainsKey(XMakeAttributes.runtime))

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦🏻 thanks

}

if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out architecture))
if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out _))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
if (!mergedParameters.TryGetValue(XMakeAttributes.architecture, out _))
if (!mergedParameters.ContainsKey(XMakeAttributes.architecture))

/// <summary>
/// Using the .NET Core/.NET 5.0+ runtime
/// </summary>
NET = 64,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this comment should be updated:

// We currently use 6 bits of this 32-bit integer. Very old builds will instantly reject any handshake that does not start with F5 or 06; slightly old builds always lead with 00.

@rainersigwald
Copy link
Copy Markdown
Member Author

I'm curious if there are any other issues (disabled tests) we can close with this PR. Have you checked/just know there aren't?

I checked lightly and didn't see anything.

@Forgind Forgind added the merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. label Nov 15, 2021
@Forgind Forgind merged commit 88882b9 into dotnet:main Nov 15, 2021
@rainersigwald rainersigwald deleted the core-taskhost branch November 15, 2021 21:27
@mooredynasty
Copy link
Copy Markdown

mooredynasty commented May 2, 2022

I see this hit the main branch. I'm wondering what SDK version(s) this was/will be released in? And/or how to find that information myself. Thanks!

@Forgind
Copy link
Copy Markdown
Contributor

Forgind commented May 2, 2022

I don't know if this is a good way to do it at all, but you can look at the release/version branches in the dotnet/sdk repo then go to eng/Versions.Details.xml and find Microsoft.Build. That gives you a commit hash for the last MSBuild in that version, and you can go to our commits list to figure out if that's before or after this.

Like for release/6.0.2xx, https://github.com/dotnet/sdk/blob/release/6.0.2xx/eng/Version.Details.xml#L71 says a02f736, which is a02f736, which came out in February, so it should also include this commit.

From there, you can do binary search.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area: Engine Issues impacting the core execution of targets and tasks. merge-when-branch-open PRs that are approved, except that there is a problem that means we are not merging stuff right now. .NET Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable out of proc TaskHost on .NET Core

7 participants