Skip to content

Commit 905878b

Browse files
[One .NET] fix for incremental CoreCompile (#5661)
Context: https://github.com/xamarin/Xamarin.Forms/tree/main-handler I noticed that when building Maui, `CoreCompile` seems to be running on every build no matter what: Building target "CoreCompile" completely. Input file "obj\Debug\net6.0-android\Core-net6.csproj.CoreCompileInputs.cache" is newer than output file "bin\Debug\net6.0-android\Microsoft.Maui.xml". I could reproduce this in a test: * Build `AppA` & `LibraryB` * Build `AppA` & `LibraryB` again, `LibraryB` will run `CoreCompile` *every* time. There is a `_GenerateCompileDependencyCache` target that basically does: <Hash ItemsToHash="@(CoreCompileCache)"> <Output TaskParameter="HashResult" PropertyName="CoreCompileDependencyHash" /> </Hash> <WriteLinesToFile Lines="$(CoreCompileDependencyHash)" File="$(IntermediateOutputPath)$(MSBuildProjectFile).CoreCompileInputs.cache" Overwrite="True" WriteOnlyWhenDifferent="True" /> https://github.com/dotnet/msbuild/blob/83cd7d4e36b71d5b2cefd02cb9a5a58d27dd6a75/src/Tasks/Microsoft.Common.CurrentVersion.targets#L3529 This `*.CoreCompileInputs.cache` file triggers `CoreCompile` to run again when it needs to. However, this file is actually updating on every build, because: 1. Our "outer" build has all our preprocessor defines listed in `@(CoreCompileCache)` like `__MOBILE__`, `__ANDROID__`, etc. 2. The "inner" build for each `$(RuntimeIdentifier)` does *not* have these symbols! And so we get into a situation where `CoreCompile` will always run. The inner & outer builds write different values in this file. To solve this problem, I added our `_AddAndroidDefines` to run before `CoreCompile` in inner builds. I also needed some changes to our MSBuild test framework: * Make `IsTargetSkipped()` and `AssertTargetIsSkipped()` supported for new project types. * Make `IsTargetSkipped()` return `false` if a `Building target "{target}" completely.` message is found.
1 parent 11c30ac commit 905878b

File tree

5 files changed

+65
-2
lines changed

5 files changed

+65
-2
lines changed

src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ _ResolveAssemblies MSBuild target.
2424
<CoreBuildDependsOn>
2525
$([MSBuild]::Unescape($(CoreBuildDependsOn.Replace('IncrementalClean;', ''))))
2626
</CoreBuildDependsOn>
27+
<CompileDependsOn>
28+
_AddAndroidDefines;
29+
$(CompileDependsOn);
30+
</CompileDependsOn>
2731
</PropertyGroup>
2832

2933
<Target Name="_ComputeFilesToPublishForRuntimeIdentifiers"

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/AssertionExtensions.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,24 @@ public static void AssertTargetIsNotSkipped (this BuildOutput output, string tar
2727
Assert.IsFalse (output.IsTargetSkipped (target), $"The target {target} should have *not* been skipped.");
2828
}
2929

30+
[DebuggerHidden]
31+
public static void AssertTargetIsSkipped (this DotNetCLI dotnet, string target, int? occurrence = null)
32+
{
33+
if (occurrence != null)
34+
Assert.IsTrue (dotnet.IsTargetSkipped (target), $"The target {target} should have been skipped. ({occurrence})");
35+
else
36+
Assert.IsTrue (dotnet.IsTargetSkipped (target), $"The target {target} should have been skipped.");
37+
}
38+
39+
[DebuggerHidden]
40+
public static void AssertTargetIsNotSkipped (this DotNetCLI dotnet, string target, int? occurrence = null)
41+
{
42+
if (occurrence != null)
43+
Assert.IsFalse (dotnet.IsTargetSkipped (target), $"The target {target} should have *not* been skipped. ({occurrence})");
44+
else
45+
Assert.IsFalse (dotnet.IsTargetSkipped (target), $"The target {target} should have *not* been skipped.");
46+
}
47+
3048
[DebuggerHidden]
3149
public static void AssertTargetIsPartiallyBuilt (this BuildOutput output, string target, int? occurrence = null)
3250
{

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,41 @@ public void XamarinLegacySdk ()
550550
nupkg.AssertContainsEntry (nupkgPath, $"lib/{legacyTargetFramework}/{proj.ProjectName}.dll");
551551
}
552552

553+
[Test]
554+
public void DotNetIncremental ()
555+
{
556+
// Setup dependencies App A -> Lib B
557+
var path = Path.Combine ("temp", TestName);
558+
559+
var libB = new XASdkProject (outputType: "Library") {
560+
ProjectName = "LibraryB"
561+
};
562+
libB.Sources.Clear ();
563+
libB.Sources.Add (new BuildItem.Source ("Foo.cs") {
564+
TextContent = () => "public class Foo { }",
565+
});
566+
567+
// Will save the project, does not need to build it
568+
CreateDotNetBuilder (libB, Path.Combine (path, libB.ProjectName));
569+
570+
var appA = new XASdkProject {
571+
ProjectName = "AppA",
572+
Sources = {
573+
new BuildItem.Source ("Bar.cs") {
574+
TextContent = () => "public class Bar : Foo { }",
575+
}
576+
}
577+
};
578+
appA.AddReference (libB);
579+
var appBuilder = CreateDotNetBuilder (appA, Path.Combine (path, appA.ProjectName));
580+
Assert.IsTrue (appBuilder.Build (), $"{appA.ProjectName} should succeed");
581+
appBuilder.AssertTargetIsNotSkipped ("CoreCompile");
582+
583+
// Build again, no changes
584+
Assert.IsTrue (appBuilder.Build (), $"{appA.ProjectName} should succeed");
585+
appBuilder.AssertTargetIsSkipped ("CoreCompile");
586+
}
587+
553588
DotNetCLI CreateDotNetBuilder (string relativeProjectDir = null)
554589
{
555590
if (string.IsNullOrEmpty (relativeProjectDir)) {

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/BuildOutput.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,10 +52,14 @@ public List<string> GetAssemblyMapCache ()
5252
return File.ReadLines (path).ToList ();
5353
}
5454

55-
public bool IsTargetSkipped (string target)
55+
public bool IsTargetSkipped (string target) => IsTargetSkipped (Builder.LastBuildOutput, target);
56+
57+
public static bool IsTargetSkipped (IEnumerable<string> output, string target)
5658
{
5759
bool found = false;
58-
foreach (var line in Builder.LastBuildOutput) {
60+
foreach (var line in output) {
61+
if (line.Contains ($"Building target \"{target}\" completely."))
62+
return false;
5963
found = line.Contains ($"Target {target} skipped due to ")
6064
|| line.Contains ($"Skipping target \"{target}\" because it has no ") //NOTE: message can say `inputs` or `outputs`
6165
|| line.Contains ($"Target \"{target}\" skipped, due to")

src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public IEnumerable<string> LastBuildOutput {
126126
}
127127
}
128128

129+
public bool IsTargetSkipped (string target) => BuildOutput.IsTargetSkipped (LastBuildOutput, target);
130+
129131
List<string> GetDefaultCommandLineArgs (string verb, string target = null, string [] parameters = null)
130132
{
131133
string testDir = Path.GetDirectoryName (projectOrSolution);

0 commit comments

Comments
 (0)