From 8eda7bb65628a5e2e9f917967beb0418ce2e8a5a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 7 Dec 2022 11:55:22 -0600 Subject: [PATCH 1/2] [Xamarin.Android.Build.Tasks] avoid `File.Exists()` check `dotnet-trace` output of a `dotnet new maui` app: dotnet trace collect --format speedscope -- C:\src\xamarin-android\bin\Release\dotnet\dotnet.exe build -bl --no-restore bar.csproj Shows an interesting case of `File.Exists()`: 49.88ms xamarin.android.build.tasks!Xamarin.Android.Tasks.FilterAssemblies.RunTask() 8.09ms System.Private.CoreLib.il!System.IO.File.Exists(class System.String) The common case is the files always exist. The rare case appears to be 783ac9e9, which would be when something like a `` doesn't exist. Instead of calling `File.Exists()` on every .NET assembly (likely done multiple times in a build), we can handle `FileNotFoundException` and take appropriate action. This will save ~8ms on *every* build, and we should get the exact same behavior as before. Potentially larger projects could even save more. --- .../Tasks/FilterAssemblies.cs | 85 ++++++++++--------- 1 file changed, 45 insertions(+), 40 deletions(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs index a0b99b9dcbf..da41939d6d7 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs @@ -42,57 +42,62 @@ public override bool RunTask () if (frameworkReferenceName.StartsWith ("Microsoft.NETCore.", StringComparison.OrdinalIgnoreCase)) { continue; // No need to process BCL assemblies } - if (!File.Exists (assemblyItem.ItemSpec)) { - Log.LogDebugMessage ($"Skipping non-existent dependency '{assemblyItem.ItemSpec}'."); - continue; - } if (string.Equals (assemblyItem.GetMetadata ("TargetPlatformIdentifier"), "android", StringComparison.OrdinalIgnoreCase)) { output.Add (assemblyItem); continue; } - using (var pe = new PEReader (File.OpenRead (assemblyItem.ItemSpec))) { - var reader = pe.GetMetadataReader (); - // Check in-memory cache - var module = reader.GetModuleDefinition (); - var key = (nameof (FilterAssemblies), reader.GetGuid (module.Mvid)); - var value = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (key, Lifetime); - if (value is bool isMonoAndroidAssembly) { - if (isMonoAndroidAssembly) { - Log.LogDebugMessage ($"Cached: {assemblyItem.ItemSpec}"); - output.Add (assemblyItem); - } - continue; - } - // Check assembly definition - var assemblyDefinition = reader.GetAssemblyDefinition (); - if (IsAndroidAssembly (assemblyDefinition, reader)) { - output.Add (assemblyItem); - BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); - continue; - } - // Fallback to looking for a Mono.Android reference - if (MonoAndroidHelper.HasMonoAndroidReference (reader)) { - Log.LogDebugMessage ($"Mono.Android reference found: {assemblyItem.ItemSpec}"); - output.Add (assemblyItem); - BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); - continue; - } - // Fallback to looking for *.jar or __Android EmbeddedResource files - if (HasEmbeddedResource (reader)) { - Log.LogDebugMessage ($"EmbeddedResource found: {assemblyItem.ItemSpec}"); - output.Add (assemblyItem); - BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); - continue; - } - // Not a MonoAndroid assembly, store false - BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: false, Lifetime); + try { + ProcessAssembly (assemblyItem, output); + } catch (FileNotFoundException) { + Log.LogDebugMessage ($"Skipping non-existent dependency '{assemblyItem.ItemSpec}'."); } + } OutputAssemblies = output.ToArray (); return !Log.HasLoggedErrors; } + void ProcessAssembly(ITaskItem assemblyItem, List output) + { + using var pe = new PEReader (File.OpenRead (assemblyItem.ItemSpec)); + var reader = pe.GetMetadataReader (); + // Check in-memory cache + var module = reader.GetModuleDefinition (); + var key = (nameof (FilterAssemblies), reader.GetGuid (module.Mvid)); + var value = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (key, Lifetime); + if (value is bool isMonoAndroidAssembly) { + if (isMonoAndroidAssembly) { + Log.LogDebugMessage ($"Cached: {assemblyItem.ItemSpec}"); + output.Add (assemblyItem); + } + return; + } + // Check assembly definition + var assemblyDefinition = reader.GetAssemblyDefinition (); + if (IsAndroidAssembly (assemblyDefinition, reader)) { + output.Add (assemblyItem); + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); + return; + } + // Fallback to looking for a Mono.Android reference + if (MonoAndroidHelper.HasMonoAndroidReference (reader)) { + Log.LogDebugMessage ($"Mono.Android reference found: {assemblyItem.ItemSpec}"); + output.Add (assemblyItem); + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); + return; + } + // Fallback to looking for *.jar or __Android EmbeddedResource files + if (HasEmbeddedResource (reader)) { + Log.LogDebugMessage ($"EmbeddedResource found: {assemblyItem.ItemSpec}"); + output.Add (assemblyItem); + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: true, Lifetime); + return; + } + // Not a MonoAndroid assembly, store false + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, value: false, Lifetime); + } + bool IsAndroidAssembly (AssemblyDefinition assembly, MetadataReader reader) { foreach (var handle in assembly.GetCustomAttributes ()) { From bf703eb7c68f2fe60f4f8ff4b3def68682d0a693 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 8 Dec 2022 16:19:20 -0600 Subject: [PATCH 2/2] Handle `DirectoryNotFoundException` --- src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs index da41939d6d7..cf783c038d8 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs @@ -48,7 +48,7 @@ public override bool RunTask () } try { ProcessAssembly (assemblyItem, output); - } catch (FileNotFoundException) { + } catch (Exception e) when (e is FileNotFoundException || e is DirectoryNotFoundException) { Log.LogDebugMessage ($"Skipping non-existent dependency '{assemblyItem.ItemSpec}'."); }