Skip to content

Commit 3669ea5

Browse files
authored
[Xamarin.Android.Build.Tasks] Fix an issue with incremental builds (#9183)
We have been getting on and off reports of the following build errors in .NET for Android for quite a while now: Resources/values/styles.xml(2): error APT2260: resource attr/colorPrimary (aka Microsoft.Maui:attr/colorPrimary) not found. It was always very confusing as to why this was happening. No one ever seemed to be able to figure out why these files were deleted. Well it turns out they were not deleted, just not included. While building dotnet/maui I came across this issue and managed to capture a `.binlog`. It turns out that the `_CleanMonoAndroidIntermediateDir` target was running, and it was deleting certain files. This target was running because `_CleanIntermediateIfNeeded` was running. That was running because the `build.props` file had changed, which was because the NuGet `project.assets.json` file had a new timestamp! On an incremental build! So it looks like NuGet sometimes touches the `project.assets.json` file even if it does not change. We can't blame them for that as we do similar things. The next confusing thing and the principal cause is that the `libraryprojectimports.cache` was not present. However, the `_ResolveLibraryProjectImports.stamp` file *was* present. This means that the `_ResolveLibraryProjectImports` target does NOT run, so we end up NOT including ANY of the resource `res.zip` files when calling `aapt2`. The `_ResolveLibraryProjectImports` target did not include `libraryprojectimports.cache` in its `Outputs`, so if the file did not exist, but the stamp file *did*, then the target would not run. It is confusing because in the `_CleanMonoAndroidIntermediateDir` we delete the `libraryprojectimports.cache` and the entire `$(IntermediateOutputPath)stamp` directory. After much searching I did something while maui was opened in VSCode: I deleted its `artifacts` directory to clean up and start a new build and ..... it magically re-appeared!!! Then I thought... Design Time Builds!!!! So the problem we have is this: in our current system the Design Time Builds and the main builds all use the same stamp files!!! What was happening is a design time build was running before the main build, and it was creating a design time cache file into `$(_AndroidIntermediateDesignTimeBuildDirectory)` (`$(IntermediateOutputPath)designtime`), but place the stamp files INTO THE SAME LOCATION as the main build would. As a result the main build would skip the `_ResolveLibraryProjectImports` target COMPLETELY even if the output cache file was not present. Once that happens it will NEVER recover until you delete the bin/obj directories. This only happened occasionally because it would depend on if you have the project open in a IDE and when the IDE decided to run a Design Time Build. Fix this by giving design-time builds their own stamp directory. It might cause some targets to run during a design time build but I think that is preferred to a failing main build. To work around the NuGet causing a build when it touches a the `project.assets.json` file we now use `<GetFileHash/>`. This means we only refresh the library projects if the contents of the `project.assets.json` file change, rather than the timestamp. Finally, there was a bug where we would automatically fallback to the NuGet lock file even if we found the `project.assets.json` file.
1 parent eb5455a commit 3669ea5

File tree

4 files changed

+62
-17
lines changed

4 files changed

+62
-17
lines changed

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

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
9090
using (var libb = CreateDllBuilder (Path.Combine (path, lib.ProjectName), false, false))
9191
using (var appb = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
9292
// Save the library project, but don't build it yet
93-
libb.Save (lib);
93+
libb.Save (lib, saveProject: true);
94+
FileAssert.Exists (Path.Combine(Root, path, lib.ProjectName, lib.ProjectFilePath));
9495
appb.BuildLogFile = "build1.log";
9596
appb.Target = target;
9697
Assert.IsTrue (appb.Build (proj, parameters: DesignerParameters), $"build should have succeeded for target `{target}`");
@@ -104,10 +105,10 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
104105
Assert.IsNotNull (doc.Element ("LinearLayout").Element ("unnamedproject.CustomTextView"),
105106
"unnamedproject.CustomTextView should have not been replaced with a $(Hash).CustomTextView");
106107
// Build the library project now
107-
Assert.IsTrue (libb.Build (lib, doNotCleanupOnUpdate: true), "library build should have succeeded.");
108+
Assert.IsTrue (libb.Build (lib, doNotCleanupOnUpdate: true, saveProject: true), "library build should have succeeded.");
108109
appb.Target = "Build";
109110
appb.BuildLogFile = "build2.log";
110-
Assert.IsTrue (appb.Build (proj, doNotCleanupOnUpdate: true), "app build should have succeeded.");
111+
Assert.IsTrue (appb.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "app build should have succeeded.");
111112
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should have run completely.");
112113
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_Foo"), "_Foo should have run completely");
113114
doc = XDocument.Load (customViewPath);
@@ -117,7 +118,16 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
117118
"unnamedproject.CustomTextView should have been replaced with a $(Hash).CustomTextView");
118119
appb.Target = target;
119120
appb.BuildLogFile = "build3.log";
120-
Assert.IsTrue (appb.Build (proj, parameters: DesignerParameters, doNotCleanupOnUpdate: true), $"build should have succeeded for target `{target}`");
121+
Assert.IsTrue (appb.DesignTimeBuild (proj, parameters: DesignerParameters, doNotCleanupOnUpdate: true), $"build should have succeeded for target `{target}`");
122+
Assert.IsFalse (appb.Output.AreTargetsAllSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should not have been skipped.");
123+
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_Foo"), "_Foo should have run completely");
124+
doc = XDocument.Load (customViewPath);
125+
Assert.IsNull (doc.Element ("LinearLayout").Element ("UnnamedProject.CustomTextView"),
126+
"UnnamedProject.CustomTextView should have been replaced with a $(Hash).CustomTextView");
127+
Assert.IsNull (doc.Element ("LinearLayout").Element ("unnamedproject.CustomTextView"),
128+
"unnamedproject.CustomTextView should have been replaced with a $(Hash).CustomTextView");
129+
appb.BuildLogFile = "build4.log";
130+
Assert.IsTrue (appb.DesignTimeBuild (proj, parameters: DesignerParameters, doNotCleanupOnUpdate: true), $"build should have succeeded for target `{target}`");
121131
Assert.IsTrue (appb.Output.AreTargetsAllSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should have been skipped.");
122132
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_Foo"), "_Foo should have run completely");
123133
doc = XDocument.Load (customViewPath);

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

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public void CheckNothingIsDeletedByIncrementalClean ([Values (true, false)] bool
105105
File.SetLastWriteTimeUtc (file, DateTime.UtcNow);
106106
}
107107
Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "Second should have succeeded");
108+
b.Output.AssertTargetIsNotSkipped ("_CleanMonoAndroidIntermediateDir");
109+
var stampFiles = Path.Combine (intermediate, "stamp", "_ResolveLibraryProjectImports.stamp");
110+
FileAssert.Exists (stampFiles, $"{stampFiles} should exists!");
111+
var libraryProjectImports = Path.Combine (intermediate, "libraryprojectimports.cache");
112+
FileAssert.Exists (libraryProjectImports, $"{libraryProjectImports} should exists!");
108113

109114
//No changes
110115
Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "Third should have succeeded");
@@ -827,6 +832,7 @@ public void ResolveLibraryProjectImports ()
827832

828833
b.BuildLogFile = "build2.log";
829834
Assert.IsTrue (b.Build (proj), "second build should have succeeded.");
835+
b.Output.AssertTargetIsNotSkipped ("_ResolveLibraryProjectImports");
830836
FileAssert.Exists (cacheFile);
831837
var actual = ReadCache (cacheFile);
832838
CollectionAssert.AreEqual (actual.Jars.Select (j => j.ItemSpec).OrderBy (j => j),
@@ -867,6 +873,22 @@ public void ResolveLibraryProjectImports ()
867873
foreach (var targetName in targets) {
868874
Assert.IsTrue (b.Output.IsTargetSkipped (targetName), $"`{targetName}` should be skipped!");
869875
}
876+
877+
var filesToTouch = new [] {
878+
Path.Combine (intermediate, "build.props"),
879+
Path.Combine (intermediate, $"{proj.ProjectName}.pdb"),
880+
};
881+
foreach (var file in filesToTouch) {
882+
FileAssert.Exists (file);
883+
File.SetLastWriteTimeUtc (file, DateTime.UtcNow);
884+
}
885+
886+
b.BuildLogFile = "build5.log";
887+
Assert.IsTrue (b.Build (proj), "fifth build should have succeeded.");
888+
b.Output.AssertTargetIsNotSkipped ("_CleanMonoAndroidIntermediateDir");
889+
b.Output.AssertTargetIsNotSkipped ("_ResolveLibraryProjectImports");
890+
FileAssert.Exists (cacheFile);
891+
FileAssert.Exists (stamp);
870892
}
871893
}
872894

