Skip to content

Commit 984aa24

Browse files
authored
Fall back to dotnet exec if apphost does not exist (#80153)
* Fall back to `dotnet exec` if apphost does not exist * Exclude apphosts from BuildBoss * Fix tests * Relax a test * Keep Windows apphosts * Revert "Keep Windows apphosts" This reverts commit ab14dc1. * Simplify logic
1 parent 061c3dc commit 984aa24

File tree

7 files changed

+107
-16
lines changed

7 files changed

+107
-16
lines changed

src/Compilers/Core/MSBuildTask/ManagedToolTask.cs

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
using System;
66
using System.Collections.Generic;
7+
using System.Diagnostics;
78
using System.IO;
89
using System.Resources;
910
using System.Text;
@@ -16,6 +17,7 @@ namespace Microsoft.CodeAnalysis.BuildTasks
1617
{
1718
public abstract class ManagedToolTask : ToolTask
1819
{
20+
private bool? _useAppHost;
1921
internal readonly PropertyDictionary _store = new PropertyDictionary();
2022

2123
/// <summary>
@@ -55,6 +57,23 @@ public abstract class ManagedToolTask : ToolTask
5557

5658
internal string PathToBuiltInTool => Path.Combine(GetToolDirectory(), ToolName);
5759

60+
/// <summary>
61+
/// We fallback to not use the apphost if it is not present (can happen in compiler toolset scenarios for example).
62+
/// </summary>
63+
private bool UseAppHost
64+
{
65+
get
66+
{
67+
if (_useAppHost is not { } useAppHost)
68+
{
69+
_useAppHost = useAppHost = File.Exists(Path.Combine(GetToolDirectory(), AppHostToolName));
70+
Debug.Assert(IsBuiltinToolRunningOnCoreClr || useAppHost);
71+
}
72+
73+
return useAppHost;
74+
}
75+
}
76+
5877
protected ManagedToolTask(ResourceManager resourceManager)
5978
: base(resourceManager)
6079
{
@@ -79,7 +98,14 @@ internal string GenerateToolArguments()
7998
/// </summary>
8099
protected sealed override string GenerateCommandLineCommands()
81100
{
82-
return GenerateToolArguments();
101+
var commandLineArguments = GenerateToolArguments();
102+
103+
if (UsingBuiltinTool && !UseAppHost)
104+
{
105+
commandLineArguments = RuntimeHostInfo.GetDotNetExecCommandLine(PathToBuiltInTool, commandLineArguments);
106+
}
107+
108+
return commandLineArguments;
83109
}
84110

85111
/// <summary>
@@ -111,12 +137,17 @@ protected sealed override string GenerateResponseFileCommands()
111137

112138
/// <summary>
113139
/// This generates the path to the executable that is directly ran.
114-
/// This could be the managed assembly itself (on desktop .NET on Windows),
115-
/// or a runtime such as dotnet.
140+
/// This could be the executable apphost or a runtime such as dotnet.
116141
/// </summary>
117-
protected sealed override string GenerateFullPathToTool() => UsingBuiltinTool
118-
? PathToBuiltInTool
119-
: Path.Combine(ToolPath ?? "", ToolExe);
142+
protected sealed override string GenerateFullPathToTool()
143+
{
144+
if (UsingBuiltinTool)
145+
{
146+
return UseAppHost ? PathToBuiltInTool : RuntimeHostInfo.GetDotNetPathOrDefault();
147+
}
148+
149+
return Path.Combine(ToolPath ?? "", ToolExe);
150+
}
120151

121152
protected abstract string ToolNameWithoutExtension { get; }
122153

@@ -132,7 +163,15 @@ protected sealed override string GenerateFullPathToTool() => UsingBuiltinTool
132163
/// It returns the name of the managed assembly, which might not be the path returned by
133164
/// GenerateFullPathToTool, which can return the path to e.g. the dotnet executable.
134165
/// </remarks>
135-
protected sealed override string ToolName => $"{ToolNameWithoutExtension}{PlatformInformation.ExeExtension}";
166+
protected sealed override string ToolName
167+
{
168+
get
169+
{
170+
return UseAppHost ? AppHostToolName : $"{ToolNameWithoutExtension}.dll";
171+
}
172+
}
173+
174+
private string AppHostToolName => $"{ToolNameWithoutExtension}{PlatformInformation.ExeExtension}";
136175

137176
/// <summary>
138177
/// This generates the command line arguments passed to the tool.

src/Compilers/Core/SdkTaskTests/SdkManagedToolTests.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.IO;
6-
using Roslyn.Utilities;
76
using Xunit;
87
using Xunit.Abstractions;
98

@@ -23,7 +22,12 @@ public void PathToBuiltinTool()
2322
{
2423
var taskPath = Path.GetDirectoryName(typeof(ManagedCompiler).Assembly.Location)!;
2524
var task = new Csc();
26-
Assert.Equal(Path.Combine(taskPath, "..", "bincore", $"csc{PlatformInformation.ExeExtension}"), task.PathToBuiltInTool);
25+
Assert.Contains(task.PathToBuiltInTool,
26+
new[]
27+
{
28+
Path.Combine(taskPath, "..", "bincore", "csc.dll"),
29+
Path.Combine(taskPath, "..", "bincore", "csc.exe"),
30+
});
2731
}
2832

2933
[Fact]

src/Compilers/Server/VBCSCompilerTests/CompilerServerApiTest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ public async Task IncorrectServerHashReturnsIncorrectHashResponse()
130130
public void QuotePipeName_Desktop()
131131
{
132132
var serverInfo = BuildServerConnection.GetServerProcessInfo(@"q:\tools", "name with space");
133-
AssertEx.Equal(@"q:\tools\VBCSCompiler.exe", serverInfo.processFilePath);
134-
AssertEx.Equal(@"""-pipename:name with space""", serverInfo.commandLineArguments);
133+
Assert.EndsWith(@"\dotnet.exe", serverInfo.processFilePath);
134+
AssertEx.Equal(@"exec ""q:\tools\VBCSCompiler.dll"" ""-pipename:name with space""", serverInfo.commandLineArguments);
135135
}
136136

137137
[ConditionalFact(typeof(CoreClrOnly))]
@@ -143,7 +143,7 @@ public void QuotePipeName_CoreClr()
143143
: "/tools";
144144
var serverInfo = BuildServerConnection.GetServerProcessInfo(toolDir, "name with space");
145145
var vbcsFilePath = Path.Combine(toolDir, "VBCSCompiler.dll");
146-
AssertEx.Equal($@"""-pipename:name with space""", serverInfo.commandLineArguments);
146+
AssertEx.Equal($@"exec ""{vbcsFilePath}"" ""-pipename:name with space""", serverInfo.commandLineArguments);
147147
}
148148

149149
[Theory]

src/Compilers/Shared/BuildServerConnection.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,14 @@ internal static (string processFilePath, string commandLineArguments) GetServerP
434434
{
435435
var processFilePath = Path.Combine(clientDir, $"VBCSCompiler{PlatformInformation.ExeExtension}");
436436
var commandLineArgs = $@"""-pipename:{pipeName}""";
437+
438+
if (!File.Exists(processFilePath))
439+
{
440+
// Fallback to not use the apphost if it is not present (can happen in compiler toolset scenarios for example).
441+
commandLineArgs = RuntimeHostInfo.GetDotNetExecCommandLine(Path.ChangeExtension(processFilePath, ".dll"), commandLineArgs);
442+
processFilePath = RuntimeHostInfo.GetDotNetPathOrDefault();
443+
}
444+
437445
return (processFilePath, commandLineArgs);
438446
}
439447

src/Compilers/Shared/RuntimeHostInfo.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
using System;
88
using System.IO;
9+
using Roslyn.Utilities;
910

1011
namespace Microsoft.CodeAnalysis
1112
{
@@ -55,5 +56,44 @@ internal static class RuntimeHostInfo
5556

5657
return null;
5758
}
59+
60+
/// <summary>
61+
/// Get the path to the dotnet executable. In the case the .NET SDK did not provide this information
62+
/// in the environment this tries to find "dotnet" on the PATH. In the case it is not found,
63+
/// this will return simply "dotnet".
64+
/// </summary>
65+
internal static string GetDotNetPathOrDefault()
66+
{
67+
if (GetDotNetHostPath() is { } pathToDotNet)
68+
{
69+
return pathToDotNet;
70+
}
71+
72+
var (fileName, sep) = PlatformInformation.IsWindows
73+
? ("dotnet.exe", new char[] { ';' })
74+
: ("dotnet", new char[] { ':' });
75+
76+
var path = Environment.GetEnvironmentVariable("PATH") ?? "";
77+
foreach (var item in path.Split(sep, StringSplitOptions.RemoveEmptyEntries))
78+
{
79+
try
80+
{
81+
var filePath = Path.Combine(item, fileName);
82+
if (File.Exists(filePath))
83+
{
84+
return filePath;
85+
}
86+
}
87+
catch
88+
{
89+
// If we can't read a directory for any reason just skip it
90+
}
91+
}
92+
93+
return fileName;
94+
}
95+
96+
internal static string GetDotNetExecCommandLine(string toolFilePath, string commandLineArguments) =>
97+
$@"exec ""{toolFilePath}"" {commandLineArguments}";
5898
}
5999
}

src/NuGet/Microsoft.Net.Compilers.Toolset/CoreClrCompilerArtifacts.targets

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,14 @@
1919

2020
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)csc\$(Configuration)\$(TargetFramework)\publish\csc.dll" />
2121
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)csc\$(Configuration)\$(TargetFramework)\publish\csc.deps.json" />
22-
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)csc\$(Configuration)\$(TargetFramework)\publish\csc.exe" Condition="'$(DotNetBuildSourceOnly)' != 'true'" />
2322
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)csc\$(Configuration)\$(TargetFramework)\publish\csc.runtimeconfig.json" />
2423

