Skip to content

Commit 373c6ed

Browse files
jonathanpeppersjonpryor
authored andcommitted
[Xamarin.Android.Build.Tasks] improve aapt2 incremental builds (dotnet#3108)
Context: https://github.com/microsoft/SmartHotel360-Mobile In 84daf03, we had to do a workaround to support custom views when aapt2 is enabled. We had to run a second `aapt2 compile` command for any layouts with custom views. In testing our build performance, I noticed a slow MSBuild task when building SmartHotel360 after a XAML change: Task Aapt2Compile 19.959s It makes sense that `_GenerateJavaStubs` ran in this case. This project does not set `$(ProduceReferenceAssembly)`, which is likely the current norm for our users. I think we can do two things to improve this: 1. Run `aapt2 compile` on only the resource directories needed, it looks like we are running against all of them. 2. Setup a new target, `_ConvertCustomView`, that will be skipped in cases of small code changes. ~~ Fix No. 1 ~~ If I look at the `<Aapt2Compile/>` task: Executing compile -o obj\Debug\90\flata\2a00f65fb8a92bd112f342ce47ad717dee3acac0.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\1\jl\res Executing compile -o obj\Debug\90\flata\compiled.flata --dir obj\Debug\90\res Executing compile -o obj\Debug\90\flata\5232a1999272d3d9b72f98243ed486aaa1b4593e.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\38\jl\res Executing compile -o obj\Debug\90\flata\3f5267fa5538ba387cf6e603b96e00903df1914b.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2\jl\res Executing compile -o obj\Debug\90\flata\0b987eee7d559f77cfd2e1e25371901bfdff8ec1.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\3\jl\res Executing compile -o obj\Debug\90\flata\5f8ee424582825b02052b46957c7e3c7670acde2.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\40\jl\res Executing compile -o obj\Debug\90\flata\7dc9d90cc8afd2ac8593b7fc3453cc0b12962428.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\8\jl\res Executing compile -o obj\Debug\90\flata\4ea0f4c451bba05912018e80c7f701bc1e71bdd8.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\4\jl\res Executing compile -o obj\Debug\90\flata\6ddbc5d6f28551b3610dcb62292152f3e5a4da40.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\10\jl\res Executing compile -o obj\Debug\90\flata\e5b8974158d857e7ad168a3e7eabd456a6379b0e.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\47\jl\res Executing compile -o obj\Debug\90\flata\15c91c5a3803931df9940672b0b87012bb56eed1.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\51\jl\res Executing compile -o obj\Debug\90\flata\1a25ed1b2a2de98e844508551fd3f18f3f60d534.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\13\jl\res Executing compile -o obj\Debug\90\flata\1063b71e02c710a47b032ae2fc8562f2b3bf94df.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\54\jl\res Executing compile -o obj\Debug\90\flata\7ba81f9192d677ba83e3f1f60ba27e3cedbbe600.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\15\jl\res Executing compile -o obj\Debug\90\flata\fc32e11566de7e5136f8c1280232e152f27589bf.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\17\jl\res Executing compile -o obj\Debug\90\flata\f0d523a4e46c0143b6e7a2a64304bd801b1c4d16.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\33\jl\res Executing compile -o obj\Debug\90\flata\782859d4e93cff6e7b533c2bcc1277f6c798ddd3.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\57\jl\res Executing compile -o obj\Debug\90\flata\eafdbe607beb895e86092f8162e013887090ed3f.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\66\jl\res Executing compile -o obj\Debug\90\flata\494e789af4faf31d77b998f73d376655215d5b4c.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\55\jl\res Executing compile -o obj\Debug\90\flata\b1948383c89f878bc1d049b3f6d00df65bed48fe.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\53\jl\res Executing compile -o obj\Debug\90\flata\0d013f17da77e518af4784c2b3fefafb6eb38d58.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\68\jl\res Executing compile -o obj\Debug\90\flata\8380133d1dcd4b4db10f23298668b8c7d691fe66.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\14\jl\res Executing compile -o obj\Debug\90\flata\8b09e95d85ed31f0b62ba610183151161499e461.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\61\jl\res Executing compile -o obj\Debug\90\flata\b7fec66873f5eb1356f2b20d94949cce3beb2e84.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\34\jl\res Executing compile -o obj\Debug\90\flata\24a8542616e4eae9d9a4c037207e18ee77b8ed11.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\16\jl\res Executing compile -o obj\Debug\90\flata\1393196416e23b44d0bb6e4884ea0121d8441723.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\63\jl\res Executing compile -o obj\Debug\90\flata\3660f9af31d03d46d80b5af133a9256879a2fb0e.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\65\jl\res We only need to do this for a subset. Looking at this item group: _ProcessedCustomViews D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2\jl\res\layout\horizontal_viewpager.xml Hash = 3f5267fa5538ba387cf6e603b96e00903df1914b StampFile = D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2.stamp D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2\jl\res\layout\vertical_viewpager.xml Hash = 3f5267fa5538ba387cf6e603b96e00903df1914b StampFile = D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2.stamp We should just run: Executing compile -o obj\Debug\90\flata\3f5267fa5538ba387cf6e603b96e00903df1914b.flata --dir D:\a\1\s\SmartHotel\Source\SmartHotel.Clients\SmartHotel.Clients.Android\obj\Debug\90\lp\2\jl\res To make this work: * I added `%(_ProcessedCustomViews.ResourceDirectory)` metadata, which is the resource directory containing the custom views. * We can run `aapt2 compile` against `%(_ProcessedCustomViews.ResourceDirectory)->Distinct()`: each directory containing an updated view, not all directories. ~~ Fix No 2. ~~ I moved the calls to `<ConvertCustomView/>` and `<Aapt2Compile/>` to a new `_ConvertCustomView` target. It can be skipped unless the `$(_CustomViewMapFile)` or `$(_AcwMapFile)` change in regards to a new `_ConvertCustomView.stamp` stamp file. This allows us to skip the task in a lot of cases for incremental builds. ~~ Other Changes ~~ I created a new `IncrementalBuildTest.ConvertCustomView()` test for these scenarios. I also made a few improvements to the XML formatting: use of spaces over tabs, fixing indentation, and putting the `Condition` attribute first. ~~ Results ~~ I tested the SmartHotel360 app, since this is where I saw the issue. Initial build: Before: 24492 ms _GenerateJavaStubs 1 calls After: 3933 ms _GenerateJavaStubs 1 calls 132 ms _ConvertCustomView 1 calls Incremental build with XAML change: Before: 24358 ms _GenerateJavaStubs 1 calls After: 3416 ms _GenerateJavaStubs 1 calls 29 ms _ConvertCustomView 1 calls They key here is going from 27 `aapt2 compile` calls to 1! NOTE: the before/after is not *exactly* accurate, since the before times were recorded on Azure DevOps. I would think this change could easily improve `aapt2` builds by 10 seconds or more.
1 parent d5e002e commit 373c6ed

File tree

3 files changed

+116
-23
lines changed

3 files changed

+116
-23
lines changed

src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,9 @@ public override bool Execute ()
9090
var stampFile = !string.IsNullOrEmpty (stamp) ? stamp : $"{filename}.stamp";
9191
Log.LogDebugMessage ($"{filename} {stampFile}");
9292
output.Add (new TaskItem (file, new Dictionary<string, string> {
93-
{ "StampFile" , $"{stampFile}" },
94-
{ "Hash" , $"{filename}" },
93+
{ "StampFile" , stampFile },
94+
{ "Hash" , filename },
95+
{ "ResourceDirectory", resdir.ItemSpec }
9596
}));
9697
}
9798
Processed = output.ToArray ();

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

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,88 @@ public void ProduceReferenceAssembly ()
525525
}
526526
}
527527