@@ -1155,13 +1177,16 @@ public void DesignTimeBuild ()
11551177
var proj = new XamarinAndroidApplicationProject ();
11561178
using (var b = CreateApkBuilder (Path.Combine ("temp", $"{nameof (IncrementalBuildTest)}{TestName}"))) {
11571179
Assert.IsTrue (b.DesignTimeBuild (proj), "first dtb should have succeeded.");
1180+
var target = "_ResolveLibraryProjectImports";
1181+
Assert.IsFalse (b.Output.IsTargetSkipped (target), $"`{target}` should not have been skipped.");
11581182
// DesignTimeBuild=true lowercased
11591183
var parameters = new [] { "DesignTimeBuild=true" };
11601184
Assert.IsTrue (b.RunTarget (proj, "Compile", doNotCleanupOnUpdate: true, parameters: parameters), "second dtb should have succeeded.");
1161-
var target = "_ResolveLibraryProjectImports";
11621185
Assert.IsTrue (b.Output.IsTargetSkipped (target), $"`{target}` should have been skipped.");
11631186
Assert.IsTrue (b.RunTarget (proj, "UpdateGeneratedFiles", doNotCleanupOnUpdate: true, parameters: parameters), "UpdateGeneratedFiles should have succeeded.");
11641187
Assert.IsTrue (b.Output.IsTargetSkipped (target), $"`{target}` should have been skipped.");
1188+
Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "full build should have succeeded.");
1189+
Assert.IsFalse (b.Output.IsTargetSkipped (target), $"`{target}` should not have been skipped.");
11651190
}
11661191
}
11671192

