Skip to content

Commit 002cab9

Browse files
authored
Make file-based caching opt out work for the basic up-to-date check too (#50927)
1 parent 189a174 commit 002cab9

File tree

3 files changed

+92
-18
lines changed

3 files changed

+92
-18
lines changed

documentation/general/dotnet-run-file.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -289,16 +289,16 @@ The build is performed using MSBuild APIs on in-memory project files.
289289
If an up-to-date check detects that inputs didn't change in subsequent `dotnet run file.cs` invocations,
290290
building is skipped (as if `--no-build` option has been passed).
291291
The up-to-date check is not 100% precise (e.g., files imported through an implicit build file are not considered).
292-
It is possible to enforce a full build using `--no-cache` flag or `dotnet build file.cs`.
292+
It is possible to enforce a full build using `--no-cache` flag or `dotnet build file.cs`
293+
(for a more permanent opt-out, there is MSBuild property `FileBasedProgramCanSkipMSBuild=false`).
293294
Environment variable [`DOTNET_CLI_CONTEXT_VERBOSE=true`][verbose-env] can be used to get more details about caching decisions made by `dotnet run file.cs`.
294295

295296
There are multiple optimization levels - skipping build altogether, running just the C# compiler, or running full MSBuild.
296297
We always need to re-run MSBuild if implicit build files like `Directory.Build.props` change but
297298
from `.cs` files, the only relevant MSBuild inputs are the `#:` directives,
298299
hence we can first check the `.cs` file timestamps and for those that have changed, compare the sets of `#:` directives.
299300
If only `.cs` files change, it is enough to invoke `csc.exe` (directly or via a build server)
300-
re-using command-line arguments that the last MSBuild invocation passed to the compiler
301-
(you can opt out of this via an MSBuild property `FileBasedProgramCanSkipMSBuild=false`).
301+
re-using command-line arguments that the last MSBuild invocation passed to the compiler.
302302
If no inputs change, it is enough to start the target executable without invoking the build at all.
303303

304304
## Alternatives and future work

src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,16 @@ public override int Execute()
356356
// Cache run info (to avoid re-evaluating the project instance).
357357
cache.CurrentEntry.Run = RunProperties.FromProject(buildRequest.ProjectInstance);
358358

359-
TryCacheCscArguments(cache, buildResult, buildRequest.ProjectInstance);
359+
if (!MSBuildUtilities.ConvertStringToBool(buildRequest.ProjectInstance.GetPropertyValue(FileBasedProgramCanSkipMSBuild), defaultValue: true))
360+
{
361+
Reporter.Verbose.WriteLine($"Not saving cache because there is an opt-out via MSBuild property {FileBasedProgramCanSkipMSBuild}.");
362+
}
363+
else
364+
{
365+
CacheCscArguments(cache, buildResult);
360366

361-
MarkBuildSuccess(cache);
367+
MarkBuildSuccess(cache);
368+
}
362369
}
363370

364371
projectInstance = buildRequest.ProjectInstance;
@@ -445,14 +452,8 @@ static Action<IDictionary<string, string>> AddRestoreGlobalProperties(ReadOnlyDi
445452
return null;
446453
}
447454

448-
void TryCacheCscArguments(CacheInfo cache, BuildResult result, ProjectInstance projectInstance)
455+
void CacheCscArguments(CacheInfo cache, BuildResult result)
449456
{
450-
if (!MSBuildUtilities.ConvertStringToBool(projectInstance.GetPropertyValue(FileBasedProgramCanSkipMSBuild), defaultValue: true))
451-
{
452-
Reporter.Verbose.WriteLine($"Not saving CSC arguments because there is an opt-out via MSBuild property {FileBasedProgramCanSkipMSBuild}.");
453-
return;
454-
}
455-
456457
// We cannot reuse CSC arguments from previous run and skip MSBuild if there are project references
457458
// because we cannot easily detect whether any referenced projects have changed.
458459
if (Directives.Any(static d => d is CSharpDirective.Project))

test/dotnet.Tests/CommandTests/Run/RunFileTests.cs

Lines changed: 79 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2787,24 +2787,97 @@ public void UpToDate_InvalidOptions()
27872787
/// <summary>
27882788
/// Up-to-date checks and optimizations currently don't support other included files.
27892789
/// </summary>
2790-
[Fact]
2791-
public void UpToDate_DefaultItems()
2790+
[Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912
2791+
public void UpToDate_DefaultItems(bool optOut)
27922792
{
27932793
var testInstance = _testAssetsManager.CreateTestDirectory();
2794-
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), s_programReadingEmbeddedResource);
2794+
var code = $"""
2795+
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
2796+
{s_programReadingEmbeddedResource}
2797+
""";
2798+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
27952799
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);
27962800

