Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] AssemblyStore+AndroidSkipAddToPackage (#…
Browse files Browse the repository at this point in the history
…6495)

Fixes: https://developercommunity.visualstudio.com/t/Xamarin-Forms-Android-App-crashes-on-sta/1578587

Context: c927026

Commit c927026 added support for assembly stores which, in turn,
depends on knowing the exact number of assemblies packaged in the
`.apk` in order to properly perform binary search on the tables of
assembly name hashes.  This information is recorded by the
`<GeneratePackageManagerJava/>` task and stored in the
`libxamarin-app.so` library at build time, and then used by the
assembly store code at run time.

The code responsible for counting failed to skip assemblies which had
`%(AndroidSkipAddToPackage)` metadata (21561c3), in our case the old
Android Support Library ones, replaced by AndroidX, with the metadata
set on the old assemblies by the AndroidX migration code.

Properly ignore skipped assemblies when counting.

Additionally, improve `assembly-store-reader` to show the hash tables
stored in the blob as well as check whether they are properly sorted.

Co-authored-by: Jonathan Peppers <jonathan.peppers@gmail.com>
  • Loading branch information
grendello and jonathanpeppers committed Nov 22, 2021
1 parent db161ae commit cb484d2
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 5 deletions.
Expand Up @@ -297,6 +297,10 @@ void AddEnvironment ()
return;
}
if (Boolean.TryParse (assembly.GetMetadata ("AndroidSkipAddToPackage"), out bool value) && value) {
return;
}
string abi = assembly.GetMetadata ("Abi");
if (String.IsNullOrEmpty (abi)) {
assemblyCount++;
Expand Down
Expand Up @@ -83,6 +83,43 @@ public void CheckMonoComponentsMask (bool enableProfiler, bool useInterpreter, b
}
}

[Test]
[NonParallelizable]
[Category ("SmokeTests")]
public void CheckAssemblyCounts ([Values (false, true)] bool isRelease)
{
var proj = new XamarinFormsAndroidApplicationProject {
IsRelease = isRelease,
EmbedAssembliesIntoApk = true,
};
proj.PackageReferences.Add (KnownPackages.AndroidXMigration);
proj.PackageReferences.Add (KnownPackages.AndroidXAppCompat);
proj.PackageReferences.Add (KnownPackages.AndroidXAppCompatResources);
proj.PackageReferences.Add (KnownPackages.AndroidXBrowser);
proj.PackageReferences.Add (KnownPackages.AndroidXMediaRouter);
proj.PackageReferences.Add (KnownPackages.AndroidXLegacySupportV4);
proj.PackageReferences.Add (KnownPackages.AndroidXLifecycleLiveData);
proj.PackageReferences.Add (KnownPackages.XamarinGoogleAndroidMaterial);

var abis = new [] { "armeabi-v7a", "x86" };
proj.SetAndroidSupportedAbis (abis);
proj.SetProperty (proj.ActiveConfigurationProperties, "AndroidUseAssemblyStore", "True");

using (var b = CreateApkBuilder ()) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
string objPath = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath);

List<string> envFiles = EnvironmentHelper.GatherEnvironmentFiles (objPath, String.Join (";", abis), true);
EnvironmentHelper.ApplicationConfig app_config = EnvironmentHelper.ReadApplicationConfig (envFiles);
Assert.That (app_config, Is.Not.Null, "application_config must be present in the environment files");

string apk = Path.Combine (Root, b.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk");
var helper = new ArchiveAssemblyHelper (apk, useAssemblyStores: true);

Assert.IsTrue (app_config.number_of_assemblies_in_apk == (uint)helper.GetNumberOfAssemblies (), "Assembly count must be equal between ApplicationConfig and the archive contents");
}
}

