Skip to content

Commit 02c07ed

Browse files
jonathanpeppersjonpryor
authored andcommitted
[Xamarin.Android.Build.Tasks] Optionally skip ConvertResourcesCases (dotnet#2348)
Currently, we run the `<ConvertResourcesCases/>` MSBuild task for all assemblies (on each assembly's `<ResolveLibraryProjectImports/>` output). `<ConvertResourcesCases/>` is one of our slower tasks. Luckily, there are some "well-known" assemblies that we could skip, so we have decided to "whitelist" certain assemblies such as the Android support libraries, Google Play Services, Firebase, etc. A new `@(_AndroidAssemblySkipCases)` item group has been added, e.g.: <_AndroidAssemblySkipCases Include="Xamarin.Android.Support.v7.AppCompat" /> <_AndroidAssemblySkipCases Include="Xamarin.Android.Support.v7.CardView" /> The `@(_AndroidAssemblySkipCases)` item group is an indicator for `<ConvertResourcesCases/>` to just completely skip these assemblies. Additionally, we can flat out skip `.aar` files in the same manner. To make this work: - Added support to put `ITaskItem` metadata in the cache file produced by `<ResolveLibraryProjectImports/>` - Added item metadata for `%(SkipAndroidResourceProcessing)` and `%(OriginalFile)`. - `<ConvertResourcesCases/>` now skips these directories and logs `%(OriginalFile)`. - `<CollectNonEmptyDirectories/>` needs to preserve item metadata for `$(AndroidUseAapt2)` to take advantage of the functionality. The results appear to be well worth the effort! Results with `$(AndroidUseAapt2)` enabled (note this is not currently the default): Before: 9912 ms ConvertResourcesCases 9 calls 2219 ms ResolveLibraryProjectImports 1 calls After: 49 ms ConvertResourcesCases 9 calls 2185 ms ResolveLibraryProjectImports 1 calls Results with `$(AndroidUseAapt2)` disabled: Before: 1564 ms ConvertResourcesCases 1 calls 1826 ms ResolveLibraryProjectImports 1 calls After: 25 ms ConvertResourcesCases 1 calls 1685 ms ResolveLibraryProjectImports 1 calls This was the Xamarin.Forms-Integration project in this repo, an initial clean build. It is basically a "Hello World" Xamarin.Forms project. These updated numbers are from a `Release` build of Xamarin.Android. Overall this will save 1-2 seconds of `<ConvertResourcesCases/>` for default projects. This MSBuild task runs on an initial build or incremental builds when Android resources have changed. I have gotten slightly different numbers on the difference, each time I've compared. There does not appear to be any noticeable slowdown in `<ResolveLibraryProjectImports/>` due to the changes.
1 parent dd786dc commit 02c07ed

File tree

8 files changed

+325
-34
lines changed

8 files changed

+325
-34
lines changed

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@ public override bool Execute ()
2121
foreach (var directory in Directories) {
2222
var firstFile = Directory.EnumerateFiles(directory.ItemSpec, "*.*", SearchOption.AllDirectories).FirstOrDefault ();
2323
if (firstFile != null) {
24-
output.Add (new TaskItem (directory.ItemSpec, new Dictionary<string, string> () {
24+
var taskItem = new TaskItem (directory.ItemSpec, new Dictionary<string, string> () {
2525
{"FileFound", firstFile}
26-
}));
26+
});
27+
directory.CopyMetadataTo (taskItem);
28+
output.Add (taskItem);
2729
}
2830
}
2931
return !Log.HasLoggedErrors;

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,18 @@ public override bool Execute ()
4747
return true;
4848
}
4949

50-
5150
void FixupResources (Dictionary<string, string> acwMap)
5251
{
53-
foreach (var dir in ResourceDirectories)
52+
foreach (var dir in ResourceDirectories) {
53+
var skipResourceProcessing = dir.GetMetadata (ResolveLibraryProjectImports.SkipAndroidResourceProcessing);
54+
if (skipResourceProcessing != null && skipResourceProcessing.Equals ("true", StringComparison.OrdinalIgnoreCase)) {
55+
var originalFile = dir.GetMetadata (ResolveLibraryProjectImports.OriginalFile);
56+
Log.LogDebugMessage ($"Skipping: `{dir.ItemSpec}` via `{ResolveLibraryProjectImports.SkipAndroidResourceProcessing}`, original file: `{originalFile}`...");
57+
continue;
58+
}
59+
5460
FixupResources (dir, acwMap);
61+
}
5562
}
5663

5764
void FixupResources (ITaskItem item, Dictionary<string, string> acwMap)

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

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ public class ResolveLibraryProjectImports : Task
4242

4343
public string CacheFile { get; set; }
4444

45+
public string [] AssembliesToSkipCases { get; set; }
46+
4547
[Required]
4648
public bool DesignTimeBuild { get; set; }
4749

@@ -60,7 +62,15 @@ public class ResolveLibraryProjectImports : Task
6062
[Output]
6163
public ITaskItem [] ResolvedResourceDirectoryStamps { get; set; }
6264

65+
internal const string OriginalFile = "OriginalFile";
66+
internal const string SkipAndroidResourceProcessing = "SkipAndroidResourceProcessing";
67+
static readonly string [] knownMetadata = new [] {
68+
OriginalFile,
69+
SkipAndroidResourceProcessing
70+
};
71+
6372
AssemblyIdentityMap assemblyMap = new AssemblyIdentityMap();
73+
HashSet<string> assembliesToSkip;
6474

6575
public ResolveLibraryProjectImports ()
6676
{
@@ -70,20 +80,13 @@ public ResolveLibraryProjectImports ()
7080
// Extracts library project contents under e.g. obj/Debug/[lp/*.jar | res/*/*]
7181
public override bool Execute ()
7282
{
73-
Log.LogDebugMessage ("ResolveLibraryProjectImports Task");
74-
Log.LogDebugMessage (" ImportsDirectory: {0}", ImportsDirectory);
75-
Log.LogDebugMessage (" OutputDirectory: {0}", OutputDirectory);
76-
Log.LogDebugMessage (" OutputImportDirectory: {0}", OutputImportDirectory);
77-
Log.LogDebugMessage (" UseShortFileNames: {0}", UseShortFileNames);
78-
Log.LogDebugTaskItems (" Assemblies: ", Assemblies);
79-
Log.LogDebugTaskItems (" AarLibraries: ", AarLibraries);
80-
8183
var jars = new List<string> ();
82-
var resolvedResourceDirectories = new List<string> ();
84+
var resolvedResourceDirectories = new List<ITaskItem> ();
8385
var resolvedAssetDirectories = new List<string> ();
8486
var resolvedEnvironmentFiles = new List<string> ();
8587

8688
assemblyMap.Load (AssemblyIdentityMapFile);
89+
assembliesToSkip = new HashSet<string> (AssembliesToSkipCases ?? new string [0], StringComparer.OrdinalIgnoreCase);
8790

8891
using (var resolver = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: false)) {
8992
try {
@@ -95,9 +98,7 @@ public override bool Execute ()
9598
}
9699

97100
Jars = jars.ToArray ();
98-
ResolvedResourceDirectories = resolvedResourceDirectories
99-
.Select (s => new TaskItem (Path.GetFullPath (s)))
100-
.ToArray ();
101+
ResolvedResourceDirectories = resolvedResourceDirectories.ToArray ();
101102
ResolvedAssetDirectories = resolvedAssetDirectories.ToArray ();
102103
ResolvedEnvironmentFiles = resolvedEnvironmentFiles.ToArray ();
103104

@@ -120,7 +121,15 @@ public override bool Execute ()
120121
new XElement ("Jars",
121122
Jars.Select(e => new XElement ("Jar", e))),
122123
new XElement ("ResolvedResourceDirectories",
123-
ResolvedResourceDirectories.Select(e => new XElement ("ResolvedResourceDirectory", e))),
124+
ResolvedResourceDirectories.Select(dir => {
125+
var e = new XElement ("ResolvedResourceDirectory", dir.ItemSpec);
126+
foreach (var name in knownMetadata) {
127+
var value = dir.GetMetadata (name);
128+
if (!string.IsNullOrEmpty (value))
129+
e.SetAttributeValue (name, value);
130+
}
131+
return e;
132+
})),
124133
new XElement ("ResolvedAssetDirectories",
125134
ResolvedAssetDirectories.Select(e => new XElement ("ResolvedAssetDirectory", e))),
126135
new XElement ("ResolvedEnvironmentFiles",
@@ -133,10 +142,10 @@ public override bool Execute ()
133142

134143
assemblyMap.Save (AssemblyIdentityMapFile);
135144

136-
Log.LogDebugTaskItems (" Jars: ", Jars.Select (s => new TaskItem (s)).ToArray ());
137-
Log.LogDebugTaskItems (" ResolvedResourceDirectories: ", ResolvedResourceDirectories.Select (s => new TaskItem (s)).ToArray ());
138-
Log.LogDebugTaskItems (" ResolvedAssetDirectories: ", ResolvedAssetDirectories.Select (s => new TaskItem (s)).ToArray ());
139-
Log.LogDebugTaskItems (" ResolvedEnvironmentFiles: ", ResolvedEnvironmentFiles.Select (s => new TaskItem (s)).ToArray ());
145+
Log.LogDebugTaskItems (" Jars: ", Jars);
146+
Log.LogDebugTaskItems (" ResolvedResourceDirectories: ", ResolvedResourceDirectories);
147+
Log.LogDebugTaskItems (" ResolvedAssetDirectories: ", ResolvedAssetDirectories);
148+
Log.LogDebugTaskItems (" ResolvedEnvironmentFiles: ", ResolvedEnvironmentFiles);
140149
Log.LogDebugTaskItems (" ResolvedResourceDirectoryStamps: ", ResolvedResourceDirectoryStamps);
141150

142151
return !Log.HasLoggedErrors;
@@ -160,7 +169,7 @@ static string GetTargetAssembly (ITaskItem assemblyName)
160169
void Extract (
161170
DirectoryAssemblyResolver res,
162171
ICollection<string> jars,
163-
ICollection<string> resolvedResourceDirectories,
172+
ICollection<ITaskItem> resolvedResourceDirectories,
164173
ICollection<string> resolvedAssetDirectories,
165174
ICollection<string> resolvedEnvironments)
166175
{
@@ -183,9 +192,10 @@ void Extract (
183192
.Select (a => GetTargetAssembly (a))
184193
.Where (a => a != null)
185194
.Distinct ()) {
186-
string assemblyIdentName = Path.GetFileNameWithoutExtension (assemblyPath);
195+
string assemblyFileName = Path.GetFileNameWithoutExtension (assemblyPath);
196+
string assemblyIdentName = assemblyFileName;
187197
if (UseShortFileNames) {
188-
assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly (assemblyIdentName);
198+
assemblyIdentName = assemblyMap.GetLibraryImportDirectoryNameForAssembly (assemblyFileName);
189199
}
190200
string outDirForDll = Path.Combine (OutputImportDirectory, assemblyIdentName);
191201
string importsDir = Path.Combine (outDirForDll, ImportsDirectory);
@@ -210,8 +220,14 @@ void Extract (
210220
if (Directory.Exists (binAssemblyDir))
211221
resolvedAssetDirectories.Add (binAssemblyDir);
212222
#endif
213-
if (Directory.Exists (resDir))
214-
resolvedResourceDirectories.Add (resDir);
223+
if (Directory.Exists (resDir)) {
224+
var taskItem = new TaskItem (resDir, new Dictionary<string, string> {
225+
{ OriginalFile, assemblyPath },
226+
});
227+
if (assembliesToSkip.Contains (assemblyFileName))
228+
taskItem.SetMetadata (SkipAndroidResourceProcessing, "True");
229+
resolvedResourceDirectories.Add (taskItem);
230+
}
215231
if (Directory.Exists (assemblyDir))
216232
resolvedAssetDirectories.Add (assemblyDir);
217233
foreach (var env in Directory.EnumerateFiles (outDirForDll, "__AndroidEnvironment__*", SearchOption.TopDirectoryOnly)) {
@@ -308,8 +324,14 @@ void Extract (
308324
if (Directory.Exists (binAssemblyDir))
309325
resolvedAssetDirectories.Add (binAssemblyDir);
310326
#endif
311-
if (Directory.Exists (resDir))
312-
resolvedResourceDirectories.Add (resDir);
327+
if (Directory.Exists (resDir)) {
328+
var taskItem = new TaskItem (resDir, new Dictionary<string, string> {
329+
{ OriginalFile, assemblyPath }
330+
});
331+
if (assembliesToSkip.Contains (assemblyFileName))
332+
taskItem.SetMetadata (SkipAndroidResourceProcessing, "True");
333+
resolvedResourceDirectories.Add (taskItem);
334+
}
313335
if (Directory.Exists (assemblyDir))
314336
resolvedAssetDirectories.Add (assemblyDir);
315337

@@ -362,7 +384,10 @@ void Extract (
362384
}
363385
}
364386
if (Directory.Exists (resDir))
365-
resolvedResourceDirectories.Add (resDir);
387+
resolvedResourceDirectories.Add (new TaskItem (resDir, new Dictionary<string, string> {
388+
{ OriginalFile, Path.GetFullPath (aarFile.ItemSpec) },
389+
{ SkipAndroidResourceProcessing, "True" },
390+
}));
366391
if (Directory.Exists (assetsDir))
367392
resolvedAssetDirectories.Add (assetsDir);
368393
}

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,64 @@ public void BuildBasicApplicationReleaseFSharp ()
5656
}
5757
}
5858

59+
[Test]
60+
public void SkipConvertResourcesCases ()
61+
{
62+
var target = "ConvertResourcesCases";
63+
var proj = new XamarinFormsAndroidApplicationProject ();
64+
proj.OtherBuildItems.Add (new BuildItem ("AndroidAarLibrary", "Jars\\material-menu-1.1.0.aar") {
65+
WebContent = "https://repo.jfrog.org/artifactory/libs-release-bintray/com/balysv/material-menu/1.1.0/material-menu-1.1.0.aar"
66+
});
67+
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
68+
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
69+
Assert.IsFalse (b.Output.IsTargetSkipped (target), $"`{target}` should not be skipped.");
70+
71+
List<string> skipped = new List<string> (), processed = new List<string> ();
72+
bool convertResourcesCases = false;
73+
foreach (var line in b.LastBuildOutput) {
74+
if (!convertResourcesCases) {
75+
convertResourcesCases = line.StartsWith ($"Task \"{target}\"", StringComparison.OrdinalIgnoreCase);
76+
} else if (line.StartsWith ($"Done executing task \"{target}\"", StringComparison.OrdinalIgnoreCase)) {
77+
break; //end of target
78+
} else if (line.IndexOf ("Processing:", StringComparison.OrdinalIgnoreCase) >= 0) {
79+
//Processing: obj\Debug\res\layout\main.xml 10/29/2018 8:19:36 PM > 1/1/0001 12:00:00 AM
80+
processed.Add (line);
81+
} else if (line.IndexOf ("Skipping:", StringComparison.OrdinalIgnoreCase) >= 0) {
82+
//Skipping: `obj\Debug\lp\5\jl\res` via `SkipAndroidResourceProcessing`, original file: `bin\TestDebug\temp\packages\Xamarin.Android.Support.Compat.27.0.2.1\lib\MonoAndroid81\Xamarin.Android.Support.Compat.dll`...
83+
skipped.Add (line);
84+
}
85+
}
86+
87+
var resources = new [] {
88+
Path.Combine ("layout", "main.xml"),
89+
Path.Combine ("layout", "tabbar.xml"),
90+
Path.Combine ("layout", "toolbar.xml"),
91+
Path.Combine ("values", "colors.xml"),
92+
Path.Combine ("values", "strings.xml"),
93+
Path.Combine ("values", "styles.xml"),
94+
};
95+
foreach (var resource in resources) {
96+
Assert.IsTrue (StringAssertEx.ContainsText (processed, resource), $"`{target}` should process `{resource}`.");
97+
}
98+
99+
var files = new [] {
100+
"Xamarin.Android.Support.Compat.dll",
101+
"Xamarin.Android.Support.Design.dll",
102+
"Xamarin.Android.Support.Media.Compat.dll",
103+
"Xamarin.Android.Support.Transition.dll",
104+
"Xamarin.Android.Support.v4.dll",
105+
"Xamarin.Android.Support.v7.AppCompat.dll",
106+
"Xamarin.Android.Support.v7.CardView.dll",
107+
"Xamarin.Android.Support.v7.MediaRouter.dll",
108+
"Xamarin.Android.Support.v7.RecyclerView.dll",
109+
"material-menu-1.1.0.aar",
110+
};
111+
foreach (var file in skipped) {
112+
Assert.IsTrue (StringAssertEx.ContainsText (skipped, file), $"`{target}` should skip `{file}`.");
113+
}
114+
}
115+
}
116+
59117
[Test]
60118
public void BuildInParallel ()
61119
{

src/Xamarin.Android.Build.Tasks/Utilities/XDocumentExtensions.cs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,28 @@ namespace Xamarin.Android.Tasks
1010
{
1111
public static class XDocumentExtensions
1212
{
13+
const string PathsElementName = "Paths";
14+
1315
public static ITaskItem[] GetPathsAsTaskItems (this XDocument doc, params string[] paths)
1416
{
15-
return doc.GetPaths (paths)
16-
.Select(x => new TaskItem(x))
17-
.ToArray ();
17+
var e = doc.Elements (PathsElementName);
18+
foreach (var p in paths)
19+
e = e.Elements (p);
20+
return e.Select (ToTaskItem).ToArray ();
21+
}
22+
23+
static ITaskItem ToTaskItem (XElement element)
24+
{
25+
var taskItem = new TaskItem (element.Value);
26+
foreach (var attribute in element.Attributes ()) {
27+
taskItem.SetMetadata (attribute.Name.LocalName, attribute.Value);
28+
}
29+
return taskItem;
1830
}
1931

2032
public static string[] GetPaths (this XDocument doc, params string[] paths)
2133
{
22-
var e = doc.Elements ("Paths");
34+
var e = doc.Elements (PathsElementName);
2335
foreach (var p in paths)
2436
e = e.Elements (p);
2537
return e.Select (p => p.Value).ToArray ();

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@
109109
<None Include="Xamarin.Android.VisualBasic.targets">
110110
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
111111
</None>
112+
<None Include="Xamarin.Android.SkipCases.projitems">
113+
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
114+
</None>
112115
</ItemGroup>
113116
<ItemGroup>
114117
<_SharedRuntimeAssemblies Include="@(MonoProfileAssembly->'$(_SharedRuntimeBuildPath)v1.0\%(Identity)')" />

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
368368
-->
369369
<!-- As we split up/refactor this file, put new imports here -->
370370
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.D8.targets" />
371+
<Import Project="$(MSBuildThisFileDirectory)Xamarin.Android.SkipCases.projitems" />
371372

372373
<Target Name="_SeparateAppExtensionReferences">
373374
<CreateItem Include="@(ProjectReference)" PreserveExistingMetadata="true" Condition="'%(Identity)' != '' AND '%(ProjectReference.IsAppExtension)' == 'true'">
@@ -1345,7 +1346,8 @@ because xbuild doesn't support framework reference assemblies.
13451346
UseShortFileNames="$(UseShortFileNames)"
13461347
OutputDirectory="$(IntermediateOutputPath)"
13471348
AssemblyIdentityMapFile="$(_AndroidLibrayProjectAssemblyMapFile)"
1348-
OutputImportDirectory="$(_AndroidLibrayProjectIntermediatePath)">
1349+
OutputImportDirectory="$(_AndroidLibrayProjectIntermediatePath)"
1350+
AssembliesToSkipCases="@(_AndroidAssemblySkipCases)">
13491351
</ResolveLibraryProjectImports>
13501352
<Touch Files="$(_AndroidStampDirectory)_ResolveLibraryProjectImports.stamp" AlwaysCreate="True" />
13511353
</Target>

0 commit comments

Comments
 (0)