diff --git a/Directory.Packages.props b/Directory.Packages.props
index 104dedfd9eaf..07229dedec94 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -18,6 +18,7 @@
+
diff --git a/NuGet.config b/NuGet.config
index 09870da28882..b7be4e0c5b7f 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -32,6 +32,7 @@
+
diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs
index d5dd7f30c73a..a8bcca667ff9 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/ArgumentEscaper.cs
@@ -86,12 +86,12 @@ private static IEnumerable EscapeArgArrayForCmd(IEnumerable argu
return escapedArgs;
}
- public static string EscapeSingleArg(string arg)
+ public static string EscapeSingleArg(string arg, Func? additionalShouldSurroundWithQuotes = null)
{
var sb = new StringBuilder();
var length = arg.Length;
- var needsQuotes = length == 0 || ShouldSurroundWithQuotes(arg);
+ var needsQuotes = length == 0 || ShouldSurroundWithQuotes(arg) || additionalShouldSurroundWithQuotes?.Invoke(arg) == true;
var isQuoted = needsQuotes || IsSurroundedWithQuotes(arg);
if (needsQuotes) sb.Append("\"");
diff --git a/src/Cli/Microsoft.DotNet.Cli.Utils/Product.cs b/src/Cli/Microsoft.DotNet.Cli.Utils/Product.cs
index 8d61c427aace..60f6aaf151f5 100644
--- a/src/Cli/Microsoft.DotNet.Cli.Utils/Product.cs
+++ b/src/Cli/Microsoft.DotNet.Cli.Utils/Product.cs
@@ -5,18 +5,32 @@
namespace Microsoft.DotNet.Cli.Utils;
-public class Product
+public static class Product
{
public static string LongName => LocalizableStrings.DotNetSdkInfo;
- public static readonly string Version = GetProductVersion();
+ public static readonly string Version;
+ public static readonly string TargetFrameworkVersion;
- private static string GetProductVersion()
+ static Product()
{
DotnetVersionFile versionFile = DotnetFiles.VersionFileObject;
- return versionFile.BuildNumber ??
+ Version = versionFile.BuildNumber ??
System.Diagnostics.FileVersionInfo.GetVersionInfo(
typeof(Product).GetTypeInfo().Assembly.Location)
.ProductVersion ??
string.Empty;
+
+ int firstDotIndex = Version.IndexOf('.');
+ if (firstDotIndex >= 0)
+ {
+ int secondDotIndex = Version.IndexOf('.', firstDotIndex + 1);
+ TargetFrameworkVersion = secondDotIndex >= 0
+ ? Version.Substring(0, secondDotIndex)
+ : Version;
+ }
+ else
+ {
+ TargetFrameworkVersion = string.Empty;
+ }
}
}
diff --git a/src/Cli/dotnet/Commands/CliCommandStrings.resx b/src/Cli/dotnet/Commands/CliCommandStrings.resx
index 75c76f009030..4bed58e12ecd 100644
--- a/src/Cli/dotnet/Commands/CliCommandStrings.resx
+++ b/src/Cli/dotnet/Commands/CliCommandStrings.resx
@@ -1539,9 +1539,13 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man
{0} is an option name like '--no-build'.
- Warning: Binary log option was specified but build will be skipped because output is up to date, specify '--no-cache' to force build.
+ Warning: Binary log option was specified but build will be skipped because output is up to date. Specify '--no-cache' to force build.
{Locked="--no-cache"}
+
+ Warning: Binary log option was specified but MSBuild will be skipped because running just csc is enough. Specify '--no-cache' to force full build.
+ {Locked="--no-cache"}{Locked="MSBuild"}{Locked="csc"}
+
Publisher for the .NET Platform
diff --git a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.Generated.cs b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.Generated.cs
new file mode 100644
index 000000000000..51cc8774cceb
--- /dev/null
+++ b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.Generated.cs
@@ -0,0 +1,236 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.DotNet.Cli.Commands.Run;
+
+// Generated by test `RunFileTests.CscArguments`.
+partial class CSharpCompilerCommand
+{
+ private IEnumerable GetCscArguments(
+ string fileNameWithoutExtension,
+ string objDir,
+ string binDir)
+ {
+ return
+ [
+ "/unsafe-",
+ "/checked-",
+ "/nowarn:1701,1702,IL2121,1701,1702",
+ "/fullpaths",
+ "/nostdlib+",
+ "/errorreport:prompt",
+ "/warn:10",
+ "/define:TRACE;DEBUG;NET;NET10_0;NETCOREAPP;NET5_0_OR_GREATER;NET6_0_OR_GREATER;NET7_0_OR_GREATER;NET8_0_OR_GREATER;NET9_0_OR_GREATER;NET10_0_OR_GREATER;NETCOREAPP1_0_OR_GREATER;NETCOREAPP1_1_OR_GREATER;NETCOREAPP2_0_OR_GREATER;NETCOREAPP2_1_OR_GREATER;NETCOREAPP2_2_OR_GREATER;NETCOREAPP3_0_OR_GREATER;NETCOREAPP3_1_OR_GREATER",
+ "/highentropyva+",
+ "/nullable:enable",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/Microsoft.CSharp.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/Microsoft.VisualBasic.Core.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/Microsoft.VisualBasic.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/Microsoft.Win32.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/Microsoft.Win32.Registry.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/mscorlib.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/netstandard.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.AppContext.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Buffers.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Collections.Concurrent.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Collections.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Collections.Immutable.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Collections.NonGeneric.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Collections.Specialized.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.Annotations.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.DataAnnotations.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.EventBasedAsync.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ComponentModel.TypeConverter.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Configuration.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Console.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Core.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Data.Common.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Data.DataSetExtensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Data.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.Contracts.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.Debug.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.DiagnosticSource.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.FileVersionInfo.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.Process.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.StackTrace.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.TextWriterTraceListener.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.Tools.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.TraceSource.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Diagnostics.Tracing.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Drawing.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Drawing.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Dynamic.Runtime.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Formats.Asn1.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Formats.Tar.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Globalization.Calendars.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Globalization.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Globalization.Extensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Compression.Brotli.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Compression.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Compression.FileSystem.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Compression.ZipFile.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.FileSystem.AccessControl.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.FileSystem.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.FileSystem.DriveInfo.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.FileSystem.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.FileSystem.Watcher.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.IsolatedStorage.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.MemoryMappedFiles.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Pipelines.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Pipes.AccessControl.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.Pipes.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.IO.UnmanagedMemoryStream.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Linq.AsyncEnumerable.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Linq.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Linq.Expressions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Linq.Parallel.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Linq.Queryable.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Memory.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Http.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Http.Json.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.HttpListener.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Mail.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.NameResolution.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.NetworkInformation.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Ping.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Quic.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Requests.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Security.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.ServerSentEvents.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.ServicePoint.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.Sockets.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.WebClient.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.WebHeaderCollection.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.WebProxy.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.WebSockets.Client.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Net.WebSockets.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Numerics.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Numerics.Vectors.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ObjectModel.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.DispatchProxy.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Emit.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Emit.ILGeneration.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Emit.Lightweight.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Extensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Metadata.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Reflection.TypeExtensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Resources.Reader.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Resources.ResourceManager.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Resources.Writer.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.CompilerServices.Unsafe.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.CompilerServices.VisualC.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Extensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Handles.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.InteropServices.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.InteropServices.JavaScript.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.InteropServices.RuntimeInformation.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Intrinsics.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Loader.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Numerics.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Serialization.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Serialization.Formatters.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Serialization.Json.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Serialization.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Runtime.Serialization.Xml.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.AccessControl.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Claims.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.Algorithms.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.Cng.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.Csp.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.Encoding.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.OpenSsl.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.Primitives.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Cryptography.X509Certificates.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Principal.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.Principal.Windows.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Security.SecureString.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ServiceModel.Web.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ServiceProcess.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.Encoding.CodePages.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.Encoding.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.Encoding.Extensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.Encodings.Web.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.Json.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Text.RegularExpressions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.AccessControl.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Channels.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Overlapped.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Tasks.Dataflow.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Tasks.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Tasks.Extensions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Tasks.Parallel.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Thread.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.ThreadPool.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Threading.Timer.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Transactions.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Transactions.Local.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.ValueTuple.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Web.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Web.HttpUtility.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Windows.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.Linq.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.ReaderWriter.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.Serialization.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.XDocument.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.XmlDocument.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.XmlSerializer.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.XPath.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/System.Xml.XPath.XDocument.dll",
+ $"/reference:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/ref/net10.0/WindowsBase.dll",
+ "/debug+",
+ "/debug:portable",
+ "/filealign:512",
+ "/optimize-",
+ $"/out:{binDir}/{fileNameWithoutExtension}.dll",
+ "/target:exe",
+ "/warnaserror-",
+ "/utf8output",
+ "/deterministic+",
+ "/langversion:13.0",
+ "/features:FileBasedProgram",
+ $"/analyzerconfig:{SdkPath}/Sdks/Microsoft.NET.Sdk/codestyle/cs/build/config/analysislevelstyle_default.globalconfig",
+ $"/analyzerconfig:{objDir}/{fileNameWithoutExtension}.GeneratedMSBuildEditorConfig.editorconfig",
+ $"/analyzerconfig:{SdkPath}/Sdks/Microsoft.NET.Sdk/analyzers/build/config/analysislevel_10_default.globalconfig",
+ $"/analyzer:{SdkPath}/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.CSharp.NetAnalyzers.dll",
+ $"/analyzer:{SdkPath}/Sdks/Microsoft.NET.Sdk/targets/../analyzers/Microsoft.CodeAnalysis.NetAnalyzers.dll",
+ $"/analyzer:{NuGetCachePath}/microsoft.net.illink.tasks/{RuntimeVersion}/analyzers/dotnet/cs/ILLink.CodeFixProvider.dll",
+ $"/analyzer:{NuGetCachePath}/microsoft.net.illink.tasks/{RuntimeVersion}/analyzers/dotnet/cs/ILLink.RoslynAnalyzer.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/Microsoft.Interop.ComInterfaceGenerator.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/Microsoft.Interop.JavaScript.JSImportGenerator.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/Microsoft.Interop.LibraryImportGenerator.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/Microsoft.Interop.SourceGeneration.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/System.Text.Json.SourceGeneration.dll",
+ $"/analyzer:{DotNetRootPath}/packs/Microsoft.NETCore.App.Ref/{RuntimeVersion}/analyzers/dotnet/cs/System.Text.RegularExpressions.Generator.dll",
+ $"{EntryPointFileFullPath}",
+ $"{objDir}/{fileNameWithoutExtension}.GlobalUsings.g.cs",
+ $"{objDir}/.NETCoreApp,Version=v10.0.AssemblyAttributes.cs",
+ $"{objDir}/{fileNameWithoutExtension}.AssemblyInfo.cs",
+ "/warnaserror+:NU1605,SYSLIB0011",
+ ];
+ }
+
+ ///
+ /// Files that come from referenced NuGet packages (e.g., analyzers for NativeAOT) need to be checked specially (if they don't exist, MSBuild needs to run).
+ ///
+ public static IEnumerable GetPathsOfCscInputsFromNuGetCache()
+ {
+ return
+ [
+ $"{NuGetCachePath}/microsoft.net.illink.tasks/{RuntimeVersion}/analyzers/dotnet/cs/ILLink.CodeFixProvider.dll",
+ $"{NuGetCachePath}/microsoft.net.illink.tasks/{RuntimeVersion}/analyzers/dotnet/cs/ILLink.RoslynAnalyzer.dll",
+ ];
+ }
+}
diff --git a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs
new file mode 100644
index 000000000000..c07736b79d79
--- /dev/null
+++ b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs
@@ -0,0 +1,356 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Collections.Immutable;
+using System.Diagnostics;
+using System.Text.Json;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CommandLine;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.DotNet.Cli.Utils;
+using Microsoft.DotNet.Cli.Utils.Extensions;
+using Microsoft.NET.HostModel.AppHost;
+using NuGet.Configuration;
+
+namespace Microsoft.DotNet.Cli.Commands.Run;
+
+///
+/// Used to invoke C# compiler in some optimized paths of dotnet run file.cs.
+///
+internal sealed partial class CSharpCompilerCommand
+{
+ private static readonly SearchValues s_additionalShouldSurroundWithQuotes = SearchValues.Create('=', ',');
+
+ ///
+ /// Options which denote paths and which might appear in the simple app compilation that we optimize for.
+ ///
+ private static readonly ImmutableArray s_pathOptions =
+ [
+ "reference:",
+ "analyzer:",
+ "additionalfile:",
+ "analyzerconfig:",
+ "embed:",
+ "resource:",
+ "linkresource:",
+ "ruleset:",
+ "keyfile:",
+ "link:",
+ ];
+
+ private static string SdkPath => field ??= PathUtility.EnsureNoTrailingDirectorySeparator(AppContext.BaseDirectory);
+ private static string DotNetRootPath => field ??= Path.GetDirectoryName(Path.GetDirectoryName(SdkPath)!)!;
+ private static string ClientDirectory => field ??= Path.Combine(SdkPath, "Roslyn", "bincore");
+ private static string NuGetCachePath => field ??= SettingsUtility.GetGlobalPackagesFolder(Settings.LoadDefaultSettings(null));
+ internal static string RuntimeVersion => field ??= RuntimeInformation.FrameworkDescription.Split(' ').Last();
+ private static string TargetFrameworkVersion => Product.TargetFrameworkVersion;
+
+ public required string EntryPointFileFullPath { get; init; }
+ public required string ArtifactsPath { get; init; }
+ public required bool CanReuseAuxiliaryFiles { get; init; }
+
+ ///
+ /// Whether the returned error code should not cause the build to fail but instead fallback to full MSBuild.
+ ///
+ public int Execute(out bool fallbackToNormalBuild)
+ {
+ // Write .rsp file and other intermediate build outputs.
+ PrepareAuxiliaryFiles(out string rspPath);
+
+ // Create a request for the compiler server
+ // (this is much faster than starting a csc.dll process, especially on Windows).
+ var buildRequest = BuildServerConnection.CreateBuildRequest(
+ requestId: EntryPointFileFullPath,
+ language: RequestLanguage.CSharpCompile,
+ arguments: ["/noconfig", "/nologo", $"@{EscapeSingleArg(rspPath)}"],
+ workingDirectory: Environment.CurrentDirectory,
+ tempDirectory: Path.GetTempPath(),
+ keepAlive: null,
+ libDirectory: null,
+ compilerHash: GetCompilerCommitHash());
+
+ // Get pipe name.
+ var pipeName = BuildServerConnection.GetPipeName(clientDirectory: ClientDirectory);
+
+ // Create logger.
+ var logger = new CompilerServerLogger(
+ identifier: $"dotnet run file {Environment.ProcessId}",
+ loggingFilePath: null);
+
+ // Send the request.
+ var responseTask = BuildServerConnection.RunServerBuildRequestAsync(
+ buildRequest,
+ pipeName: pipeName,
+ clientDirectory: ClientDirectory,
+ logger,
+ cancellationToken: default);
+
+ // Process the response.
+ return ProcessBuildResponse(responseTask.Result, out fallbackToNormalBuild);
+
+ static string GetCompilerCommitHash()
+ {
+ return typeof(CSharpCompilation).Assembly.GetCustomAttributesData()
+ .FirstOrDefault(attr => attr.AttributeType.FullName == "Microsoft.CodeAnalysis.CommitHashAttribute")?
+ .ConstructorArguments
+ .FirstOrDefault()
+ .Value as string
+ ?? throw new InvalidOperationException("Could not find compiler commit hash in the assembly attributes.");
+ }
+
+ static int ProcessBuildResponse(BuildResponse response, out bool fallbackToNormalBuild)
+ {
+ switch (response)
+ {
+ case CompletedBuildResponse completed:
+ Reporter.Verbose.WriteLine("Compiler server processed compilation.");
+ Reporter.Output.Write(completed.Output);
+ fallbackToNormalBuild = false;
+ return completed.ReturnCode;
+
+ case IncorrectHashBuildResponse:
+ Reporter.Error.WriteLine("Error: Compiler server reports a different hash version than the SDK.".Red());
+ fallbackToNormalBuild = false;
+ return 1;
+
+ case null:
+ Reporter.Output.WriteLine("Warning: Could not launch the compiler server.".Yellow());
+ fallbackToNormalBuild = true;
+ return 1;
+
+ default:
+ Reporter.Error.WriteLine($"Warning: Compiler server returned unexpected response: {response.GetType().Name}".Yellow());
+ fallbackToNormalBuild = true;
+ return 1;
+ }
+ }
+ }
+
+ private void PrepareAuxiliaryFiles(out string rspPath)
+ {
+ Reporter.Verbose.WriteLine(CanReuseAuxiliaryFiles
+ ? "CSC auxiliary files can be reused."
+ : "CSC auxiliary files can NOT be reused.");
+
+ string fileDirectory = Path.GetDirectoryName(EntryPointFileFullPath) ?? string.Empty;
+ string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(EntryPointFileFullPath);
+
+ // Note that Release builds won't go through this optimized code path because `-c Release` translates to global property `Configuration=Release`
+ // and customizing global properties triggers a full MSBuild run.
+ string objDir = Path.Join(ArtifactsPath, "obj", "debug");
+ Directory.CreateDirectory(objDir);
+ string binDir = Path.Join(ArtifactsPath, "bin", "debug");
+ Directory.CreateDirectory(binDir);
+
+ string assemblyAttributes = Path.Join(objDir, $".NETCoreApp,Version=v{TargetFrameworkVersion}.AssemblyAttributes.cs");
+ if (ShouldEmit(assemblyAttributes))
+ {
+ File.WriteAllText(assemblyAttributes, /* lang=C#-test */ $"""
+ //
+ using System;
+ using System.Reflection;
+ [assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v{TargetFrameworkVersion}", FrameworkDisplayName = ".NET {TargetFrameworkVersion}")]
+
+ """);
+ }
+
+ string globalUsings = Path.Join(objDir, $"{fileNameWithoutExtension}.GlobalUsings.g.cs");
+ if (ShouldEmit(globalUsings))
+ {
+ File.WriteAllText(globalUsings, /* lang=C#-test */ """
+ //
+ global using System;
+ global using System.Collections.Generic;
+ global using System.IO;
+ global using System.Linq;
+ global using System.Net.Http;
+ global using System.Threading;
+ global using System.Threading.Tasks;
+
+ """);
+ }
+
+ string assemblyInfo = Path.Join(objDir, $"{fileNameWithoutExtension}.AssemblyInfo.cs");
+ if (ShouldEmit(assemblyInfo))
+ {
+ File.WriteAllText(assemblyInfo, /* lang=C#-test */ $"""
+ //------------------------------------------------------------------------------
+ //
+ // This code was generated by a tool.
+ //
+ // Changes to this file may cause incorrect behavior and will be lost if
+ // the code is regenerated.
+ //
+ //------------------------------------------------------------------------------
+
+ using System;
+ using System.Reflection;
+
+ [assembly: System.Reflection.AssemblyCompanyAttribute("{fileNameWithoutExtension}")]
+ [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
+ [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
+ [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0")]
+ [assembly: System.Reflection.AssemblyProductAttribute("{fileNameWithoutExtension}")]
+ [assembly: System.Reflection.AssemblyTitleAttribute("{fileNameWithoutExtension}")]
+ [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
+
+ // Generated by the MSBuild WriteCodeFragment class.
+
+
+ """);
+ }
+
+ string editorconfig = Path.Join(objDir, $"{fileNameWithoutExtension}.GeneratedMSBuildEditorConfig.editorconfig");
+ if (ShouldEmit(editorconfig))
+ {
+ File.WriteAllText(editorconfig, $"""
+ is_global = true
+ build_property.EnableAotAnalyzer = true
+ build_property.EnableSingleFileAnalyzer = true
+ build_property.EnableTrimAnalyzer = true
+ build_property.IncludeAllContentForSelfExtract =
+ build_property.TargetFramework = net{TargetFrameworkVersion}
+ build_property.TargetFrameworkIdentifier = .NETCoreApp
+ build_property.TargetFrameworkVersion = v{TargetFrameworkVersion}
+ build_property.TargetPlatformMinVersion =
+ build_property.UsingMicrosoftNETSdkWeb =
+ build_property.ProjectTypeGuids =
+ build_property.InvariantGlobalization =
+ build_property.PlatformNeutralAssembly =
+ build_property.EnforceExtendedAnalyzerRules =
+ build_property._SupportedPlatformList = Linux,macOS,Windows
+ build_property.RootNamespace = {fileNameWithoutExtension}
+ build_property.ProjectDir = {fileDirectory}{Path.DirectorySeparatorChar}
+ build_property.EnableComHosting =
+ build_property.EnableGeneratedComInterfaceComImportInterop = false
+ build_property.EffectiveAnalysisLevelStyle = {TargetFrameworkVersion}
+ build_property.EnableCodeStyleSeverity =
+
+ """);
+ }
+
+ var apphostTarget = Path.Join(binDir, $"{fileNameWithoutExtension}{FileNameSuffixes.CurrentPlatform.Exe}");
+ if (ShouldEmit(apphostTarget))
+ {
+ var rid = RuntimeInformation.RuntimeIdentifier;
+ var apphostSource = Path.Join(SdkPath, "..", "..", "packs", $"Microsoft.NETCore.App.Host.{rid}", RuntimeVersion, "runtimes", rid, "native", $"apphost{FileNameSuffixes.CurrentPlatform.Exe}");
+ HostWriter.CreateAppHost(
+ appHostSourceFilePath: apphostSource,
+ appHostDestinationFilePath: apphostTarget,
+ appBinaryFilePath: $"{fileNameWithoutExtension}.dll",
+ enableMacOSCodeSign: OperatingSystem.IsMacOS());
+ }
+
+ var runtimeConfig = Path.Join(binDir, $"{fileNameWithoutExtension}{FileNameSuffixes.RuntimeConfigJson}");
+ if (ShouldEmit(runtimeConfig))
+ {
+ File.WriteAllText(runtimeConfig, $$"""
+ {
+ "runtimeOptions": {
+ "tfm": "net{{TargetFrameworkVersion}}",
+ "framework": {
+ "name": "Microsoft.NETCore.App",
+ "version": {{JsonSerializer.Serialize(RuntimeVersion)}}
+ },
+ "configProperties": {
+ "EntryPointFilePath": {{JsonSerializer.Serialize(EntryPointFileFullPath)}},
+ "EntryPointFileDirectoryPath": {{JsonSerializer.Serialize(fileDirectory)}},
+ "Microsoft.Extensions.DependencyInjection.VerifyOpenGenericServiceTrimmability": true,
+ "System.ComponentModel.DefaultValueAttribute.IsSupported": false,
+ "System.ComponentModel.Design.IDesignerHost.IsSupported": false,
+ "System.ComponentModel.TypeConverter.EnableUnsafeBinaryFormatterInDesigntimeLicenseContextSerialization": false,
+ "System.ComponentModel.TypeDescriptor.IsComObjectDescriptorSupported": false,
+ "System.Data.DataSet.XmlSerializationIsSupported": false,
+ "System.Diagnostics.Tracing.EventSource.IsSupported": false,
+ "System.Linq.Enumerable.IsSizeOptimized": true,
+ "System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
+ "System.Resources.ResourceManager.AllowCustomResourceTypes": false,
+ "System.Resources.UseSystemResourceKeys": false,
+ "System.Runtime.CompilerServices.RuntimeFeature.IsDynamicCodeSupported": false,
+ "System.Runtime.InteropServices.BuiltInComInterop.IsSupported": false,
+ "System.Runtime.InteropServices.EnableConsumingManagedCodeFromNativeHosting": false,
+ "System.Runtime.InteropServices.EnableCppCLIHostActivation": false,
+ "System.Runtime.InteropServices.Marshalling.EnableGeneratedComInterfaceComImportInterop": false,
+ "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false,
+ "System.StartupHookProvider.IsSupported": false,
+ "System.Text.Encoding.EnableUnsafeUTF7Encoding": false,
+ "System.Text.Json.JsonSerializer.IsReflectionEnabledByDefault": false,
+ "System.Threading.Thread.EnableAutoreleasePool": false,
+ "System.Linq.Expressions.CanEmitObjectArrayDelegate": false
+ }
+ }
+ }
+ """);
+ }
+
+ rspPath = Path.Join(ArtifactsPath, "csc.rsp");
+ if (ShouldEmit(rspPath))
+ {
+ IEnumerable args = GetCscArguments(
+ fileNameWithoutExtension: fileNameWithoutExtension,
+ objDir: objDir,
+ binDir: binDir);
+
+ File.WriteAllLines(rspPath, args.Select(EscapeSingleArg));
+ }
+
+ bool ShouldEmit(string file)
+ {
+ if (!CanReuseAuxiliaryFiles)
+ {
+ return true;
+ }
+
+ if (!File.Exists(file))
+ {
+ Reporter.Verbose.WriteLine($"Generating CSC auxiliary file because it does not exist: {file}");
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ private static string EscapeSingleArg(string arg)
+ {
+ if (IsPathOption(arg, out var colonIndex))
+ {
+ return arg[..(colonIndex + 1)] + EscapeCore(arg[(colonIndex + 1)..]);
+ }
+
+ return EscapeCore(arg);
+
+ static string EscapeCore(string arg)
+ {
+ return ArgumentEscaper.EscapeSingleArg(arg, additionalShouldSurroundWithQuotes: static (string arg) =>
+ {
+ return arg.ContainsAny(s_additionalShouldSurroundWithQuotes);
+ });
+ }
+ }
+
+ public static bool IsPathOption(string arg, out int colonIndex)
+ {
+ if (!arg.StartsWith('/'))
+ {
+ colonIndex = -1;
+ return false;
+ }
+
+ var span = arg.AsSpan(start: 1);
+ foreach (var optionName in s_pathOptions)
+ {
+ Debug.Assert(!optionName.StartsWith('/') && optionName.EndsWith(':'));
+
+ if (span.StartsWith(optionName, StringComparison.OrdinalIgnoreCase))
+ {
+ colonIndex = optionName.Length;
+ return true;
+ }
+ }
+
+ colonIndex = -1;
+ return false;
+ }
+}
diff --git a/src/Cli/dotnet/Commands/Run/RunCommand.cs b/src/Cli/dotnet/Commands/Run/RunCommand.cs
index efdf946c313e..73ca0f93f8ff 100644
--- a/src/Cli/dotnet/Commands/Run/RunCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/RunCommand.cs
@@ -275,8 +275,8 @@ private void EnsureProjectIsBuilt(out Func?
if (EntryPointFileFullPath is not null)
{
var command = CreateVirtualCommand();
- projectFactory = command.CreateProjectInstance;
buildResult = command.Execute();
+ projectFactory = command.LastBuildLevel is BuildLevel.Csc ? null : command.CreateProjectInstance;
}
else
{
@@ -340,6 +340,14 @@ internal static VerbosityOptions GetDefaultVerbosity(bool interactive)
internal ICommand GetTargetCommand(Func? projectFactory)
{
+ if (projectFactory is null && ProjectFileFullPath is null)
+ {
+ // If we are running a file-based app and projectFactory is null, it means csc was used instead of full msbuild.
+ // So we can skip project evaluation to continue the optimized path.
+ Debug.Assert(EntryPointFileFullPath is not null);
+ return CreateCommandForCscBuiltProgram(EntryPointFileFullPath);
+ }
+
FacadeLogger? logger = LoggerUtility.DetermineBinlogger([..MSBuildArgs.OtherMSBuildArgs], "dotnet-run");
var project = EvaluateProject(ProjectFileFullPath, projectFactory, MSBuildArgs, logger);
ValidatePreconditions(project);
@@ -394,15 +402,41 @@ static ICommand CreateCommandFromRunProperties(ProjectInstance project, RunPrope
var command = CommandFactoryUsingResolver.Create(commandSpec)
.WorkingDirectory(runProperties.RunWorkingDirectory);
- var rootVariableName = EnvironmentVariableNames.TryGetDotNetRootVariableName(
+ SetRootVariableName(
+ command,
project.GetPropertyValue("RuntimeIdentifier"),
project.GetPropertyValue("DefaultAppHostRuntimeIdentifier"),
project.GetPropertyValue("TargetFrameworkVersion"));
+ return command;
+ }
+
+ static void SetRootVariableName(ICommand command, string runtimeIdentifier, string defaultAppHostRuntimeIdentifier, string targetFrameworkVersion)
+ {
+ var rootVariableName = EnvironmentVariableNames.TryGetDotNetRootVariableName(
+ runtimeIdentifier,
+ defaultAppHostRuntimeIdentifier,
+ targetFrameworkVersion);
if (rootVariableName != null && Environment.GetEnvironmentVariable(rootVariableName) == null)
{
command.EnvironmentVariable(rootVariableName, Path.GetDirectoryName(new Muxer().MuxerPath));
}
+ }
+
+ static ICommand CreateCommandForCscBuiltProgram(string entryPointFileFullPath)
+ {
+ var artifactsPath = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointFileFullPath);
+ var exePath = Path.Join(artifactsPath, "bin", "debug", Path.GetFileNameWithoutExtension(entryPointFileFullPath) + FileNameSuffixes.CurrentPlatform.Exe);
+ var commandSpec = new CommandSpec(path: exePath, args: null);
+ var command = CommandFactoryUsingResolver.Create(commandSpec)
+ .WorkingDirectory(Path.GetDirectoryName(entryPointFileFullPath));
+
+ SetRootVariableName(
+ command,
+ runtimeIdentifier: RuntimeInformation.RuntimeIdentifier,
+ defaultAppHostRuntimeIdentifier: RuntimeInformation.RuntimeIdentifier,
+ targetFrameworkVersion: $"v{VirtualProjectBuildingCommand.TargetFrameworkVersion}");
+
return command;
}
diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
index d042e5f638b6..838b590b2ab6 100644
--- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
+++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Buffers;
-using System.Collections.Frozen;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using System.Diagnostics;
@@ -44,23 +42,46 @@ internal sealed class VirtualProjectBuildingCommand : CommandBase
///
private const string BuildSuccessCacheFileName = "build-success.cache";
- private static readonly ImmutableArray s_implicitBuildFileNames =
+ ///
+ /// IsMSBuildFile is if the presence of the implicit build file (even if there are no s)
+ /// implies that CSC is not enough and MSBuild is needed to build the project, i.e., the file alone can affect MSBuild props or targets.
+ ///
+ ///
+ /// For example, the simple programs our CSC optimized path handles do not need NuGet restore, hence we can ignore NuGet config files.
+ ///
+ private static readonly ImmutableArray<(string Name, bool IsMSBuildFile)> s_implicitBuildFiles =
[
- "global.json",
+ ("global.json", false),
// All these casings are recognized on case-sensitive platforms:
// https://github.com/NuGet/NuGet.Client/blob/ab6b96fd9ba07ed3bf629ee389799ca4fb9a20fb/src/NuGet.Core/NuGet.Configuration/Settings/Settings.cs#L32-L37
- "nuget.config",
- "NuGet.config",
- "NuGet.Config",
-
- "Directory.Build.props",
- "Directory.Build.targets",
- "Directory.Packages.props",
- "Directory.Build.rsp",
- "MSBuild.rsp",
+ ("nuget.config", false),
+ ("NuGet.config", false),
+ ("NuGet.Config", false),
+
+ ("Directory.Build.props", true),
+ ("Directory.Build.targets", true),
+ ("Directory.Packages.props", true),
+ ("Directory.Build.rsp", true),
+ ("MSBuild.rsp", true),
+ ];
+
+ ///
+ /// For purposes of determining whether CSC is enough to build as opposed to full MSBuild,
+ /// we can ignore properties that do not affect the build on their own.
+ /// See also the IsMSBuildFile flag in .
+ ///
+ ///
+ /// This is an rather than to avoid boxing at the use site.
+ ///
+ private static readonly IEnumerable s_ignorableProperties =
+ [
+ // This is set by default by `dotnet run`, so it must be ignored otherwise the CSC optimization would not kick in by default.
+ "NuGetInteractive",
];
+ public static string TargetFrameworkVersion => Product.TargetFrameworkVersion;
+
internal static readonly string TargetOverrides = """
+
diff --git a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs
index edb3e4fe661e..fd980814d9de 100644
--- a/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs
+++ b/test/Microsoft.NET.Publish.Tests/GivenThatWeWantToPublishAHelloWorldProject.cs
@@ -1174,17 +1174,7 @@ public static void Main()
var result = runCommand.Execute();
if (expectedRoot != null)
{
- // SDK tests use /tmp for test assets. On macOS, it is a symlink - the app will print the resolved path
- if (OperatingSystem.IsMacOS())
- {
- string tmpPath = "/tmp/";
- DirectoryInfo tmp = new DirectoryInfo(tmpPath[..^1]); // No trailing slash in order to properly check the link target
- if (tmp.LinkTarget != null && expectedRoot.StartsWith(tmpPath))
- {
- expectedRoot = Path.Combine(tmp.ResolveLinkTarget(true).FullName, expectedRoot[tmpPath.Length..]);
- }
- }
-
+ expectedRoot = TestPathUtility.ResolveTempPrefixLink(expectedRoot);
result.Should().Pass()
.And.HaveStdOutContaining($"Runtime directory: {expectedRoot}");
}
diff --git a/test/Microsoft.NET.TestFramework/TestAssetsManager.cs b/test/Microsoft.NET.TestFramework/TestAssetsManager.cs
index 52809ebe67ca..e05bdbe22572 100644
--- a/test/Microsoft.NET.TestFramework/TestAssetsManager.cs
+++ b/test/Microsoft.NET.TestFramework/TestAssetsManager.cs
@@ -151,9 +151,9 @@ private TestAsset CreateTestProjectsInDirectory(
return testAsset;
}
- public TestDirectory CreateTestDirectory([CallerMemberName] string? testName = null, string? identifier = null)
+ public TestDirectory CreateTestDirectory([CallerMemberName] string? testName = null, string? identifier = null, string? baseDirectory = null)
{
- string dir = GetTestDestinationDirectoryPath(testName, testName, identifier ?? string.Empty);
+ string dir = GetTestDestinationDirectoryPath(testName, testName, identifier ?? string.Empty, baseDirectory: baseDirectory);
return new TestDirectory(dir, TestContext.Current.SdkVersion);
}
@@ -177,9 +177,10 @@ public static string GetTestDestinationDirectoryPath(
string? testProjectName,
string? callingMethodAndFileName,
string? identifier,
- bool allowCopyIfPresent = false)
+ bool allowCopyIfPresent = false,
+ string? baseDirectory = null)
{
- string? baseDirectory = TestContext.Current.TestExecutionDirectory;
+ baseDirectory ??= TestContext.Current.TestExecutionDirectory;
var directoryName = new StringBuilder(callingMethodAndFileName).Append(identifier);
if (testProjectName != callingMethodAndFileName)
diff --git a/test/Microsoft.NET.TestFramework/ToolsetInfo.cs b/test/Microsoft.NET.TestFramework/ToolsetInfo.cs
index 6290312c17d1..d063e6b30177 100644
--- a/test/Microsoft.NET.TestFramework/ToolsetInfo.cs
+++ b/test/Microsoft.NET.TestFramework/ToolsetInfo.cs
@@ -68,6 +68,8 @@ public string? MSBuildVersion
public string? SdkResolverPath { get; set; }
+ public string? RepoRoot { get; set; }
+
public ToolsetInfo(string dotNetRoot)
{
DotNetRoot = dotNetRoot;
@@ -291,7 +293,10 @@ public static ToolsetInfo Create(string? repoRoot, string? repoArtifactsDir, str
throw new FileNotFoundException($"Host '{dotnetHost}' not found. {hostNotFoundReason}");
}
- var ret = new ToolsetInfo(dotnetRoot);
+ var ret = new ToolsetInfo(dotnetRoot)
+ {
+ RepoRoot = repoRoot,
+ };
if (!string.IsNullOrEmpty(commandLine.FullFrameworkMSBuildPath))
{
diff --git a/test/Microsoft.NET.TestFramework/Utilities/TestPathUtility.cs b/test/Microsoft.NET.TestFramework/Utilities/TestPathUtility.cs
new file mode 100644
index 000000000000..46971a48fd35
--- /dev/null
+++ b/test/Microsoft.NET.TestFramework/Utilities/TestPathUtility.cs
@@ -0,0 +1,28 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace Microsoft.NET.TestFramework.Utilities;
+
+public static class TestPathUtility
+{
+#if NET
+ ///
+ /// For path like /tmp/something, returns /private/tmp/something on macOS.
+ ///
+ public static string ResolveTempPrefixLink(string path)
+ {
+ // SDK tests use /tmp for test assets. On macOS, it is a symlink - the app will print the resolved path
+ if (OperatingSystem.IsMacOS())
+ {
+ string tmpPath = "/tmp/";
+ var tmp = new DirectoryInfo(tmpPath[..^1]); // No trailing slash in order to properly check the link target
+ if (tmp.LinkTarget != null && path.StartsWith(tmpPath) && tmp.ResolveLinkTarget(true) is { } linkTarget)
+ {
+ return Path.Combine(linkTarget.FullName, path[tmpPath.Length..]);
+ }
+ }
+
+ return path;
+ }
+#endif
+}
diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
index a7887db1c0fb..d793ec056950 100644
--- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
+++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
@@ -3,11 +3,13 @@
using System.Runtime.Versioning;
using System.Text.Json;
+using Basic.CompilerLog.Util;
using Microsoft.Build.Framework;
using Microsoft.Build.Logging.StructuredLogger;
using Microsoft.CodeAnalysis;
using Microsoft.DotNet.Cli.Commands;
using Microsoft.DotNet.Cli.Commands.Run;
+using Microsoft.DotNet.Cli.Utils;
namespace Microsoft.DotNet.Cli.Run.Tests;
@@ -74,6 +76,12 @@ public static string GetMessage()
}
""";
+ ///
+ /// Used when we need an out-of-tree base test directory to avoid having implicit build files
+ /// like Directory.Build.props in scope and negating the optimizations we want to test.
+ ///
+ private static string OutOfTreeBaseDirectory => field ??= PrepareOutOfTreeBaseDirectory();
+
private static bool HasCaseInsensitiveFileSystem
{
get
@@ -83,6 +91,25 @@ private static bool HasCaseInsensitiveFileSystem
}
}
+ ///
+ private static string PrepareOutOfTreeBaseDirectory()
+ {
+ string outOfTreeBaseDirectory = TestPathUtility.ResolveTempPrefixLink(Path.Join(Path.GetTempPath(), "dotnetSdkTests"));
+ Directory.CreateDirectory(outOfTreeBaseDirectory);
+
+ // Create NuGet.config in our out-of-tree base directory.
+ var sourceNuGetConfig = Path.Join(TestContext.Current.TestExecutionDirectory, "NuGet.config");
+ var targetNuGetConfig = Path.Join(outOfTreeBaseDirectory, "NuGet.config");
+ File.Copy(sourceNuGetConfig, targetNuGetConfig, overwrite: true);
+
+ // Check there are no implicit build files that would prevent testing optimizations.
+ VirtualProjectBuildingCommand.CollectImplicitBuildFiles(new DirectoryInfo(outOfTreeBaseDirectory), [], out var exampleMSBuildFile);
+ exampleMSBuildFile.Should().BeNull(because: "there should not be any implicit build files in the temp directory or its parents " +
+ "so we can test optimizations that would be disabled with implicit build files present");
+
+ return outOfTreeBaseDirectory;
+ }
+
///
/// dotnet run file.cs succeeds without a project file.
///
@@ -1406,10 +1433,10 @@ public void ArtifactsDirectory_Permissions()
.Should().Be(actualMode, artifactsDir);
}
- [Fact]
- public void LaunchProfile()
+ [Theory, CombinatorialData]
+ public void LaunchProfile(bool cscOnly)
{
- var testInstance = _testAssetsManager.CreateTestDirectory();
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: cscOnly ? OutOfTreeBaseDirectory : null);
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program + """
Console.WriteLine($"Message: '{Environment.GetEnvironmentVariable("Message")}'");
@@ -1417,29 +1444,35 @@ public void LaunchProfile()
Directory.CreateDirectory(Path.Join(testInstance.Path, "Properties"));
File.WriteAllText(Path.Join(testInstance.Path, "Properties", "launchSettings.json"), s_launchSettings);
- new DotnetCommand(Log, "run", "--no-launch-profile", "Program.cs")
+ var prefix = cscOnly
+ ? CliCommandStrings.NoBinaryLogBecauseRunningJustCsc + Environment.NewLine
+ : string.Empty;
+
+ new DotnetCommand(Log, "run", "-bl", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
- .And.HaveStdOut("""
+ .And.HaveStdOutContaining(prefix + """
Hello from Program
- Message: ''
+ Message: 'TestProfileMessage1'
""");
- new DotnetCommand(Log, "run", "Program.cs")
+ prefix = CliCommandStrings.NoBinaryLogBecauseUpToDate + Environment.NewLine;
+
+ new DotnetCommand(Log, "run", "-bl", "--no-launch-profile", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
- .And.HaveStdOutContaining("""
+ .And.HaveStdOut(prefix + """
Hello from Program
- Message: 'TestProfileMessage1'
+ Message: ''
""");
- new DotnetCommand(Log, "run", "-lp", "TestProfile2", "Program.cs")
+ new DotnetCommand(Log, "run", "-bl", "-lp", "TestProfile2", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
- .And.HaveStdOutContaining("""
+ .And.HaveStdOutContaining(prefix + """
Hello from Program
Message: 'TestProfileMessage2'
""");
@@ -1649,29 +1682,397 @@ public void ProjectReference_Errors()
string.Format(CliStrings.MoreThanOneProjectInDirectory, Path.Join(testInstance.Path, "dir/"))));
}
+ ///
+ /// Verifies that msbuild-based runs use CSC args equivalent to csc-only runs.
+ /// Can regenerate CSC arguments template in .
+ ///
+ [Fact]
+ public void CscArguments()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ const string programName = "TestProgram";
+ const string fileName = $"{programName}.cs";
+ string entryPointPath = Path.Join(testInstance.Path, fileName);
+ File.WriteAllText(entryPointPath, s_program);
+
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointPath);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ // Build using MSBuild.
+ new DotnetCommand(Log, "run", fileName, "-bl", "--no-cache")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut($"Hello from {programName}");
+
+ // Find the csc args used by the build.
+ var msbuildCall = FindCompilerCall(Path.Join(testInstance.Path, "msbuild.binlog"));
+ var msbuildCallArgs = msbuildCall.GetArguments();
+ var msbuildCallArgsString = ArgumentEscaper.EscapeAndConcatenateArgArrayForProcessStart(msbuildCallArgs);
+
+ // Generate argument template code.
+ string sdkPath = NormalizePath(TestContext.Current.ToolsetUnderTest.SdkFolderUnderTest);
+ string dotNetRootPath = NormalizePath(TestContext.Current.ToolsetUnderTest.DotNetRoot);
+ string nuGetCachePath = NormalizePath(TestContext.Current.NuGetCachePath!);
+ string artifactsDirNormalized = NormalizePath(artifactsDir);
+ string objPath = $"{artifactsDirNormalized}/obj/debug";
+ string entryPointPathNormalized = NormalizePath(entryPointPath);
+ var msbuildArgsToVerify = new List();
+ var nuGetPackageFilePaths = new List();
+ var code = new StringBuilder();
+ code.AppendLine($$"""
+ // Licensed to the .NET Foundation under one or more agreements.
+ // The .NET Foundation licenses this file to you under the MIT license.
+
+ namespace Microsoft.DotNet.Cli.Commands.Run;
+
+ // Generated by test `{{nameof(RunFileTests)}}.{{nameof(CscArguments)}}`.
+ partial class CSharpCompilerCommand
+ {
+ private IEnumerable GetCscArguments(
+ string fileNameWithoutExtension,
+ string objDir,
+ string binDir)
+ {
+ return
+ [
+ """);
+ foreach (var arg in msbuildCallArgs)
+ {
+ // This option needs to be passed on the command line, not in an RSP file.
+ if (arg is "/noconfig")
+ {
+ continue;
+ }
+
+ // We don't need to generate a ref assembly.
+ if (arg.StartsWith("/refout:", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ // There should be no source link arguments.
+ if (arg.StartsWith("/sourcelink:", StringComparison.Ordinal))
+ {
+ Assert.Fail($"Unexpected source link argument: {arg}");
+ }
+
+ // PreferredUILang is normally not set by default but can be in builds, so ignore it.
+ if (arg.StartsWith("/preferreduilang:", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ bool needsInterpolation = false;
+ bool fromNuGetPackage = false;
+
+ // Normalize slashes in paths.
+ string rewritten = NormalizePathArg(arg);
+
+ // Remove quotes.
+ rewritten = RemoveQuotes(rewritten);
+
+ string msbuildArgToVerify = rewritten;
+
+ // Use variable SDK path.
+ if (rewritten.Contains(sdkPath, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(sdkPath, "{SdkPath}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Use variable .NET root path.
+ if (rewritten.Contains(dotNetRootPath, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(dotNetRootPath, "{DotNetRootPath}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Use variable NuGet cache path.
+ if (rewritten.Contains(nuGetCachePath, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(nuGetCachePath, "{NuGetCachePath}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ fromNuGetPackage = true;
+ }
+
+ // Use variable intermediate dir path.
+ if (rewritten.Contains(objPath, StringComparison.OrdinalIgnoreCase))
+ {
+ // We want to emit the resulting DLL directly into the bin folder.
+ bool isOut = arg.StartsWith("/out", StringComparison.Ordinal);
+ string replacement = isOut ? "{binDir}" : "{objDir}";
+
+ if (isOut)
+ {
+ msbuildArgToVerify = msbuildArgToVerify.Replace("/obj/", "/bin/", StringComparison.OrdinalIgnoreCase);
+ }
+
+ rewritten = rewritten.Replace(objPath, replacement, StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Use variable file name.
+ if (rewritten.Contains(entryPointPathNormalized, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(entryPointPathNormalized, "{" + nameof(CSharpCompilerCommand.EntryPointFileFullPath) + "}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Use variable program name.
+ if (rewritten.Contains(programName, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(programName, "{fileNameWithoutExtension}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Use variable runtime version.
+ if (rewritten.Contains(CSharpCompilerCommand.RuntimeVersion, StringComparison.OrdinalIgnoreCase))
+ {
+ rewritten = rewritten.Replace(CSharpCompilerCommand.RuntimeVersion, "{" + nameof(CSharpCompilerCommand.RuntimeVersion) + "}", StringComparison.OrdinalIgnoreCase);
+ needsInterpolation = true;
+ }
+
+ // Ignore `/analyzerconfig` which is not variable (so it comes from the machine or sdk repo).
+ if (!needsInterpolation && arg.StartsWith("/analyzerconfig", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ string prefix = needsInterpolation ? "$" : string.Empty;
+
+ code.AppendLine($"""
+ {prefix}"{rewritten}",
+ """);
+
+ msbuildArgsToVerify.Add(msbuildArgToVerify);
+
+ if (fromNuGetPackage)
+ {
+ nuGetPackageFilePaths.Add(CSharpCompilerCommand.IsPathOption(rewritten, out int colonIndex)
+ ? rewritten.Substring(colonIndex + 1)
+ : rewritten);
+ }
+ }
+ code.AppendLine("""
+ ];
+ }
+
+ ///
+ /// Files that come from referenced NuGet packages (e.g., analyzers for NativeAOT) need to be checked specially (if they don't exist, MSBuild needs to run).
+ ///
+ public static IEnumerable GetPathsOfCscInputsFromNuGetCache()
+ {
+ return
+ [
+ """);
+ foreach (var nuGetPackageFilePath in nuGetPackageFilePaths)
+ {
+ code.AppendLine($"""
+ $"{nuGetPackageFilePath}",
+ """);
+ }
+ code.AppendLine("""
+ ];
+ }
+ }
+ """);
+
+ // Save the code.
+ var codeFolder = new DirectoryInfo(Path.Join(
+ TestContext.Current.ToolsetUnderTest.RepoRoot,
+ "src", "Cli", "dotnet", "Commands", "Run"));
+ var nonGeneratedFile = codeFolder.File("CSharpCompilerCommand.cs");
+ if (!nonGeneratedFile.Exists)
+ {
+ Log.WriteLine($"Skipping code generation because file does not exist: {nonGeneratedFile.FullName}");
+ }
+ else
+ {
+ var codeFilePath = codeFolder.File("CSharpCompilerCommand.Generated.cs");
+ var existingText = codeFilePath.Exists ? File.ReadAllText(codeFilePath.FullName) : string.Empty;
+ var newText = code.ToString();
+ if (existingText != newText)
+ {
+ Log.WriteLine($"{codeFilePath.FullName} needs to be updated:");
+ Log.WriteLine(newText);
+ if (Environment.GetEnvironmentVariable("CI") == "true")
+ {
+ throw new InvalidOperationException($"Not updating file in CI: {codeFilePath.FullName}");
+ }
+ else
+ {
+ File.WriteAllText(codeFilePath.FullName, newText);
+ throw new InvalidOperationException($"File outdated, commit the changes: {codeFilePath.FullName}");
+ }
+ }
+ }
+
+ // Build using CSC.
+ Directory.Delete(artifactsDir, recursive: true);
+ new DotnetCommand(Log, "run", fileName, "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut($"""
+ {CliCommandStrings.NoBinaryLogBecauseRunningJustCsc}
+ Hello from {programName}
+ """);
+
+ // Read args from csc.rsp file.
+ var rspFilePath = Path.Join(artifactsDir, "csc.rsp");
+ var cscOnlyCallArgs = File.ReadAllLines(rspFilePath);
+ var cscOnlyCallArgsString = string.Join(' ', cscOnlyCallArgs);
+
+ // Check that csc args between MSBuild run and CSC-only run are equivalent.
+ var normalizedCscOnlyArgs = cscOnlyCallArgs
+ .Select(static a => NormalizePathArg(RemoveQuotes(a)));
+ Log.WriteLine("CSC-only args:");
+ Log.WriteLine(string.Join(Environment.NewLine, normalizedCscOnlyArgs));
+ Log.WriteLine("MSBuild args:");
+ Log.WriteLine(string.Join(Environment.NewLine, msbuildArgsToVerify));
+ normalizedCscOnlyArgs.Should().Equal(msbuildArgsToVerify,
+ "the generated file might be outdated, run this test locally to regenerate it");
+
+ static CompilerCall FindCompilerCall(string binaryLogPath)
+ {
+ using var reader = BinaryLogReader.Create(binaryLogPath);
+ return reader.ReadAllCompilerCalls().Should().ContainSingle().Subject;
+ }
+
+ static string NormalizePathArg(string arg)
+ {
+ return CSharpCompilerCommand.IsPathOption(arg, out int colonIndex)
+ ? string.Concat(arg.AsSpan(0, colonIndex + 1), NormalizePath(arg.Substring(colonIndex + 1)))
+ : NormalizePath(arg);
+ }
+
+ static string NormalizePath(string path)
+ {
+ return PathUtility.GetPathWithForwardSlashes(TestPathUtility.ResolveTempPrefixLink(path));
+ }
+
+ static string RemoveQuotes(string arg)
+ {
+ return arg.Replace("\"", string.Empty);
+ }
+ }
+
+ ///
+ /// Verifies that csc-only runs emit auxiliary files equivalent to msbuild-based runs.
+ ///
+ [Theory]
+ [InlineData("Program.cs")]
+ [InlineData("test.cs")]
+ [InlineData("noext")]
+ public void CscVsMSBuild(string fileName)
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ string entryPointPath = Path.Join(testInstance.Path, fileName);
+ File.WriteAllText(entryPointPath, $"""
+ #!/test
+ {s_program}
+ """);
+
+ string programName = Path.GetFileNameWithoutExtension(fileName);
+
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(entryPointPath);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+ var artifactsBackupDir = Path.ChangeExtension(artifactsDir, ".bak");
+ if (Directory.Exists(artifactsBackupDir)) Directory.Delete(artifactsBackupDir, recursive: true);
+
+ // Build using CSC.
+ new DotnetCommand(Log, "run", fileName, "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut($"""
+ {CliCommandStrings.NoBinaryLogBecauseRunningJustCsc}
+ Hello from {programName}
+ """);
+
+ // Backup the artifacts directory.
+ Directory.Move(artifactsDir, artifactsBackupDir);
+
+ // Build using MSBuild.
+ new DotnetCommand(Log, "run", fileName, "-bl", "--no-cache")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut($"Hello from {programName}");
+
+ // Check that files generated by MSBuild and CSC-only runs are equivalent.
+ var cscOnlyFiles = Directory.EnumerateFiles(artifactsBackupDir, "*", SearchOption.AllDirectories)
+ .Where(f =>
+ Path.GetDirectoryName(f) != artifactsBackupDir && // exclude top-level marker files
+ Path.GetFileName(f) != programName && // binary on unix
+ Path.GetExtension(f) is not (".dll" or ".exe" or ".pdb")); // other binaries
+ bool hasErrors = false;
+ foreach (var cscOnlyFile in cscOnlyFiles)
+ {
+ var relativePath = Path.GetRelativePath(relativeTo: artifactsBackupDir, path: cscOnlyFile);
+ var msbuildFile = Path.Join(artifactsDir, relativePath);
+
+ if (!File.Exists(msbuildFile))
+ {
+ throw new InvalidOperationException($"File exists in CSC-only run but not in MSBuild run: {cscOnlyFile}");
+ }
+
+ var cscOnlyFileText = File.ReadAllText(cscOnlyFile);
+ var msbuildFileText = File.ReadAllText(msbuildFile);
+ if (cscOnlyFileText.ReplaceLineEndings() != msbuildFileText.ReplaceLineEndings())
+ {
+ Log.WriteLine($"File differs between MSBuild and CSC-only runs: {cscOnlyFile}");
+ const int limit = 3_000;
+ if (cscOnlyFileText.Length < limit && msbuildFileText.Length < limit)
+ {
+ Log.WriteLine("MSBuild file content:");
+ Log.WriteLine(msbuildFileText);
+ Log.WriteLine("CSC-only file content:");
+ Log.WriteLine(cscOnlyFileText);
+ }
+ else
+ {
+ Log.WriteLine($"MSBuild file size: {msbuildFileText.Length} chars");
+ Log.WriteLine($"CSC-only file size: {cscOnlyFileText.Length} chars");
+ }
+ hasErrors = true;
+ }
+ }
+ hasErrors.Should().BeFalse();
+ }
+
[Fact]
public void UpToDate()
{
- var testInstance = _testAssetsManager.CreateTestDirectory();
- File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Console.WriteLine("Hello v1");
+ """);
- Build(expectedUpToDate: false);
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs"));
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ Build(testInstance, BuildLevel.Csc, expectedOutput: "Hello v1");
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None, expectedOutput: "Hello v1");
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None, expectedOutput: "Hello v1");
// Change the source file (a rebuild is necessary).
- File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program + " ");
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
- Build(expectedUpToDate: false);
+ Build(testInstance, BuildLevel.Csc);
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None);
// Change an unrelated source file (no rebuild necessary).
File.WriteAllText(Path.Join(testInstance.Path, "Program2.cs"), "test");
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None);
// Add an implicit build file (a rebuild is necessary).
string buildPropsFile = Path.Join(testInstance.Path, "Directory.Build.props");
@@ -1683,12 +2084,12 @@ public void UpToDate()
""");
- Build(expectedUpToDate: false, expectedOutput: """
+ Build(testInstance, BuildLevel.All, expectedOutput: """
Hello from Program
Custom define
""");
- Build(expectedUpToDate: true, expectedOutput: """
+ Build(testInstance, BuildLevel.None, expectedOutput: """
Hello from Program
Custom define
""");
@@ -1705,7 +2106,7 @@ Custom define
""");
- Build(expectedUpToDate: false);
+ Build(testInstance, BuildLevel.All);
// Change the imported build file (this is not recognized).
File.WriteAllText(importedFile, """
@@ -1716,43 +2117,43 @@ Custom define
""");
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None);
// Force rebuild.
- Build(expectedUpToDate: false, args: ["--no-cache"], expectedOutput: """
+ Build(testInstance, BuildLevel.All, args: ["--no-cache"], expectedOutput: """
Hello from Program
Custom define
""");
// Remove an implicit build file (a rebuild is necessary).
File.Delete(buildPropsFile);
- Build(expectedUpToDate: false);
+ Build(testInstance, BuildLevel.Csc);
// Force rebuild.
- Build(expectedUpToDate: false, args: ["--no-cache"]);
+ Build(testInstance, BuildLevel.All, args: ["--no-cache"]);
- Build(expectedUpToDate: true);
+ Build(testInstance, BuildLevel.None);
// Pass argument (no rebuild necessary).
- Build(expectedUpToDate: true, args: ["--", "test-arg"], expectedOutput: """
+ Build(testInstance, BuildLevel.None, args: ["--", "test-arg"], expectedOutput: """
echo args:test-arg
Hello from Program
""");
// Change config (a rebuild is necessary).
- Build(expectedUpToDate: false, args: ["-c", "Release"], expectedOutput: """
+ Build(testInstance, BuildLevel.All, args: ["-c", "Release"], expectedOutput: """
Hello from Program
Release config
""");
// Keep changed config (no rebuild necessary).
- Build(expectedUpToDate: true, args: ["-c", "Release"], expectedOutput: """
+ Build(testInstance, BuildLevel.None, args: ["-c", "Release"], expectedOutput: """
Hello from Program
Release config
""");
// Change config back (a rebuild is necessary).
- Build(expectedUpToDate: false);
+ Build(testInstance, BuildLevel.Csc);
// Build with a failure.
new DotnetCommand(Log, ["run", "Program.cs", "-p:LangVersion=Invalid"])
@@ -1762,34 +2163,41 @@ Release config
.And.HaveStdOutContaining("error CS1617"); // Invalid option 'Invalid' for /langversion.
// A rebuild is necessary since the last build failed.
- Build(expectedUpToDate: false);
+ Build(testInstance, BuildLevel.Csc);
+ }
- void Build(bool expectedUpToDate, ReadOnlySpan args = default, string expectedOutput = "Hello from Program")
+ private void Build(TestDirectory testInstance, BuildLevel level, ReadOnlySpan args = default, string expectedOutput = "Hello from Program")
+ {
+ string prefix = level switch
{
- new DotnetCommand(Log, ["run", "Program.cs", "-bl", .. args])
- .WithWorkingDirectory(testInstance.Path)
- .Execute()
- .Should().Pass()
- .And.HaveStdOut(expectedUpToDate
- ? $"""
- {CliCommandStrings.NoBinaryLogBecauseUpToDate}
- {expectedOutput}
- """
- : expectedOutput);
-
- var binlogs = new DirectoryInfo(testInstance.Path)
- .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly);
-
- binlogs.Select(f => f.Name)
- .Should().BeEquivalentTo(
- expectedUpToDate
- ? ["msbuild-dotnet-run.binlog"]
- : ["msbuild.binlog", "msbuild-dotnet-run.binlog"]);
-
- foreach (var binlog in binlogs)
- {
- binlog.Delete();
- }
+ BuildLevel.None => CliCommandStrings.NoBinaryLogBecauseUpToDate + Environment.NewLine,
+ BuildLevel.Csc => CliCommandStrings.NoBinaryLogBecauseRunningJustCsc + Environment.NewLine,
+ BuildLevel.All => string.Empty,
+ _ => throw new ArgumentOutOfRangeException(paramName: nameof(level)),
+ };
+
+ new DotnetCommand(Log, ["run", "Program.cs", "-bl", .. args])
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut(prefix + expectedOutput);
+
+ var binlogs = new DirectoryInfo(testInstance.Path)
+ .EnumerateFiles("*.binlog", SearchOption.TopDirectoryOnly);
+
+ binlogs.Select(f => f.Name)
+ .Should().BeEquivalentTo(
+ level switch
+ {
+ BuildLevel.Csc => [],
+ BuildLevel.None => ["msbuild-dotnet-run.binlog"],
+ BuildLevel.All => ["msbuild.binlog", "msbuild-dotnet-run.binlog"],
+ _ => throw new ArgumentOutOfRangeException(paramName: nameof(level), message: level.ToString()),
+ });
+
+ foreach (var binlog in binlogs)
+ {
+ binlog.Delete();
}
}
@@ -1806,6 +2214,210 @@ public void UpToDate_InvalidOptions()
.And.HaveStdErrContaining(string.Format(CliCommandStrings.InvalidOptionCombination, RunCommandParser.NoCacheOption.Name, RunCommandParser.NoBuildOption.Name));
}
+ [Fact]
+ public void CscOnly()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Console.WriteLine("v1");
+ """);
+
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs"));
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ Build(testInstance, BuildLevel.Csc, expectedOutput: "v1");
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Console.WriteLine("v2");
+ #if !DEBUG
+ Console.WriteLine("Release config");
+ #endif
+ """);
+
+ Build(testInstance, BuildLevel.Csc, expectedOutput: "v2");
+
+ // Customizing a property forces MSBuild to be used.
+ Build(testInstance, BuildLevel.All, args: ["-c", "Release"], expectedOutput: """
+ v2
+ Release config
+ """);
+ }
+
+ [Fact]
+ public void CscOnly_CompilationDiagnostics()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ string x = null;
+ Console.WriteLine("ran" + x);
+ """);
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
+ // warning CS8600: Converting null literal or possible null value to non-nullable type.
+ .And.HaveStdOutContaining("warning CS8600")
+ .And.HaveStdOutContaining("ran");
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Console.Write
+ """);
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
+ // error CS1002: ; expected
+ .And.HaveStdOutContaining("error CS1002")
+ .And.HaveStdErrContaining(CliCommandStrings.RunCommandException);
+ }
+
+ ///
+ /// Checks that the DOTNET_ROOT env var is set the same in csc mode as in msbuild mode.
+ ///
+ [Fact]
+ public void CscOnly_DotNetRoot()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ foreach (var entry in Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process)
+ .Cast()
+ .Where(e => ((string)e.Key).StartsWith("DOTNET_ROOT")))
+ {
+ Console.WriteLine($"{entry.Key}={entry.Value}");
+ }
+ """);
+
+ var expectedDotNetRoot = TestContext.Current.ToolsetUnderTest.DotNetRoot;
+
+ var cscResult = new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute();
+
+ cscResult.Should().Pass()
+ .And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
+ .And.HaveStdOutContaining("DOTNET_ROOT")
+ .And.HaveStdOutContaining($"={expectedDotNetRoot}");
+
+ // Add an implicit build file to force use of msbuild instead of csc.
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), "");
+
+ var msbuildResult = new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute();
+
+ msbuildResult.Should().Pass()
+ .And.NotHaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
+ .And.HaveStdOutContaining("DOTNET_ROOT")
+ .And.HaveStdOutContaining($"={expectedDotNetRoot}");
+
+ // The set of DOTNET_ROOT env vars should be the same in both cases.
+ var cscVars = cscResult.StdOut!
+ .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
+ .Where(line => line.StartsWith("DOTNET_ROOT"));
+ var msbuildVars = msbuildResult.StdOut!
+ .Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries)
+ .Where(line => line.StartsWith("DOTNET_ROOT"));
+ cscVars.Should().BeEquivalentTo(msbuildVars);
+ }
+
+ ///
+ /// In CSC-only mode, the SDK needs to manually create intermediate files
+ /// like GlobalUsings.g.cs which are normally generated by MSBuild targets.
+ /// This tests the SDK recreates the files when they are outdated.
+ ///
+ [Fact]
+ public void CscOnly_IntermediateFiles()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Expression> e = () => 1 + 1;
+ Console.WriteLine(e);
+ """);
+
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs"));
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), "");
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ // error CS0246: The type or namespace name 'Expression<>' could not be found
+ .And.HaveStdOutContaining("error CS0246");
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Directory.Build.props"), """
+
+
+
+
+
+ """);
+
+ Build(testInstance, BuildLevel.All, expectedOutput: "() => 2");
+
+ File.Delete(Path.Join(testInstance.Path, "Directory.Build.props"));
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ .And.HaveStdOutContaining(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc)
+ // error CS0246: The type or namespace name 'Expression<>' could not be found
+ .And.HaveStdOutContaining("error CS0246");
+ }
+
+ ///
+ /// If a file from a NuGet package (which would be used by CSC-only build) does not exist, full MSBuild should be used instead.
+ ///
+ [Fact]
+ public void CscOnly_NotRestored()
+ {
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_program);
+
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(Path.Join(testInstance.Path, "Program.cs"));
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl", "--no-restore")
+ .WithEnvironmentVariable("NUGET_PACKAGES", Path.Join(testInstance.Path, "packages"))
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Fail()
+ // error NETSDK1004: Assets file '...\obj\project.assets.json' not found. Run a NuGet package restore to generate this file.
+ .And.HaveStdOutContaining("NETSDK1004");
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithEnvironmentVariable("NUGET_PACKAGES", Path.Join(testInstance.Path, "packages"))
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut("Hello from Program");
+
+ File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), """
+ Console.WriteLine("v2");
+ """);
+
+ new DotnetCommand(Log, "run", "Program.cs", "-bl")
+ .WithEnvironmentVariable("NUGET_PACKAGES", Path.Join(testInstance.Path, "packages"))
+ .WithWorkingDirectory(testInstance.Path)
+ .Execute()
+ .Should().Pass()
+ .And.HaveStdOut($"""
+ {CliCommandStrings.NoBinaryLogBecauseRunningJustCsc}
+ v2
+ """);
+ }
+
private static string ToJson(string s) => JsonSerializer.Serialize(s);
///
@@ -2082,21 +2694,29 @@ public void Api_RunCommand()
""");
}
- [Fact]
- public void EntryPointFilePath()
+ [Theory, CombinatorialData]
+ public void EntryPointFilePath(bool cscOnly)
{
- var testInstance = _testAssetsManager.CreateTestDirectory();
+ var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: cscOnly ? OutOfTreeBaseDirectory : null);
var filePath = Path.Join(testInstance.Path, "Program.cs");
File.WriteAllText(filePath, """"
var entryPointFilePath = AppContext.GetData("EntryPointFilePath") as string;
Console.WriteLine($"""EntryPointFilePath: {entryPointFilePath}""");
"""");
- new DotnetCommand(Log, "run", "Program.cs")
+ // Remove artifacts from possible previous runs of this test.
+ var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(filePath);
+ if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);
+
+ var prefix = cscOnly
+ ? CliCommandStrings.NoBinaryLogBecauseRunningJustCsc + Environment.NewLine
+ : string.Empty;
+
+ new DotnetCommand(Log, "run", "-bl", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass()
- .And.HaveStdOut($"EntryPointFilePath: {filePath}");
+ .And.HaveStdOut(prefix + $"EntryPointFilePath: {filePath}");
}
[Fact]