diff --git a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs index 84ac2b073fe0..f4a4bc902973 100644 --- a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs +++ b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs @@ -142,6 +142,17 @@ static int ProcessBuildResponse(BuildResponse response, out bool fallbackToNorma { case CompletedBuildResponse completed: Reporter.Verbose.WriteLine("Compiler server processed compilation."); + + // Check if the compilation failed with CS0006 error (metadata file not found). + // This can happen when NuGet cache is cleared and referenced DLLs (e.g., analyzers or libraries) are missing. + if (completed.ReturnCode != 0 && completed.Output.Contains("error CS0006:", StringComparison.Ordinal)) + { + Reporter.Verbose.WriteLine("CS0006 error detected in fast compilation path, falling back to full MSBuild."); + Reporter.Verbose.Write(completed.Output); + fallbackToNormalBuild = true; + return completed.ReturnCode; + } + Reporter.Output.Write(completed.Output); fallbackToNormalBuild = false; return completed.ReturnCode; diff --git a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs index c6ab42bbc498..7b47b5b6762d 100644 --- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs +++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs @@ -244,11 +244,6 @@ public override int Execute() if (buildLevel is BuildLevel.Csc) { - if (binaryLogger is not null) - { - Reporter.Output.WriteLine(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc.Yellow()); - } - MarkBuildStart(); // Execute CSC. @@ -269,6 +264,11 @@ public override int Execute() MarkBuildSuccess(cache); } + if (binaryLogger is not null) + { + Reporter.Output.WriteLine(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc.Yellow()); + } + return result; } diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs index 5d7b53c65a3e..1a15efebfc71 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs @@ -3128,7 +3128,13 @@ Release config Build(testInstance, BuildLevel.Csc); } - private void Build(TestDirectory testInstance, BuildLevel expectedLevel, ReadOnlySpan args = default, string expectedOutput = "Hello from Program", string programFileName = "Program.cs") + private void Build( + TestDirectory testInstance, + BuildLevel expectedLevel, + ReadOnlySpan args = default, + string expectedOutput = "Hello from Program", + string programFileName = "Program.cs", + Func? customizeCommand = null) { string prefix = expectedLevel switch { @@ -3138,9 +3144,15 @@ private void Build(TestDirectory testInstance, BuildLevel expectedLevel, ReadOnl _ => throw new ArgumentOutOfRangeException(paramName: nameof(expectedLevel)), }; - new DotnetCommand(Log, ["run", programFileName, "-bl", .. args]) - .WithWorkingDirectory(testInstance.Path) - .Execute() + var command = new DotnetCommand(Log, ["run", programFileName, "-bl", .. args]) + .WithWorkingDirectory(testInstance.Path); + + if (customizeCommand != null) + { + command = customizeCommand(command); + } + + command.Execute() .Should().Pass() .And.HaveStdOut(prefix + expectedOutput); @@ -3787,6 +3799,102 @@ public void CscOnly_AfterMSBuild_AuxiliaryFilesNotReused() Build(testInstance, BuildLevel.Csc, expectedOutput: "v3 "); } + /// + /// Testing optimization when the NuGet cache is cleared between builds. + /// See . + /// + [Fact] + public void CscOnly_NuGetCacheCleared() + { + var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory); + + var code = """ + Console.Write("v1"); + """; + + var programPath = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(programPath, code); + + var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); + + var packageDir = Path.Join(testInstance.Path, "packages"); + TestCommand CustomizeCommand(TestCommand command) => command.WithEnvironmentVariable("NUGET_PACKAGES", packageDir); + + Assert.False(Directory.Exists(packageDir)); + + // Ensure the packages exist first. + Build(testInstance, BuildLevel.All, expectedOutput: "v1", customizeCommand: CustomizeCommand); + + Assert.True(Directory.Exists(packageDir)); + + // Now clear the build outputs (but not packages) to verify CSC is used even from "first run". + if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); + + code = code.Replace("v1", "v2"); + File.WriteAllText(programPath, code); + + Build(testInstance, BuildLevel.Csc, expectedOutput: "v2", customizeCommand: CustomizeCommand); + + code = code.Replace("v2", "v3"); + File.WriteAllText(programPath, code); + + // Clear NuGet cache. + Directory.Delete(packageDir, recursive: true); + Assert.False(Directory.Exists(packageDir)); + + Build(testInstance, BuildLevel.All, expectedOutput: "v3", customizeCommand: CustomizeCommand); + + Assert.True(Directory.Exists(packageDir)); + } + + /// + /// Combination of and . + /// + [Fact] + public void CscOnly_AfterMSBuild_NuGetCacheCleared() + { + var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory); + + var code = """ + #:property PublishAot=false + #:package System.CommandLine@2.0.0-beta4.22272.1 + new System.CommandLine.RootCommand("v1"); + Console.WriteLine("v1"); + """; + + var programPath = Path.Join(testInstance.Path, "Program.cs"); + File.WriteAllText(programPath, code); + + var artifactsDir = VirtualProjectBuildingCommand.GetArtifactsPath(programPath); + if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true); + + var packageDir = Path.Join(testInstance.Path, "packages"); + TestCommand CustomizeCommand(TestCommand command) => command.WithEnvironmentVariable("NUGET_PACKAGES", packageDir); + + Assert.False(Directory.Exists(packageDir)); + + Build(testInstance, BuildLevel.All, expectedOutput: "v1", customizeCommand: CustomizeCommand); + + Assert.True(Directory.Exists(packageDir)); + + code = code.Replace("v1", "v2"); + File.WriteAllText(programPath, code); + + Build(testInstance, BuildLevel.Csc, expectedOutput: "v2", customizeCommand: CustomizeCommand); + + code = code.Replace("v2", "v3"); + File.WriteAllText(programPath, code); + + // Clear NuGet cache. + Directory.Delete(packageDir, recursive: true); + Assert.False(Directory.Exists(packageDir)); + + Build(testInstance, BuildLevel.All, expectedOutput: "v3", customizeCommand: CustomizeCommand); + + Assert.True(Directory.Exists(packageDir)); + } + private static string ToJson(string s) => JsonSerializer.Serialize(s); ///