Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] <FilterAssemblies/> in-memory cache (#4975
Browse files Browse the repository at this point in the history
)

In a customer's build log, a large solution build with no changes had:

    1956 ms  FilterAssemblies                          18 calls

The `<FilterAssemblies/>` MSBuild task runs on every build for class
libraries and application projects. It looks at assemblies to classify
them as a "MonoAndroid" assembly.

Since we are already using System.Reflection.Metadata to open each
assembly, we can use each assembly's MVID and `RegisterTaskObject` to
skip future calls to the same assembly. This will be helpful in
incremental builds in the IDE, as well as, large solutions that
encounter the same assemblies throughout a build.

Using the in-memory cache, we don't need to check any file timestamps.
Unique MVIDs will yield the same result for the `<FilterAssemblies/>`
task -- regardless if the assembly was built with `$(Deterministic)`
or not.

~~ Results ~~

I did not have access to the original customer project, so I tested
this change using xamarin/Xamarin.Forms@d9926450 instead. There
are 8 Xamarin.Android projects during the build.

    Before:
    165 ms  FilterAssemblies                          16 calls
    After:
    156 ms  FilterAssemblies                          16 calls
    After (with MSBuild node running):
    99 ms  FilterAssemblies                          16 calls

Even on the first build, many assemblies are pulled from the cache. It
printed ~326 instances of this log message:

    Task "FilterAssemblies"
    ...
        Cached: C:\src\Xamarin.Forms\Xamarin.Forms.Platform.Android.FormsViewGroup\bin\Release\FormsViewGroup.dll

This saves ~10ms from an initial build of Xamarin.Forms' source and
~66ms from incremental builds. This will have a bigger impact on
larger solutions.

~~ Other changes ~~

I removed an unused `DesignTimeBuild` property from
`<FilterAssemblies/>`.

I removed the log message:

    // In the rare case, [assembly: TargetFramework("MonoAndroid,Version=v9.0")] may not match
    Log.LogDebugMessage ($"{nameof (TargetFrameworkIdentifier)} did not match: {assemblyItem.ItemSpec}");

It is actually not a "rare" case, it was printed 330 times from the
customer's log. This log message didn't add much value, so I removed
it to reduce string allocations.
  • Loading branch information
jonathanpeppers authored Aug 5, 2020
1 parent 396aca1 commit a963c30
Show file tree
Hide file tree
Showing 3 changed files with 27 additions and 12 deletions.
6 changes: 6 additions & 0 deletions Documentation/release-notes/4975.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
### Build and deployment performance

The `<FilterAssemblies/>` MSBuild task caches information in-memory to
improve build times. Incremental builds of Xamarin.Forms' source code
were improved from 165ms to 99ms on a test system. This will have a
bigger impact on large solutions.
25 changes: 19 additions & 6 deletions src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ public class FilterAssemblies : AndroidTask
public override string TaskPrefix => "FLT";

const string TargetFrameworkIdentifier = "MonoAndroid";

public bool DesignTimeBuild { get; set; }
const RegisteredTaskObjectLifetime Lifetime = RegisteredTaskObjectLifetime.AppDomain;
const bool AllowEarlyCollection = false;

public ITaskItem [] InputAssemblies { get; set; }

Expand All @@ -41,28 +41,41 @@ public override bool RunTask ()
}
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.GetRegisteredTaskObject (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 ();
var targetFrameworkIdentifier = GetTargetFrameworkIdentifier (assemblyDefinition, reader);
if (string.Compare (targetFrameworkIdentifier, TargetFrameworkIdentifier, StringComparison.OrdinalIgnoreCase) == 0) {
output.Add (assemblyItem);
BuildEngine4.RegisterTaskObject (key, true, Lifetime, AllowEarlyCollection);
continue;
}

// In the rare case, [assembly: TargetFramework("MonoAndroid,Version=v9.0")] may not match
Log.LogDebugMessage ($"{nameof (TargetFrameworkIdentifier)} did not match: {assemblyItem.ItemSpec}");

// Fallback to looking for a Mono.Android reference
if (MonoAndroidHelper.HasMonoAndroidReference (reader)) {
Log.LogDebugMessage ($"Mono.Android reference found: {assemblyItem.ItemSpec}");
output.Add (assemblyItem);
BuildEngine4.RegisterTaskObject (key, true, Lifetime, AllowEarlyCollection);
continue;
}
// Fallback to looking for *.jar or __Android EmbeddedResource files
if (HasEmbeddedResource (reader)) {
Log.LogDebugMessage ($"EmbeddedResource found: {assemblyItem.ItemSpec}");
output.Add (assemblyItem);
BuildEngine4.RegisterTaskObject (key, true, Lifetime, AllowEarlyCollection);
continue;
}
// Not a MonoAndroid assembly, store false
BuildEngine4.RegisterTaskObject (key, false, Lifetime, AllowEarlyCollection);
}
}
OutputAssemblies = output.ToArray ();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -430,14 +430,10 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
<_ReferenceDependencyPaths Include="@(ReferenceDependencyPaths)"
Condition="'$(AndroidApplication)' == '' Or !$(AndroidApplication)"/>
</ItemGroup>
<FilterAssemblies
DesignTimeBuild="$(DesignTimeBuild)"
InputAssemblies="@(_ReferencePath)">
<FilterAssemblies InputAssemblies="@(_ReferencePath)">
<Output TaskParameter="OutputAssemblies" ItemName="_MonoAndroidReferencePath" />
</FilterAssemblies>
<FilterAssemblies
DesignTimeBuild="$(DesignTimeBuild)"
InputAssemblies="@(_ReferenceDependencyPaths)">
<FilterAssemblies InputAssemblies="@(_ReferenceDependencyPaths)">
<Output TaskParameter="OutputAssemblies" ItemName="_MonoAndroidReferenceDependencyPaths" />
</FilterAssemblies>
</Target>
Expand Down

0 comments on commit a963c30

Please sign in to comment.