Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,20 @@ 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 analyzer DLLs are missing.
// The error code "CS0006" is language-independent (same across all locales),
// though the error message text may vary by locale.
// Error format: "error CS0006: Metadata file 'path' could not be found"
if (completed.ReturnCode != 0 && completed.Output.Contains("error CS0006:", StringComparison.Ordinal))
{
Reporter.Verbose.WriteLine("CS0006 error detected in optimized compilation, 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;
Expand Down
27 changes: 21 additions & 6 deletions src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -927,14 +927,29 @@ private BuildLevel GetBuildLevel(out CacheInfo cache)
}
else
{
Reporter.Verbose.WriteLine("We have CSC arguments from previous run. Skipping MSBuild and using CSC only.");
// Check that NuGet cache files still exist before attempting to reuse cached CSC arguments
bool canUseCachedArguments = true;
foreach (var filePath in CSharpCompilerCommand.GetPathsOfCscInputsFromNuGetCache())
{
if (!File.Exists(filePath))
{
Reporter.Verbose.WriteLine($"Cannot use CSC arguments from previous run because NuGet package file does not exist: {filePath}");
canUseCachedArguments = false;
break;
}
}

// Keep the cached info for next time, so we can use CSC again.
cache.CurrentEntry.CscArguments = cache.PreviousEntry.CscArguments;
cache.CurrentEntry.BuildResultFile = cache.PreviousEntry.BuildResultFile;
cache.CurrentEntry.Run = cache.PreviousEntry.Run;
if (canUseCachedArguments)
{
Reporter.Verbose.WriteLine("We have CSC arguments from previous run. Skipping MSBuild and using CSC only.");

// Keep the cached info for next time, so we can use CSC again.
cache.CurrentEntry.CscArguments = cache.PreviousEntry.CscArguments;
cache.CurrentEntry.BuildResultFile = cache.PreviousEntry.BuildResultFile;
cache.CurrentEntry.Run = cache.PreviousEntry.Run;

return BuildLevel.Csc;
return BuildLevel.Csc;
}
}
}

Expand Down
45 changes: 45 additions & 0 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4256,4 +4256,49 @@ Dictionary<string, string> ReadFiles()
return result;
}
}

[Fact]
public void FallbackToMSBuildWhenNuGetCacheCleared()
{
// This test simulates the scenario where NuGet cache is cleared after
// the initial compilation. When recompiling with CSC-only path, the analyzer
// DLLs won't be found, causing CS0006 errors. The fix should detect this and
// fallback to full MSBuild which will restore the packages.
// Note: Default file-based apps use PublishAot=true which references analyzers
// from the NuGet cache (ILLink.CodeFixProvider.dll, ILLink.RoslynAnalyzer.dll).

var testInstance = _testAssetsManager.CreateTestDirectory();
string programPath = Path.Join(testInstance.Path, "Program.cs");

// Write a simple program
File.WriteAllText(programPath, s_program);

// First run: compile and run successfully
var firstRun = new DotnetCommand(Log, "run", programPath)
.WithWorkingDirectory(testInstance.Path)
.Execute();

firstRun.Should().Pass();
firstRun.StdOut.Should().Contain("Hello from Program");

// Modify the program slightly to trigger recompilation via CSC path
File.WriteAllText(programPath, s_program.Replace("Hello from", "Greetings from"));

// Delete the artifacts to simulate scenario similar to cleared cache
// This ensures the CSC path will be attempted but should fallback to MSBuild
var artifactsPath = VirtualProjectBuildingCommand.GetArtifactsPath(programPath);
if (Directory.Exists(artifactsPath))
{
Directory.Delete(artifactsPath, recursive: true);
}

// Second run: should still succeed
// If fallback mechanism works, it will use MSBuild and succeed
var secondRun = new DotnetCommand(Log, "run", programPath)
.WithWorkingDirectory(testInstance.Path)
.Execute();

secondRun.Should().Pass();
secondRun.StdOut.Should().Contain("Greetings from Program");
}
}
Loading