44
55using System ;
66using System . Collections . Generic ;
7+ using System . Diagnostics ;
78using System . IO ;
89using System . Resources ;
910using System . Text ;
@@ -15,8 +16,10 @@ namespace Microsoft.CodeAnalysis.BuildTasks
1516{
1617 public abstract class ManagedToolTask : ToolTask
1718 {
19+ private static bool DefaultIsSdkFrameworkToCoreBridgeTask { get ; } = CalculateIsSdkFrameworkToCoreBridgeTask ( ) ;
20+
1821 /// <summary>
19- /// Is the standard tool being used here? When false the developer has specified a custom tool
22+ /// Is the builtin tool being used here? When false the developer has specified a custom tool
2023 /// to be run by this task
2124 /// </summary>
2225 /// <remarks>
@@ -26,39 +29,33 @@ public abstract class ManagedToolTask : ToolTask
2629 /// ToolExe == ToolName, we know nothing is overridden, and
2730 /// we can use our own csc.
2831 /// </remarks>
29- protected bool IsManagedTool => string . IsNullOrEmpty ( ToolPath ) && ToolExe == ToolName ;
32+ protected bool UsingBuiltinTool => string . IsNullOrEmpty ( ToolPath ) && ToolExe == ToolName ;
3033
3134 /// <summary>
32- /// Used to determine the directory where the tools (like csc) are located.
33- /// See <see cref="Utilities.GenerateFullPathToTool"/>.
35+ /// A copy of this task, compiled for .NET Framework, is deployed into the .NET SDK. It is a bridge task
36+ /// that is loaded into .NET Framework MSBuild but launches the .NET Core compiler. This task necessarily
37+ /// has different behaviors than the standard build task compiled for .NET Framework and loaded into the
38+ /// .NET Framework MSBuild.
3439 /// </summary>
35- protected virtual RoslynCompilerType GetCompilerType ( ) => DefaultCompilerType ;
36-
37- protected const RoslynCompilerType DefaultCompilerType
38- #if NET
39- = RoslynCompilerType . Core ;
40- #else
41- = RoslynCompilerType . Framework ;
42- #endif
40+ /// <remarks>
41+ /// This is mutable to facilitate testing
42+ /// </remarks>
43+ internal bool IsSdkFrameworkToCoreBridgeTask { get ; init ; } = DefaultIsSdkFrameworkToCoreBridgeTask ;
4344
44- internal string PathToManagedTool => Utilities . GenerateFullPathToTool ( ToolName , GetCompilerType ( ) ) ;
45+ /// <summary>
46+ /// Is the builtin tool executed by this task running on .NET Core?
47+ /// </summary>
48+ internal bool IsBuiltinToolRunningOnCoreClr => RuntimeHostInfo . IsCoreClrRuntime || IsSdkFrameworkToCoreBridgeTask ;
4549
46- private string PathToManagedToolWithoutExtension
47- {
48- get
49- {
50- var extension = Path . GetExtension ( PathToManagedTool ) ;
51- return PathToManagedTool . Substring ( 0 , PathToManagedTool . Length - extension . Length ) ;
52- }
53- }
50+ internal string PathToBuiltInTool => Path . Combine ( GetToolDirectory ( ) , ToolName ) ;
5451
5552 protected ManagedToolTask ( ResourceManager resourceManager )
5653 : base ( resourceManager )
5754 {
5855 }
5956
6057 /// <summary>
61- /// Generate the arguments to pass directly to the managed tool. These do not include
58+ /// Generate the arguments to pass directly to the buitin tool. These do not include
6259 /// arguments in the response file.
6360 /// </summary>
6461 /// <remarks>
@@ -77,9 +74,9 @@ internal string GenerateToolArguments()
7774 protected sealed override string GenerateCommandLineCommands ( )
7875 {
7976 var commandLineArguments = GenerateToolArguments ( ) ;
80- if ( IsManagedTool )
77+ if ( UsingBuiltinTool && IsBuiltinToolRunningOnCoreClr )
8178 {
82- ( _ , commandLineArguments , _ ) = RuntimeHostInfo . GetProcessInfo ( PathToManagedToolWithoutExtension , commandLineArguments ) ;
79+ commandLineArguments = RuntimeHostInfo . GetDotNetExecCommandLine ( PathToBuiltInTool , commandLineArguments ) ;
8380 }
8481
8582 return commandLineArguments ;
@@ -96,7 +93,7 @@ protected sealed override string GenerateResponseFileCommands()
9693 }
9794
9895 /// <summary>
99- /// Generate the arguments to pass directly to the managed tool. These do not include
96+ /// Generate the arguments to pass directly to the buitin tool. These do not include
10097 /// arguments in the response file.
10198 /// </summary>
10299 /// <remarks>
@@ -117,10 +114,12 @@ protected sealed override string GenerateResponseFileCommands()
117114 /// This could be the managed assembly itself (on desktop .NET on Windows),
118115 /// or a runtime such as dotnet.
119116 /// </summary>
120- protected sealed override string GenerateFullPathToTool ( ) =>
121- IsManagedTool
122- ? RuntimeHostInfo . GetProcessInfo ( PathToManagedToolWithoutExtension , string . Empty ) . processFilePath
123- : Path . Combine ( ToolPath ?? "" , ToolExe ) ;
117+ protected sealed override string GenerateFullPathToTool ( ) => ( UsingBuiltinTool , IsBuiltinToolRunningOnCoreClr ) switch
118+ {
119+ ( true , true ) => RuntimeHostInfo . GetDotNetPathOrDefault ( ) ,
120+ ( true , false ) => PathToBuiltInTool ,
121+ ( false , _ ) => Path . Combine ( ToolPath ?? "" , ToolExe )
122+ } ;
124123
125124 protected abstract string ToolNameWithoutExtension { get ; }
126125
@@ -129,18 +128,17 @@ protected sealed override string GenerateFullPathToTool() =>
129128 protected abstract void AddResponseFileCommands ( CommandLineBuilderExtension commandLine ) ;
130129
131130 /// <summary>
132- /// ToolName is only used in cases where <see cref="IsManagedTool"/> returns true.
133- /// It returns the name of the managed assembly, which might not be the path returned by
134- /// GenerateFullPathToTool, which can return the path to e.g. the dotnet executable.
131+ /// This is the file name of the builtin tool that will be executed.
135132 /// </summary>
136133 /// <remarks>
137- /// We *cannot* actually call IsManagedTool in the implementation of this method,
138- /// as the implementation of IsManagedTool calls this property. See the comment in
139- /// <see cref="ManagedToolTask.IsManagedTool"/> .
134+ /// ToolName is only used in cases where <see cref="UsingBuiltinTool"/> returns true.
135+ /// It returns the name of the managed assembly, which might not be the path returned by
136+ /// GenerateFullPathToTool, which can return the path to e.g. the dotnet executable .
140137 /// </remarks>
141- protected sealed override string ToolName => RuntimeHostInfo . IsCoreClrRuntime
142- ? $ "{ ToolNameWithoutExtension } .dll"
143- : $ "{ ToolNameWithoutExtension } .exe";
138+ protected sealed override string ToolName =>
139+ IsBuiltinToolRunningOnCoreClr
140+ ? $ "{ ToolNameWithoutExtension } .dll"
141+ : $ "{ ToolNameWithoutExtension } .exe";
144142
145143 /// <summary>
146144 /// This generates the command line arguments passed to the tool.
@@ -177,5 +175,60 @@ protected static ITaskItem[] GenerateCommandLineArgsTaskItems(List<string> comma
177175
178176 return items ;
179177 }
178+
179+ private string GetToolDirectory ( )
180+ {
181+ var buildTask = typeof ( ManagedToolTask ) . Assembly ;
182+ var buildTaskDirectory = GetBuildTaskDirectory ( ) ;
183+ #if NET
184+ return Path . Combine ( buildTaskDirectory , "bincore" ) ;
185+ #else
186+ return IsSdkFrameworkToCoreBridgeTask
187+ ? Path . Combine ( buildTaskDirectory , ".." , "bincore" )
188+ : buildTaskDirectory ;
189+ #endif
190+ }
191+
192+ /// <summary>
193+ /// <see cref="IsSdkFrameworkToCoreBridgeTask"/>
194+ /// </summary>
195+ /// <remarks>
196+ /// Using the file system as a way to differentiate between the two tasks is not ideal, but it is effective
197+ /// and allows us to avoid significantly complicating the build process. The alternative is another parameter
198+ /// to the Csc / Vbc / etc ... tasks that all invocations would need to pass along.
199+ /// </remarks>
200+ internal static bool CalculateIsSdkFrameworkToCoreBridgeTask ( )
201+ {
202+ #if NET
203+ return false ;
204+ #else
205+ // This logic needs to be updated when this issue is fixed. That moves csc.exe out to a subdirectory
206+ // and hence the check below will need to change
207+ //
208+ // https://github.com/dotnet/roslyn/issues/78001
209+
210+ var buildTaskDirectory = GetBuildTaskDirectory ( ) ;
211+ var buildTaskDirectoryName = Path . GetFileName ( buildTaskDirectory ) ;
212+ return
213+ string . Equals ( buildTaskDirectoryName , "binfx" , StringComparison . OrdinalIgnoreCase ) &&
214+ ! File . Exists ( Path . Combine ( buildTaskDirectory , "csc.exe" ) ) &&
215+ Directory . Exists ( Path . Combine ( buildTaskDirectory , ".." , "bincore" ) ) ;
216+ #endif
217+ }
218+
219+ internal static string GetBuildTaskDirectory ( )
220+ {
221+ var buildTask = typeof ( ManagedToolTask ) . Assembly ;
222+ var buildTaskDirectory = Path . GetDirectoryName ( buildTask . Location ) ;
223+ if ( buildTaskDirectory is null )
224+ {
225+ // This should not happen in supported product scenarios but could happen if
226+ // a non-supported scenario tried to load our task (like AOT) and call
227+ // through these members.
228+ throw new InvalidOperationException ( "Unable to determine the location of the build task assembly." ) ;
229+ }
230+
231+ return buildTaskDirectory ;
232+ }
180233 }
181234}
0 commit comments