528+
[Test]
529+
public void ConvertCustomView ([Values (true, false)] bool useAapt2)
530+
{
531+
var path = Path.Combine ("temp", TestName);
532+
var app = new XamarinAndroidApplicationProject {
533+
ProjectName = "MyApp",
534+
Sources = {
535+
new BuildItem.Source ("Foo.cs") {
536+
TextContent = () => "public class Foo : Bar { }"
537+
},
538+
new BuildItem.Source ("CustomTextView.cs") {
539+
TextContent = () =>
540+
@"using Android.Widget;
541+
using Android.Content;
542+
using Android.Util;
543+
namespace MyApp
544+
{
545+
public class CustomTextView : TextView
546+
{
547+
public CustomTextView(Context context, IAttributeSet attributes) : base(context, attributes)
548+
{
549+
}
550+
}
551+
}"
552+
}
553+
}
554+
};
555+
// Use a custom view
556+
app.LayoutMain = app.LayoutMain.Replace ("</LinearLayout>", "<MyApp.CustomTextView android:id=\"@+id/myText\" /></LinearLayout>");
557+
//NOTE: so _BuildApkEmbed runs in commercial tests
558+
app.SetProperty ("EmbedAssembliesIntoApk", "True");
559+
app.SetProperty ("AndroidUseSharedRuntime", "False");
560+
app.SetProperty ("AndroidUseAapt2", useAapt2.ToString ());
561+
562+
int count = 0;
563+
var lib = new DotNetStandard {
564+
ProjectName = "MyLibrary",
565+
Sdk = "Microsoft.NET.Sdk",
566+
TargetFramework = "netstandard2.0",
567+
Sources = {
568+
new BuildItem.Source ("Bar.cs") {
569+
TextContent = () => "public class Bar { public Bar () { System.Console.WriteLine (" + count++ + "); } }"
570+
},
571+
}
572+
};
573+
//NOTE: this test is checking when $(ProduceReferenceAssembly) is False
574+
lib.SetProperty ("ProduceReferenceAssembly", "False");
575+
app.References.Add (new BuildItem.ProjectReference ($"..\\{lib.ProjectName}\\{lib.ProjectName}.csproj", lib.ProjectName, lib.ProjectGuid));
576+
577+
using (var libBuilder = CreateDllBuilder (Path.Combine (path, lib.ProjectName), false))
578+
using (var appBuilder = CreateApkBuilder (Path.Combine (path, app.ProjectName))) {
579+
Assert.IsTrue (libBuilder.Build (lib), "first library build should have succeeded.");
580+
Assert.IsTrue (appBuilder.Build (app), "first app build should have succeeded.");
581+
582+
lib.Touch ("Bar.cs");
583+
584+
Assert.IsTrue (libBuilder.Build (lib, doNotCleanupOnUpdate: true, saveProject: false), "second library build should have succeeded.");
585+
Assert.IsTrue (appBuilder.Build (app, doNotCleanupOnUpdate: true, saveProject: false), "second app build should have succeeded.");
586+
587+
var targetsShouldSkip = new [] {
588+
"_BuildLibraryImportsCache",
589+
"_ResolveLibraryProjectImports",
590+
"_ConvertCustomView",
591+
};
592+
foreach (var target in targetsShouldSkip) {
593+
Assert.IsTrue (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should be skipped!");
594+
}
595+
596+
var targetsShouldRun = new [] {
597+
//MyLibrary.dll changed and $(ProduceReferenceAssembly)=False
598+
"CoreCompile",
599+
"_GenerateJavaStubs",
600+
"_BuildApkEmbed",
601+
"_CopyPackage",
602+
"_Sign",
603+
};
604+
foreach (var target in targetsShouldRun) {
605+
Assert.IsFalse (appBuilder.Output.IsTargetSkipped (target), $"`{target}` should *not* be skipped!");
606+
}
607+
}
608+
}
609+
528610
[Test]
529611
public void ResolveLibraryProjectImports ()
530612
{

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

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2309,34 +2309,41 @@ because xbuild doesn't support framework reference assemblies.
23092309
<FileWrites Include="@(_TypeMapAssemblySource)" />
23102310
</ItemGroup>
23112311

2312+
<Touch Files="$(_AndroidStampDirectory)_GenerateJavaStubs.stamp" AlwaysCreate="True" />
2313+
</Target>
2314+
2315+
<Target Name="_ConvertCustomView"
2316+
Condition="Exists('$(_CustomViewMapFile)')"
2317+
Inputs="$(_CustomViewMapFile);$(_AcwMapFile)"
2318+
Outputs="$(_AndroidStampDirectory)_ConvertCustomView.stamp">
23122319
<ConvertCustomView
2313-
Condition="Exists('$(_CustomViewMapFile)')"
2314-
CustomViewMapFile="$(_CustomViewMapFile)"
2315-
AcwMapFile="$(_AcwMapFile)"
2316-
ResourceDirectories="$(MonoAndroidResDirIntermediate);@(_LibraryResourceHashDirectories)"
2317-
ResourceNameCaseMap="$(_AndroidResourceNameCaseMap)"
2318-
>
2320+
CustomViewMapFile="$(_CustomViewMapFile)"
2321+
AcwMapFile="$(_AcwMapFile)"
2322+
ResourceDirectories="$(MonoAndroidResDirIntermediate);@(_LibraryResourceHashDirectories)"
2323+
ResourceNameCaseMap="$(_AndroidResourceNameCaseMap)">
23192324
<Output TaskParameter="Processed" ItemName="_ProcessedCustomViews" />
23202325
</ConvertCustomView>
2321-
<Delete Files="@(_ProcessedCustomViews->'$(_AndroidLibraryFlatArchivesDirectory)%(Hash).stamp')"
2322-
Condition=" '$(AndroidUseAapt2)' == 'True' And '@(_ProcessedCustomViews)' != '' "
2326+
<Delete
2327+
Condition=" '$(AndroidUseAapt2)' == 'True' And '@(_ProcessedCustomViews)' != '' "
2328+
Files="@(_ProcessedCustomViews->'$(_AndroidLibraryFlatArchivesDirectory)%(Hash).stamp')"
23232329
/>
23242330
<Aapt2Compile
2325-
Condition=" '$(AndroidUseAapt2)' == 'True' And '@(_ProcessedCustomViews)' != '' "
2326-
ContinueOnError="$(DesignTimeBuild)"
2327-
ResourceDirectories="@(_LibraryResourceHashDirectories);$(MonoAndroidResDirIntermediate)"
2328-
ExplicitCrunch="$(AndroidExplicitCrunch)"
2329-
ExtraArgs="$(AndroidAapt2CompileExtraArgs)"
2330-
FlatArchivesDirectory="$(_AndroidLibraryFlatArchivesDirectory)"
2331-
ToolPath="$(Aapt2ToolPath)"
2332-
ToolExe="$(Aapt2ToolExe)">
2331+
Condition=" '$(AndroidUseAapt2)' == 'True' And '@(_ProcessedCustomViews)' != '' "
2332+
ContinueOnError="$(DesignTimeBuild)"
2333+
ResourceDirectories="@(_ProcessedCustomViews->'%(ResourceDirectory)'->Distinct())"
2334+
ExplicitCrunch="$(AndroidExplicitCrunch)"
2335+
ExtraArgs="$(AndroidAapt2CompileExtraArgs)"
2336+
FlatArchivesDirectory="$(_AndroidLibraryFlatArchivesDirectory)"
2337+
ToolPath="$(Aapt2ToolPath)"
2338+
ToolExe="$(Aapt2ToolExe)">
23332339
<Output TaskParameter="CompiledResourceFlatArchives" ItemName="_UpdatedFlatArchives" />
23342340
</Aapt2Compile>
2335-
<Touch Files="@(_UpdatedFlatArchives->'$(_AndroidLibraryFlatArchivesDirectory)\%(Filename).stamp')"
2336-
Condition=" '$(AndroidUseAapt2)' == 'True' And '@(_UpdatedFlatArchives)' != '' "
2337-
AlwaysCreate="True"
2341+
<Touch
2342+
Condition=" '@(_UpdatedFlatArchives)' != '' "
2343+
Files="@(_UpdatedFlatArchives->'$(_AndroidLibraryFlatArchivesDirectory)\%(Filename).stamp')"
2344+
AlwaysCreate="True"
23382345
/>
2339-
<Touch Files="$(_AndroidStampDirectory)_GenerateJavaStubs.stamp" AlwaysCreate="True" />
2346+
<Touch Files="$(_AndroidStampDirectory)_ConvertCustomView.stamp" AlwaysCreate="True" />
23402347
</Target>
23412348

23422349
<Target Name="_ReadAndroidManifest"
@@ -2400,6 +2407,7 @@ because xbuild doesn't support framework reference assemblies.
24002407
<PropertyGroup>
24012408
<_GeneratePackageManagerJavaDependsOn>
24022409
_GenerateJavaStubs;
2410+
_ConvertCustomView;
24032411
_GenerateEnvironmentFiles;
24042412
_AddStaticResources;
24052413
$(_AfterAddStaticResources);
@@ -2453,6 +2461,7 @@ because xbuild doesn't support framework reference assemblies.
24532461
<PropertyGroup>
24542462
<_CreateBaseApkDependsOnTargets>
24552463
_GenerateJavaStubs;
2464+
_ConvertCustomView;
24562465
_GenerateEnvironmentFiles;
24572466
_GetLibraryImports;
24582467
_CheckDuplicateJavaLibraries;
@@ -2578,7 +2587,7 @@ because xbuild doesn't support framework reference assemblies.
25782587
</ItemGroup>
25792588
</Target>
25802589

2581-
<Target Name="_FindJavaStubFiles" DependsOnTargets="_GenerateJavaStubs;_GenerateEnvironmentFiles;">
2590+
<Target Name="_FindJavaStubFiles" DependsOnTargets="_GenerateJavaStubs;_ConvertCustomView;_GenerateEnvironmentFiles;">
25822591
<CreateItem
25832592
Include="$(IntermediateOutputPath)android\src\\**\*.java">
25842593
<Output TaskParameter="Include" ItemName="_JavaStubFiles" />
@@ -2944,6 +2953,7 @@ because xbuild doesn't support framework reference assemblies.
29442953
_CopyMdbFiles;
29452954
_LinkAssemblies;
29462955
_GenerateJavaStubs;
2956+
_ConvertCustomView;
29472957
_GenerateEnvironmentFiles;
29482958
_CompileJava;
29492959
_CompileDex;

0 commit comments

Comments
 (0)