diff --git a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs index a41d3822f6e8..95a581aa7486 100644 --- a/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs +++ b/src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs @@ -144,6 +144,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 ddd68ad109f5..b1232b50da29 100644 --- a/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs +++ b/src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs @@ -252,11 +252,6 @@ public override int Execute() if (buildLevel is BuildLevel.Csc) { - if (binaryLogger is not null) - { - Reporter.Output.WriteLine(CliCommandStrings.NoBinaryLogBecauseRunningJustCsc.Yellow()); - } - MarkBuildStart(); // Execute CSC. @@ -277,6 +272,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 1eebfddaef95..1d048d940376 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests.cs @@ -3214,7 +3214,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 { @@ -3224,9 +3230,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); @@ -3873,6 +3885,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); ///