2524
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)vbc\$(Configuration)\$(TargetFramework)\publish\vbc.dll" />
2625
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)vbc\$(Configuration)\$(TargetFramework)\publish\vbc.deps.json" />
27-
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)vbc\$(Configuration)\$(TargetFramework)\publish\vbc.exe" Condition="'$(DotNetBuildSourceOnly)' != 'true'" />
2826
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)vbc\$(Configuration)\$(TargetFramework)\publish\vbc.runtimeconfig.json" />
2927

3028
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)VBCSCompiler\$(Configuration)\$(TargetFramework)\publish\VBCSCompiler.dll" />
3129
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)VBCSCompiler\$(Configuration)\$(TargetFramework)\publish\VBCSCompiler.deps.json" />
32-
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)VBCSCompiler\$(Configuration)\$(TargetFramework)\publish\VBCSCompiler.exe" Condition="'$(DotNetBuildSourceOnly)' != 'true'" />
3330
<CoreClrCompilerBinArtifact Include="$(ArtifactsBinDir)VBCSCompiler\$(Configuration)\$(TargetFramework)\publish\VBCSCompiler.runtimeconfig.json" />
3431

3532
<!-- References that are either not in the target framework or are a higher version -->

src/Tools/BuildBoss/CompilerNuGetCheckerUtil.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,10 @@ private bool CheckPackages(TextWriter textWriter)
180180
excludeFunc: relativeFileName =>
181181
relativeFileName.StartsWith(@"tasks\netcore\bincore\Microsoft.DiaSymReader.Native", PathComparison) ||
182182
relativeFileName.StartsWith(@"tasks\netcore\bincore\Microsoft.CodeAnalysis.ExternalAccess.RazorCompiler.dll", PathComparison) ||
183-
(relativeFileName.StartsWith(@"tasks\netcore\binfx\", PathComparison) && relativeFileName.EndsWith(".targets", PathComparison)),
183+
(relativeFileName.StartsWith(@"tasks\netcore\binfx\", PathComparison) && relativeFileName.EndsWith(".targets", PathComparison)) ||
184+
relativeFileName.Equals(@"tasks\netcore\bincore\csc.exe", PathComparison) ||
185+
relativeFileName.Equals(@"tasks\netcore\bincore\vbc.exe", PathComparison) ||
186+
relativeFileName.Equals(@"tasks\netcore\bincore\VBCSCompiler.exe", PathComparison),
184187
(@"tasks\net472", GetProjectOutputDirectory("csc", "net472")),
185188
(@"tasks\net472", GetProjectOutputDirectory("vbc", "net472")),
186189
(@"tasks\net472", GetProjectOutputDirectory("csi", "net472")),

0 commit comments

Comments
 (0)