src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -910,13 +910,18 @@ because xbuild doesn't support framework reference assemblies.
910910
<AndroidLinkResources Condition="'$(AndroidLinkResources)' == '' And '$(AndroidIncludeDebugSymbols)' != 'True'">False</AndroidLinkResources>
911911
<_AndroidBuildPropertiesCacheExists Condition=" Exists('$(_AndroidBuildPropertiesCache)') ">True</_AndroidBuildPropertiesCacheExists>
912912
<_NuGetAssetsFile Condition=" Exists('$(ProjectAssetsFile)') ">$(ProjectAssetsFile)</_NuGetAssetsFile>
913-
<_NuGetAssetsFile Condition=" Exists('$(ProjectLockFile)') ">$(ProjectLockFile)</_NuGetAssetsFile>
913+
<_NuGetAssetsFile Condition=" '$(_NuGetAssetsFile)' == '' and Exists('$(ProjectLockFile)') ">$(ProjectLockFile)</_NuGetAssetsFile>
914914
<_NuGetAssetsFile Condition=" '$(_NuGetAssetsFile)' == '' and Exists('packages.config') ">packages.config</_NuGetAssetsFile>
915-
<_NuGetAssetsTimestamp Condition=" '$(_NuGetAssetsFile)' != '' ">$([System.IO.File]::GetLastWriteTime('$(_NuGetAssetsFile)').Ticks)</_NuGetAssetsTimestamp>
916915
<_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' != 'True' ">mvid</_TypeMapKind>
917916
<_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' And '$(_InstantRunEnabled)' == 'True' ">strings-files</_TypeMapKind>
918917
<_TypeMapKind Condition=" '$(AndroidIncludeDebugSymbols)' == 'True' And '$(_InstantRunEnabled)' != 'True' ">strings-asm</_TypeMapKind>
919918
</PropertyGroup>
919+
<GetFileHash
920+
Condition=" Exists('$(_NuGetAssetsFile)') "
921+
Files="$(_NuGetAssetsFile)"
922+
>
923+
<Output TaskParameter="Items" ItemName="_NuGetAssetsFileHash" />
924+
</GetFileHash>
920925
<ItemGroup>
921926
<!-- List of items we want to trigger a build if changed -->
922927
<_PropertyCacheItems Include="AotAssemblies=$(AotAssemblies)" />
@@ -945,7 +950,7 @@ because xbuild doesn't support framework reference assemblies.
945950
<_PropertyCacheItems Include="OS=$(OS)" />
946951
<_PropertyCacheItems Include="AndroidIncludeDebugSymbols=$(AndroidIncludeDebugSymbols)" />
947952
<_PropertyCacheItems Include="AndroidPackageNamingPolicy=$(AndroidPackageNamingPolicy)" />
948-
<_PropertyCacheItems Include="_NuGetAssetsTimestamp=$(_NuGetAssetsTimestamp)" />
953+
<_PropertyCacheItems Include="_NuGetAssetsFileHash=%(_NuGetAssetsFileHash.FileHash)" />
949954
<_PropertyCacheItems Include="TypeMapKind=$(_TypeMapKind)" />
950955
<_PropertyCacheItems Include="AndroidManifestPlaceholders=$(AndroidManifestPlaceholders)" />
951956
<_PropertyCacheItems Include="ProjectFullPath=$(MSBuildProjectFullPath)" />