27972801
Build(testInstance, BuildLevel.All, expectedOutput: "[MyString, TestValue]");
27982802

27992803
// Update the RESX file.
28002804
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx.Replace("TestValue", "UpdatedValue"));
28012805

2802-
Build(testInstance, BuildLevel.None, expectedOutput: "[MyString, TestValue]"); // note: outdated output (build skipped)
2806+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.None, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "[MyString, TestValue]"); // note: outdated output (build skipped)
2807+
2808+
// Update the C# file.
2809+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + code);
2810+
2811+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "[MyString, TestValue]"); // note: outdated output (only CSC used)
2812+
2813+
Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]");
2814+
}
2815+
2816+
/// <summary>
2817+
/// Combination of <see cref="UpToDate_DefaultItems"/> with <see cref="CscOnly"/> optimization.
2818+
/// </summary>
2819+
[Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912
2820+
public void UpToDate_DefaultItems_CscOnly(bool optOut)
2821+
{
2822+
var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
2823+
var code = $"""
2824+
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
2825+
{s_programReadingEmbeddedResource}
2826+
""";
2827+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
2828+
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);
2829+
2830+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, TestValue]" : "Resource not found");
2831+
2832+
// Update the RESX file.
2833+
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx.Replace("TestValue", "UpdatedValue"));
2834+
2835+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.None, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found");
2836+
2837+
// Update the C# file.
2838+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + code);
2839+
2840+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "Resource not found");
2841+
2842+
Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]");
2843+
2844+
// Update the C# file.
2845+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v3\n" + code);
2846+
2847+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: "[MyString, UpdatedValue]");
2848+
}
2849+
2850+
/// <summary>
2851+
/// Combination of <see cref="UpToDate_DefaultItems"/> with <see cref="CscOnly_AfterMSBuild"/> optimization.
2852+
/// </summary>
2853+
[Theory, CombinatorialData] // https://github.com/dotnet/sdk/issues/50912
2854+
public void UpToDate_DefaultItems_CscOnly_AfterMSBuild(bool optOut)
2855+
{
2856+
var testInstance = _testAssetsManager.CreateTestDirectory(baseDirectory: OutOfTreeBaseDirectory);
2857+
var code = $"""
2858+
#:property Configuration=Release
2859+
{(optOut ? "#:property FileBasedProgramCanSkipMSBuild=false" : "")}
2860+
{s_programReadingEmbeddedResource}
2861+
""";
2862+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), code);
2863+
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx);
2864+
2865+
Build(testInstance, BuildLevel.All, expectedOutput: "[MyString, TestValue]");
2866+
2867+
// Update the C# file.
2868+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + code);
2869+
2870+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, TestValue]" : "[MyString, TestValue]");
2871+
2872+
// Update the RESX file.
2873+
File.WriteAllText(Path.Join(testInstance.Path, "Resources.resx"), s_resx.Replace("TestValue", "UpdatedValue"));
2874+
2875+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.None, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "[MyString, TestValue]");
28032876

28042877
// Update the C# file.
2805-
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v2\n" + s_programReadingEmbeddedResource);
2878+
File.WriteAllText(Path.Join(testInstance.Path, "Program.cs"), "//v3\n" + code);
28062879

2807-
Build(testInstance, BuildLevel.Csc, expectedOutput: "[MyString, TestValue]"); // note: outdated output (only CSC used)
2880+
Build(testInstance, optOut ? BuildLevel.All : BuildLevel.Csc, expectedOutput: optOut ? "[MyString, UpdatedValue]" : "[MyString, TestValue]");
28082881

28092882
Build(testInstance, BuildLevel.All, ["--no-cache"], expectedOutput: "[MyString, UpdatedValue]");
28102883
}

0 commit comments

Comments
 (0)