Skip to content
Merged
15 changes: 12 additions & 3 deletions src/Cli/dotnet/Commands/Run/CSharpCompilerCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,16 +202,25 @@ static bool HaveMatchingSizeAndTimeStamp(FileInfo sourceFile, FileInfo destinati
}
}

private void PrepareAuxiliaryFiles(out string rspPath)
internal static string WriteCscRspFile(string artifactsPath, ImmutableArray<string> cscArguments)
{
rspPath = Path.Join(ArtifactsPath, "csc.rsp");
string rspPath = GetCscRspPath(artifactsPath);
File.WriteAllLines(rspPath, cscArguments);
return rspPath;
}

private static string GetCscRspPath(string artifactsPath) => Path.Join(artifactsPath, "csc.rsp");

private void PrepareAuxiliaryFiles(out string rspPath)
{
if (!CscArguments.IsDefaultOrEmpty)
{
File.WriteAllLines(rspPath, CscArguments);
rspPath = WriteCscRspFile(ArtifactsPath, CscArguments);
return;
}

rspPath = GetCscRspPath(ArtifactsPath);

// 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");
Expand Down
20 changes: 19 additions & 1 deletion src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,15 @@ public override int Execute()
// Then do a build.
if (exitCode == 0 && !NoBuild && !evalOnly)
{
var effectiveTargets = Builder.RequestedTargets is { Length: > 0 } requestedTargets
? requestedTargets
: [Constants.Build, Constants.CoreCompile];
var buildRequest = new BuildRequestData(
CreateProjectInstance(projectCollection),
targetsToBuild: Builder.RequestedTargets ?? [Constants.Build, Constants.CoreCompile]);
targetsToBuild: effectiveTargets,
hostServices: null,
// SkipNonexistentTargets: CoreCompile doesn't exist in the outer build of multi-target projects.
BuildRequestDataFlags.SkipNonexistentTargets);

var buildResult = BuildManager.DefaultBuildManager.BuildRequest(buildRequest);
if (buildResult.OverallResult != BuildResultCode.Success)
Expand All @@ -339,6 +345,7 @@ public override int Execute()
cache.CurrentEntry.Run = LastRunProperties;

CacheCscArguments(cache, buildResult);
WriteCscRsp(cache);
CollectAdditionalSources(cache, buildRequest.ProjectInstance);

MarkBuildSuccess(cache);
Expand Down Expand Up @@ -489,6 +496,17 @@ void ReuseInfoFromPreviousCacheEntry(CacheInfo cache)
}
}

void WriteCscRsp(CacheInfo cache)
{
if (cache.CurrentEntry.CscArguments.IsDefaultOrEmpty)
{
return;
}

string rspPath = CSharpCompilerCommand.WriteCscRspFile(Builder.ArtifactsPath, cache.CurrentEntry.CscArguments);
Reporter.Verbose.WriteLine($"Wrote '{rspPath}'.");
}

bool CanSaveCache(ProjectInstance projectInstance)
{
if (!MSBuildUtilities.ConvertStringToBool(projectInstance.GetPropertyValue(FileBasedProgramCanSkipMSBuild), defaultValue: true))
Expand Down
57 changes: 57 additions & 0 deletions test/dotnet.Tests/CommandTests/Run/RunFileTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5515,6 +5515,63 @@ public void CscOnly_AfterMSBuild_AuxiliaryFilesNotReused()
Build(testInstance, BuildLevel.Csc, expectedOutput: "v3 ");
}

/// <summary>
/// Verifies that <c>csc.rsp</c> is written to disk after a full MSBuild build,
/// so that IDEs can read it to create a virtual project.
/// </summary>
[Fact]
public void MSBuild_WritesCscRsp()
{
var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);

var programPath = Path.Join(testInstance.Path, "Program.cs");
File.WriteAllText(programPath, """
#:property Configuration=Release
Console.Write("Hello");
""");

// Remove artifacts from possible previous runs of this test.
var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath);
if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);

// A build directive forces a full MSBuild build.
Build(testInstance, BuildLevel.All, expectedOutput: "Hello");

// csc.rsp should be written to disk after a full MSBuild build.
var rspPath = Path.Join(artifactsDir, "csc.rsp");
File.Exists(rspPath).Should().BeTrue("csc.rsp should be written after a full MSBuild build");
File.ReadAllLines(rspPath).Should().NotBeEmpty("csc.rsp should contain compiler arguments");
}

/// <summary>
/// Verifies that <c>csc.rsp</c> is written to disk after <c>dotnet build file.cs</c>,
/// so that IDEs can read it to create a virtual project.
/// </summary>
[Fact]
public void DotnetBuild_WritesCscRsp()
{
var testInstance = _testAssetsManager.CreateTestDirectory();

var programPath = Path.Join(testInstance.Path, "Program.cs");
File.WriteAllText(programPath, """
Console.Write("Hello");
""");

// Remove artifacts from possible previous runs of this test.
var artifactsDir = VirtualProjectBuilder.GetArtifactsPath(programPath);
if (Directory.Exists(artifactsDir)) Directory.Delete(artifactsDir, recursive: true);

new DotnetCommand(Log, "build", "Program.cs")
.WithWorkingDirectory(testInstance.Path)
.Execute()
.Should().Pass();

// csc.rsp should be written to disk after dotnet build.
var rspPath = Path.Join(artifactsDir, "csc.rsp");
File.Exists(rspPath).Should().BeTrue("csc.rsp should be written after dotnet build file.cs");
File.ReadAllLines(rspPath).Should().NotBeEmpty("csc.rsp should contain compiler arguments");
}

/// <summary>
/// Testing <see cref="CscOnly"/> optimization when the NuGet cache is cleared between builds.
/// See <see href="https://github.com/dotnet/sdk/issues/45169"/>.
Expand Down
Loading