[Test]
[Category ("SmokeTests")]
public void SmokeTestBuildWithSpecialCharacters ([Values (false, true)] bool forms)
Expand Down
Expand Up @@ -164,7 +164,7 @@ public List<string> ListArchiveContents (string storeEntryPrefix = DefaultAssemb
foreach (var asm in explorer.Assemblies) {
string prefix = storeEntryPrefix;

if (haveMultipleRids &&!String.IsNullOrEmpty (asm.Store.Arch)) {
if (haveMultipleRids && !String.IsNullOrEmpty (asm.Store.Arch)) {
string arch = ArchToAbi[asm.Store.Arch];
prefix = $"{prefix}{arch}/";
}
Expand All @@ -187,6 +187,27 @@ public List<string> ListArchiveContents (string storeEntryPrefix = DefaultAssemb
return entries;
}

public int GetNumberOfAssemblies (bool countAbiAssembliesOnce = true, bool forceRefresh = false)
{
List<string> contents = ListArchiveContents (assembliesRootDir, forceRefresh);
var dlls = contents.Where (x => x.EndsWith (".dll", StringComparison.OrdinalIgnoreCase));

if (!countAbiAssembliesOnce) {
return dlls.Count ();
}

var cache = new HashSet<string> (StringComparer.OrdinalIgnoreCase);
return dlls.Where (x => {
string name = Path.GetFileName (x);
if (cache.Contains (name)) {
return false;
}
cache.Add (name);
return true;
}).Count ();
}

public bool Exists (string entryPath, bool forceRefresh = false)
{
List<string> contents = ListArchiveContents (assembliesRootDir, forceRefresh);
Expand Down
5 changes: 2 additions & 3 deletions src/monodroid/jni/embedded-assemblies.cc
Expand Up @@ -349,7 +349,7 @@ EmbeddedAssemblies::find_assembly_store_entry (hash_t hash, const AssemblyStoreH
{
#if !defined (__MINGW32__) || (defined (__MINGW32__) && __GNUC__ >= 10)
hash_t entry_hash;
const AssemblyStoreHashEntry *ret;
const AssemblyStoreHashEntry *ret = nullptr;

while (entry_count > 0) {
ret = entries + (entry_count / 2);
Expand All @@ -358,8 +358,7 @@ EmbeddedAssemblies::find_assembly_store_entry (hash_t hash, const AssemblyStoreH
} else {
entry_hash = ret->hash32;
}

std::strong_ordering result = hash <=> entry_hash;
auto result = hash <=> entry_hash;

if (result < 0) {
entry_count /= 2;
Expand Down
47 changes: 46 additions & 1 deletion tools/assembly-store-reader/Program.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

using Mono.Options;

Expand Down Expand Up @@ -50,13 +52,56 @@ static void ShowAssemblyStoreInfo (string storePath)
Console.WriteLine ();
}

AssemblyStoreReader? indexStore = null;
foreach (var kvp in explorer.Stores) {
List<AssemblyStoreReader> storeList = kvp.Value;
foreach (AssemblyStoreReader store in storeList) {
if (!store.HasGlobalIndex) {
continue;
}

indexStore = store;
break;
}
}

if (indexStore == null) {
Console.WriteLine ("Index store not found in the set, not showing hash tables.");
return;
}

Console.WriteLine ("Hash tables:");
WriteHashTable ("32", indexStore.GlobalIndex32, "x08");
WriteHashTable ("64", indexStore.GlobalIndex64, "x016");

void WriteHashTable (string bitness, List<AssemblyStoreHashEntry> table, string hashFormat)
{
Console.WriteLine ($" {bitness}-bit:");
var sb = new StringBuilder ();
ulong prev = 0;

for (int i = 0; i < table.Count; i++) {
AssemblyStoreHashEntry entry = table[i];

if (entry.Hash < prev) {
Console.WriteLine ($"{infoIndent}* entry {i} is smaller than the previous one");
} else if (entry.Hash == prev) {
Console.WriteLine ($"{infoIndent}* entry {i} is a duplicate of the previous one");
}
sb.Append ($"{infoIndent}0x");
sb.AppendLine (entry.Hash.ToString (hashFormat));
prev = entry.Hash;
}
Console.WriteLine (sb.ToString ());
}

void WriteOptionalDataLine (string label, uint offset, uint size)
{
Console.Write ($"{infoIndent}{label}: ");
if (offset == 0) {
Console.WriteLine ("absent");
} else {
Console.WriteLine ("offset == {offset}; size == {size}");
Console.WriteLine ($"offset == {offset}; size == {size}");
}
}

Expand Down

0 comments on commit cb484d2

Please sign in to comment.