src/Xamarin.Android.Build.Tasks/Xamarin.Android.DesignTime.targets

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,45 +13,50 @@ This file is used by all project types, including binding projects.
1313

1414
<PropertyGroup>
1515
<_AndroidIntermediateDesignTimeBuildDirectory>$(IntermediateOutputPath)designtime\</_AndroidIntermediateDesignTimeBuildDirectory>
16+
<_AndroidIntermediateDesignTimeStampDirectory>$(_AndroidIntermediateDesignTimeBuildDirectory)stamp\</_AndroidIntermediateDesignTimeStampDirectory>
1617
<_AndroidDesignTimeBuildPropertiesCache>$(_AndroidIntermediateDesignTimeBuildDirectory)build.props</_AndroidDesignTimeBuildPropertiesCache>
1718
<_AndroidLibraryImportsDesignTimeCache>$(_AndroidIntermediateDesignTimeBuildDirectory)libraryimports.cache</_AndroidLibraryImportsDesignTimeCache>
1819
<_AndroidLibraryProjectImportsDesignTimeCache>$(_AndroidIntermediateDesignTimeBuildDirectory)libraryprojectimports.cache</_AndroidLibraryProjectImportsDesignTimeCache>
1920
<_AndroidManagedResourceDesignerFile>$(_AndroidIntermediateDesignTimeBuildDirectory)$(_AndroidResourceDesigner)</_AndroidManagedResourceDesignerFile>
2021
</PropertyGroup>
2122

22-
<Target Name="_CreateStampDirectory">
23-
<MakeDir
24-
Condition=" '$(_AndroidStampDirectory)' != '' And !Exists('$(_AndroidStampDirectory)') "
25-
Directories="$(_AndroidStampDirectory)"
26-
/>
27-
</Target>
28-
2923
<Target Name="_SetupMSBuildAllProjects">
3024
<ItemGroup>
3125
<_AndroidMSBuildAllProjects Include="$(MSBuildAllProjects)" Exclude="$(MSBuildProjectFullPath).user" />
3226
</ItemGroup>
3327
</Target>
3428

35-
<Target Name="_SetupDesignTimeBuildForBuild" DependsOnTargets="_CreateStampDirectory">
29+
<Target Name="_SetupDesignTimeBuildForBuild">
3630
<PropertyGroup>
3731
<DesignTimeBuild Condition=" '$(DesignTimeBuild)' == '' ">false</DesignTimeBuild>
3832
</PropertyGroup>
33+
<MakeDir
34+
Condition=" '$(_AndroidStampDirectory)' != '' And !Exists('$(_AndroidStampDirectory)') "
35+
Directories="$(_AndroidStampDirectory)"
36+
/>
3937
</Target>
4038

41-
<Target Name="_SetupDesignTimeBuildForCompile" DependsOnTargets="_CreateStampDirectory">
39+
<Target Name="_SetupDesignTimeBuildForCompile">
4240
<PropertyGroup>
4341
<DesignTimeBuild Condition=" '$(DesignTimeBuild)' == '' ">true</DesignTimeBuild>
4442
<ManagedDesignTimeBuild Condition=" '$(AndroidGenerateResourceDesigner)' == 'True' And '$(AndroidUseManagedDesignTimeResourceGenerator)' == 'true' And '$(DesignTimeBuild)' == 'true' And '$(BuildingInsideVisualStudio)' == 'true' ">true</ManagedDesignTimeBuild>
4543
<ManagedDesignTimeBuild Condition=" '$(ManagedDesignTimeBuild)' == '' And '$(DesignTimeBuild)' == 'True' And '$(AndroidUseDesignerAssembly)' == 'True' " >True</ManagedDesignTimeBuild>
4644
<ManagedDesignTimeBuild Condition=" '$(ManagedDesignTimeBuild)' == '' ">False</ManagedDesignTimeBuild>
45+
<_AndroidStampDirectory Condition=" '$(DesignTimeBuild)' == 'true' ">$(_AndroidIntermediateDesignTimeStampDirectory)</_AndroidStampDirectory>
4746
<_AndroidLibraryImportsCache Condition=" '$(DesignTimeBuild)' == 'true' And !Exists ('$(_AndroidLibraryImportsCache)') ">$(_AndroidLibraryImportsDesignTimeCache)</_AndroidLibraryImportsCache>
4847
<_AndroidLibraryProjectImportsCache Condition=" '$(DesignTimeBuild)' == 'true' And !Exists ('$(_AndroidLibraryProjectImportsCache)') ">$(_AndroidLibraryProjectImportsDesignTimeCache)</_AndroidLibraryProjectImportsCache>
4948
<_AndroidBuildPropertiesCache Condition=" '$(DesignTimeBuild)' == 'true' ">$(_AndroidDesignTimeBuildPropertiesCache)</_AndroidBuildPropertiesCache>
49+
<_GeneratorStampFile Condition=" '$(DesignTimeBuild)' == 'true' And !Exists('$(_GeneratorStampFile)') ">$(_AndroidIntermediateDesignTimeStampDirectory)generator.stamp</_GeneratorStampFile>
50+
<_AndroidResgenFlagFile Condition=" '$(DesignTimeBuild)' == 'true' And !Exists('$(_AndroidResgenFlagFile)') ">$(_AndroidIntermediateDesignTimeBuildDirectory)R.cs.flag</_AndroidResgenFlagFile>
5051
</PropertyGroup>
5152
<MakeDir
5253
Condition=" '$(_AndroidIsBindingProject)' != 'true' And !Exists ('$(_AndroidIntermediateDesignTimeBuildDirectory)') "
5354
Directories="$(_AndroidIntermediateDesignTimeBuildDirectory)"
5455
/>
56+
<MakeDir
57+
Condition=" '$(_AndroidStampDirectory)' != '' And !Exists('$(_AndroidStampDirectory)') "
58+
Directories="$(_AndroidStampDirectory)"
59+
/>
5560
</Target>
5661

5762
</Project>

0 commit comments

Comments
 (0)