diff --git a/CHANGELOG.md b/CHANGELOG.md index b32853db5b..8d608379d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ ### BREAKING CHANGES - This release adds support for .NET 10 and drops support for net8.0-android, net8.0-ios, net8.0-maccatalyst and net8.0-windows10.0.19041.0 ([#4461](https://github.com/getsentry/sentry-dotnet/pull/4461)) +- Added support for v3 of the Android AssemblyStore format that is used in .NET 10 and dropped support for v1 that was used in .NET 8 ([#4516](https://github.com/getsentry/sentry-dotnet/pull/4516)) + ## Unreleased ### Features @@ -54,7 +56,7 @@ - [diff](https://github.com/getsentry/sentry-cli/compare/2.53.0...2.54.0) - Bump Native SDK from v0.10.1 to v0.11.0 ([#4542](https://github.com/getsentry/sentry-dotnet/pull/4542)) - [changelog](https://github.com/getsentry/sentry-native/blob/master/CHANGELOG.md#0110) - - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.0) + - [diff](https://github.com/getsentry/sentry-native/compare/0.10.1...0.11.0) ## 5.15.0 diff --git a/modules/sentry-native b/modules/sentry-native index 075b3bfee1..11f94efc64 160000 --- a/modules/sentry-native +++ b/modules/sentry-native @@ -1 +1 @@ -Subproject commit 075b3bfee1dbb85fa10d50df631286196943a3e0 +Subproject commit 11f94efc64d55e90aef9456ce01716c846ae1732 diff --git a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs index e111a46f71..2e4b8d03ab 100644 --- a/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs +++ b/src/Sentry.Android.AssemblyReader/AndroidAssemblyReaderFactory.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader; @@ -19,28 +18,13 @@ public static IAndroidAssemblyReader Open(string apkPath, IList supporte { logger?.Invoke(DebugLoggerLevel.Debug, "Opening APK: {0}", apkPath); -#if NET9_0 - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V2 APK layout."); - if (AndroidAssemblyStoreReaderV2.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) + if (AndroidAssemblyStoreReader.TryReadStore(apkPath, supportedAbis, logger, out var readerV2)) { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V2"); + logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore"); return readerV2; } logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV2(apkPath, supportedAbis, logger); -#else - logger?.Invoke(DebugLoggerLevel.Debug, "Reading files using V1 APK layout."); - - var zipArchive = ZipFile.OpenRead(apkPath); - if (zipArchive.GetEntry("assemblies/assemblies.manifest") is not null) - { - logger?.Invoke(DebugLoggerLevel.Debug, "APK uses AssemblyStore V1"); - return new AndroidAssemblyStoreReaderV1(zipArchive, supportedAbis, logger); - } - - logger?.Invoke(DebugLoggerLevel.Debug, "APK doesn't use AssemblyStore"); - return new AndroidAssemblyDirectoryReaderV1(zipArchive, supportedAbis, logger); -#endif + return new AndroidAssemblyDirectoryReader(apkPath, supportedAbis, logger); } } diff --git a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt b/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt deleted file mode 100644 index 16c3f15ed7..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/ATTRIBUTION.txt +++ /dev/null @@ -1,31 +0,0 @@ -Parts of the code in this subdirectory have been adapted from -https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - -The original license is as follows: - - -Xamarin.Android SDK - -The MIT License (MIT) - -Copyright (c) .NET Foundation Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs deleted file mode 100644 index 7b5e1c1a7e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyDirectoryReaderV1.cs +++ /dev/null @@ -1,53 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - public AndroidAssemblyDirectoryReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) { } - - public PEReader? TryReadAssembly(string name) - { - if (File.Exists(name)) - { - // The assembly is already extracted to the file system. Just read it. - var stream = File.OpenRead(name); - return new PEReader(stream); - } - - var zipEntry = FindAssembly(name); - if (zipEntry is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK at {1}", name, zipEntry.FullName); - - // We need a seekable stream for the PEReader (or even to check whether the DLL is compressed), so make a copy. - var memStream = zipEntry.Extract(); - return ArchiveUtils.CreatePEReader(name, memStream, Logger); - } - - private ZipArchiveEntry? FindAssembly(string name) - { - var zipEntry = ZipArchive.GetEntry($"assemblies/{name}"); - - if (zipEntry is null) - { - foreach (var abi in SupportedAbis) - { - if (abi.Length > 0) - { - zipEntry = ZipArchive.GetEntry($"assemblies/{abi}/{name}"); - if (zipEntry is not null) - { - break; - } - } - } - } - - return zipEntry; - } -} diff --git a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs deleted file mode 100644 index 60c947b23e..0000000000 --- a/src/Sentry.Android.AssemblyReader/V1/AndroidAssemblyStoreReaderV1.cs +++ /dev/null @@ -1,510 +0,0 @@ -namespace Sentry.Android.AssemblyReader.V1; - -// See https://devblogs.microsoft.com/dotnet/performance-improvements-in-dotnet-maui/#single-file-assembly-stores -internal sealed class AndroidAssemblyStoreReaderV1 : AndroidAssemblyReader, IAndroidAssemblyReader -{ - private readonly AssemblyStoreExplorer _explorer; - - public AndroidAssemblyStoreReaderV1(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - : base(zip, supportedAbis, logger) - { - _explorer = new(zip, supportedAbis, logger); - } - - public PEReader? TryReadAssembly(string name) - { - var assembly = TryFindAssembly(name); - if (assembly is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't find assembly {0} in the APK AssemblyStore", name); - return null; - } - - Logger?.Invoke(DebugLoggerLevel.Debug, "Resolved assembly {0} in the APK {1} AssemblyStore", name, assembly.Store.Arch); - - var stream = assembly.GetImage(); - if (stream is null) - { - Logger?.Invoke(DebugLoggerLevel.Debug, "Couldn't access assembly {0} image stream", name); - return null; - } - - return ArchiveUtils.CreatePEReader(name, stream, Logger); - } - - private AssemblyStoreAssembly? TryFindAssembly(string name) - { - if (_explorer.AssembliesByName.TryGetValue(name, out var assembly)) - { - return assembly; - } - - if (name.EndsWith(".dll", ignoreCase: true, CultureInfo.InvariantCulture) || - name.EndsWith(".exe", ignoreCase: true, CultureInfo.InvariantCulture)) - { - if (_explorer.AssembliesByName.TryGetValue(name.Substring(0, name.Length - 4), out assembly)) - { - return assembly; - } - } - - return null; - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreExplorer.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreExplorer - { - private AssemblyStoreReader? _indexStore; - private readonly AssemblyStoreManifestReader _manifest; - private readonly DebugLogger? _logger; - - public IDictionary AssembliesByName { get; } = - new SortedDictionary(StringComparer.OrdinalIgnoreCase); - - public IDictionary AssembliesByHash32 { get; } = - new Dictionary(); - - public IDictionary AssembliesByHash64 { get; } = - new Dictionary(); - - public List Assemblies { get; } = new(); - - public IDictionary> Stores { get; } = - new SortedDictionary>(); - - public AssemblyStoreExplorer(ZipArchive zip, IList supportedAbis, DebugLogger? logger) - { - _logger = logger; - _manifest = new AssemblyStoreManifestReader(zip.GetEntry("assemblies/assemblies.manifest")!.Open()); - - TryAddStore(zip, null); - foreach (var abi in supportedAbis) - { - if (!string.IsNullOrEmpty(abi)) - { - TryAddStore(zip, abi); - } - } - - zip.Dispose(); - ProcessStores(); - } - - private void ProcessStores() - { - if (Stores.Count == 0 || _indexStore == null) - { - return; - } - - ProcessIndex(_indexStore.GlobalIndex32, "32", (he, assembly) => - { - assembly.Hash32 = (uint)he.Hash; - assembly.RuntimeIndex = he.MappingIndex; - - if (_manifest.EntriesByHash32.TryGetValue(assembly.Hash32, out var me)) - { - assembly.Name = me.Name; - } - - if (!AssembliesByHash32.ContainsKey(assembly.Hash32)) - { - AssembliesByHash32.Add(assembly.Hash32, assembly); - } - }); - - ProcessIndex(_indexStore.GlobalIndex64, "64", (he, assembly) => - { - assembly.Hash64 = he.Hash; - if (assembly.RuntimeIndex != he.MappingIndex) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"assembly with hashes 0x{assembly.Hash32} and 0x{assembly.Hash64} has a different 32-bit runtime index ({assembly.RuntimeIndex}) than the 64-bit runtime index({he.MappingIndex})"); - } - - if (_manifest.EntriesByHash64.TryGetValue(assembly.Hash64, out var me)) - { - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} did not match any assembly name in the manifest"); - assembly.Name = me.Name; - if (string.IsNullOrEmpty(assembly.Name)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"64-bit hash 0x{assembly.Hash64:x} did not match any assembly name in the manifest"); - } - } - else if (!string.Equals(assembly.Name, me.Name, StringComparison.Ordinal)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"32-bit hash 0x{assembly.Hash32:x} maps to assembly name '{assembly.Name}', however 64-bit hash 0x{assembly.Hash64:x} for the same entry matches assembly name '{me.Name}'"); - } - } - - if (!AssembliesByHash64.ContainsKey(assembly.Hash64)) - { - AssembliesByHash64.Add(assembly.Hash64, assembly); - } - }); - - foreach (var kvp in Stores) - { - var list = kvp.Value; - if (list.Count < 2) - { - continue; - } - - var template = list[0]; - for (var i = 1; i < list.Count; i++) - { - var other = list[i]; - if (!template.HasIdenticalContent(other)) - { - throw new Exception( - $"Store ID {template.StoreId} for architecture {other.Arch} is not identical to other stores with the same ID"); - } - } - } - - void ProcessIndex(List index, string bitness, - Action assemblyHandler) - { - foreach (var he in index) - { - if (!Stores.TryGetValue(he.StoreId, out var storeList)) - { - _logger?.Invoke(DebugLoggerLevel.Debug, $"store with id {he.StoreId} not part of the set"); - continue; - } - - foreach (var store in storeList) - { - if (he.LocalStoreIndex >= (uint)store.Assemblies.Count) - { - _logger?.Invoke(DebugLoggerLevel.Debug, - $"{bitness}-bit index entry with hash 0x{he.Hash:x} has invalid store {store.StoreId} index {he.LocalStoreIndex} (maximum allowed is {store.Assemblies.Count})"); - continue; - } - - var assembly = store.Assemblies[(int)he.LocalStoreIndex]; - assemblyHandler(he, assembly); - - if (!AssembliesByName.ContainsKey(assembly.Name)) - { - AssembliesByName.Add(assembly.Name, assembly); - } - } - } - } - } - - private void TryAddStore(ZipArchive archive, string? abi) - { - var infix = abi is null ? "" : $".{abi}"; - if (archive.GetEntry($"assemblies/assemblies{infix}.blob") is { } zipEntry) - { - var memStream = new MemoryStream((int)zipEntry.Length); - using (var zipStream = zipEntry.Open()) - { - zipStream.CopyTo(memStream); - memStream.Position = 0; - } - - AddStore(new AssemblyStoreReader(memStream, abi)); - } - } - - private void AddStore(AssemblyStoreReader reader) - { - if (reader.HasGlobalIndex) - { - _indexStore = reader; - } - - if (!Stores.TryGetValue(reader.StoreId, out var storeList)) - { - storeList = new List(); - Stores.Add(reader.StoreId, storeList); - } - - storeList.Add(reader); - - Assemblies.AddRange(reader.Assemblies); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestReader - { - public List Entries { get; } = new(); - public Dictionary EntriesByHash32 { get; } = new(); - public Dictionary EntriesByHash64 { get; } = new(); - - public AssemblyStoreManifestReader(Stream manifest) - { - using var sr = new StreamReader(manifest, Encoding.UTF8, detectEncodingFromByteOrderMarks: false); - ReadManifest(sr); - } - - private void ReadManifest(StreamReader reader) - { - // First line is ignored, it contains headers - reader.ReadLine(); - - // Each subsequent line consists of fields separated with any number of spaces (for the pleasure of a human being reading the manifest) - while (!reader.EndOfStream) - { - var fields = reader.ReadLine()?.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - if (fields == null) - { - continue; - } - - var entry = new AssemblyStoreManifestEntry(fields); - Entries.Add(entry); - if (entry.Hash32 != 0) - { - EntriesByHash32.Add(entry.Hash32, entry); - } - - if (entry.Hash64 != 0) - { - EntriesByHash64.Add(entry.Hash64, entry); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreManifestEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreManifestEntry - { - // Fields are: - // Hash 32 | Hash 64 | Store ID | Store idx | Name - private const int NumberOfFields = 5; - private const int Hash32FieldIndex = 0; - private const int Hash64FieldIndex = 1; - private const int StoreIdFieldIndex = 2; - private const int StoreIndexFieldIndex = 3; - private const int NameFieldIndex = 4; - - public uint Hash32 { get; } - public ulong Hash64 { get; } - public uint StoreId { get; } - public uint IndexInStore { get; } - public string Name { get; } - - public AssemblyStoreManifestEntry(string[] fields) - { - if (fields.Length != NumberOfFields) - { - throw new ArgumentOutOfRangeException(nameof(fields), "Invalid number of fields"); - } - - Hash32 = GetUInt32(fields[Hash32FieldIndex]); - Hash64 = GetUInt64(fields[Hash64FieldIndex]); - StoreId = GetUInt32(fields[StoreIdFieldIndex]); - IndexInStore = GetUInt32(fields[StoreIndexFieldIndex]); - Name = fields[NameFieldIndex].Trim(); - } - - private static uint GetUInt32(string value) - { - if (uint.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static ulong GetUInt64(string value) - { - if (ulong.TryParse(PrepHexValue(value), NumberStyles.HexNumber, null, out var hash)) - { - return hash; - } - - return 0; - } - - private static string PrepHexValue(string value) - { - if (value.StartsWith("0x", StringComparison.Ordinal)) - { - return value.Substring(2); - } - - return value; - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreHashEntry.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreHashEntry - { - public bool Is32Bit { get; } - public ulong Hash { get; } - public uint MappingIndex { get; } - public uint LocalStoreIndex { get; } - public uint StoreId { get; } - - internal AssemblyStoreHashEntry(BinaryReader reader, bool is32Bit) - { - Is32Bit = is32Bit; - Hash = reader.ReadUInt64(); - MappingIndex = reader.ReadUInt32(); - LocalStoreIndex = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreReader.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreReader - { - // These two constants must be identical to the native ones in src/monodroid/jni/xamarin-app.hh - private const uint ASSEMBLY_STORE_MAGIC = 0x41424158; // 'XABA', little-endian - private const uint ASSEMBLY_STORE_FORMAT_VERSION = 1; // The highest format version this reader understands - - private readonly MemoryStream _storeData; - - public uint Version { get; private set; } - public uint LocalEntryCount { get; private set; } - public uint GlobalEntryCount { get; private set; } - public uint StoreId { get; private set; } - public List Assemblies { get; } - public List GlobalIndex32 { get; } = new(); - public List GlobalIndex64 { get; } = new(); - public string Arch { get; } - - public bool HasGlobalIndex => StoreId == 0; - - public AssemblyStoreReader(MemoryStream store, string? arch = null) - { - Arch = arch ?? string.Empty; - _storeData = store; - using var reader = new BinaryReader(store, Encoding.UTF8, leaveOpen: true); - ReadHeader(reader); - - Assemblies = new List(); - ReadLocalEntries(reader, Assemblies); - if (HasGlobalIndex) - { - ReadGlobalIndex(reader, GlobalIndex32, GlobalIndex64); - } - } - - internal MemoryStream? GetAssemblyImageSlice(AssemblyStoreAssembly assembly) => - GetDataSlice(assembly.DataOffset, assembly.DataSize); - - internal MemoryStream? GetAssemblyDebugDataSlice(AssemblyStoreAssembly assembly) => - assembly.DebugDataOffset == 0 ? null : GetDataSlice(assembly.DebugDataOffset, assembly.DebugDataSize); - - internal MemoryStream? GetAssemblyConfigSlice(AssemblyStoreAssembly assembly) => - assembly.ConfigDataOffset == 0 ? null : GetDataSlice(assembly.ConfigDataOffset, assembly.ConfigDataSize); - - private MemoryStream? GetDataSlice(uint offset, uint size) => - size == 0 ? null : new ArchiveUtils.MemorySlice(_storeData, (int)offset, (int)size); - - public bool HasIdenticalContent(AssemblyStoreReader other) - { - return - other.Version == Version && - other.LocalEntryCount == LocalEntryCount && - other.GlobalEntryCount == GlobalEntryCount && - other.StoreId == StoreId && - other.Assemblies.Count == Assemblies.Count && - other.GlobalIndex32.Count == GlobalIndex32.Count && - other.GlobalIndex64.Count == GlobalIndex64.Count; - } - - private void ReadHeader(BinaryReader reader) - { - if (reader.ReadUInt32() != ASSEMBLY_STORE_MAGIC) - { - throw new InvalidOperationException("Invalid header magic number"); - } - - Version = reader.ReadUInt32(); - if (Version == 0) - { - throw new InvalidOperationException("Invalid version number: 0"); - } - - if (Version > ASSEMBLY_STORE_FORMAT_VERSION) - { - throw new InvalidOperationException( - $"Store format version {Version} is higher than the one understood by this reader, {ASSEMBLY_STORE_FORMAT_VERSION}"); - } - - LocalEntryCount = reader.ReadUInt32(); - GlobalEntryCount = reader.ReadUInt32(); - StoreId = reader.ReadUInt32(); - } - - private void ReadLocalEntries(BinaryReader reader, List assemblies) - { - for (uint i = 0; i < LocalEntryCount; i++) - { - assemblies.Add(new AssemblyStoreAssembly(reader, this)); - } - } - - private void ReadGlobalIndex(BinaryReader reader, List index32, - List index64) - { - ReadIndex(true, index32); - ReadIndex(false, index64); - - void ReadIndex(bool is32Bit, List index) - { - for (uint i = 0; i < GlobalEntryCount; i++) - { - index.Add(new AssemblyStoreHashEntry(reader, is32Bit)); - } - } - } - } - - // Adapted from https://github.com/xamarin/xamarin-android/blob/c92702619f5fabcff0ed88e09160baf9edd70f41/tools/assembly-store-reader/AssemblyStoreAssembly.cs - // With the original code licensed under MIT License (https://github.com/xamarin/xamarin-android/blob/2bd13c4a00ae78db34663a4b9c7a4c5bfb20c344/LICENSE). - private class AssemblyStoreAssembly - { - public uint DataOffset { get; } - public uint DataSize { get; } - public uint DebugDataOffset { get; } - public uint DebugDataSize { get; } - public uint ConfigDataOffset { get; } - public uint ConfigDataSize { get; } - - public uint Hash32 { get; set; } - public ulong Hash64 { get; set; } - public string Name { get; set; } = string.Empty; - public uint RuntimeIndex { get; set; } - - public AssemblyStoreReader Store { get; } - - internal AssemblyStoreAssembly(BinaryReader reader, AssemblyStoreReader store) - { - Store = store; - - DataOffset = reader.ReadUInt32(); - DataSize = reader.ReadUInt32(); - DebugDataOffset = reader.ReadUInt32(); - DebugDataSize = reader.ReadUInt32(); - ConfigDataOffset = reader.ReadUInt32(); - ConfigDataSize = reader.ReadUInt32(); - } - - public MemoryStream? GetImage() => Store.GetAssemblyImageSlice(this); - - public MemoryStream? GetDebugData() => Store.GetAssemblyDebugDataSlice(this); - - public MemoryStream? GetAssemblyConfig() => Store.GetAssemblyConfigSlice(this); - } -} diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs similarity index 98% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs index b250640d1f..ca2b96d886 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyDirectoryReader.cs @@ -1,13 +1,13 @@ namespace Sentry.Android.AssemblyReader.V2; // The "Old" app type - where each DLL is placed in the 'assemblies' directory as an individual file. -internal sealed class AndroidAssemblyDirectoryReaderV2 : IAndroidAssemblyReader +internal sealed class AndroidAssemblyDirectoryReader : IAndroidAssemblyReader { private DebugLogger? Logger { get; } private HashSet SupportedArchitectures { get; } = new(); private readonly ArchiveAssemblyHelper _archiveAssemblyHelper; - public AndroidAssemblyDirectoryReaderV2(string apkPath, IList supportedAbis, DebugLogger? logger) + public AndroidAssemblyDirectoryReader(string apkPath, IList supportedAbis, DebugLogger? logger) { Logger = logger; foreach (var abi in supportedAbis) diff --git a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs similarity index 94% rename from src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs index 2840c7ec67..0e9c6586f4 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AndroidAssemblyStoreReader.cs @@ -1,17 +1,17 @@ namespace Sentry.Android.AssemblyReader.V2; -internal class AndroidAssemblyStoreReaderV2 : IAndroidAssemblyReader +internal class AndroidAssemblyStoreReader : IAndroidAssemblyReader { private readonly IList _explorers; private readonly DebugLogger? _logger; - private AndroidAssemblyStoreReaderV2(IList explorers, DebugLogger? logger) + private AndroidAssemblyStoreReader(IList explorers, DebugLogger? logger) { _explorers = explorers; _logger = logger; } - public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReaderV2? reader) + public static bool TryReadStore(string inputFile, IList supportedAbis, DebugLogger? logger, [NotNullWhen(true)] out AndroidAssemblyStoreReader? reader) { List supportedExplorers = []; @@ -67,7 +67,7 @@ public static bool TryReadStore(string inputFile, IList supportedAbis, D return false; } - reader = new AndroidAssemblyStoreReaderV2(supportedExplorers, logger); + reader = new AndroidAssemblyStoreReader(supportedExplorers, logger); return true; } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs index 0ab9c04e2e..5854d3c070 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreExplorer.cs @@ -79,28 +79,19 @@ public static (IList? explorers, string? errorMessage) Op } private static (IList? explorers, string? errorMessage) OpenAab(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabPaths, StoreReader_V1.AabPaths], logger); + => OpenCommon(fi, StoreReader.AabPaths, logger); private static (IList? explorers, string? errorMessage) OpenAabBase(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.AabBasePaths, StoreReader_V1.AabBasePaths], logger); + => OpenCommon(fi, StoreReader.AabBasePaths, logger); private static (IList? explorers, string? errorMessage) OpenApk(FileInfo fi, DebugLogger? logger) - => OpenCommon(fi, [StoreReaderV2.ApkPaths, StoreReader_V1.ApkPaths], logger); + => OpenCommon(fi, StoreReader.ApkPaths, logger); - private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, List> pathLists, DebugLogger? logger) + private static (IList? explorers, string? errorMessage) OpenCommon(FileInfo fi, IList paths, DebugLogger? logger) { using var zip = ZipFile.OpenRead(fi.FullName); - - foreach (var paths in pathLists) - { - var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); - if (pathsFound) - { - return (explorers, errorMessage); - } - } - - return (null, "Unable to find any blob entries"); + var (explorers, errorMessage, pathsFound) = TryLoad(fi, zip, paths, logger); + return pathsFound ? (explorers, errorMessage) : (null, "Unable to find any blob entries"); } private static (IList? explorers, string? errorMessage, bool pathsFound) TryLoad(FileInfo fi, ZipArchive zip, IList paths, DebugLogger? logger) diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs index a132f51ead..2ecde5fd48 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreItem.cs @@ -1,5 +1,7 @@ /* * Adapted from https://github.com/dotnet/android/blob/86260ed36dfe1a90c8ed6a2bb1cd0607d637f403/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/AssemblyStoreItem.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ @@ -17,11 +19,13 @@ internal abstract class AssemblyStoreItem public uint ConfigOffset { get; protected set; } public uint ConfigSize { get; protected set; } public AndroidTargetArch TargetArch { get; protected set; } + public bool Ignore { get; } - protected AssemblyStoreItem(string name, bool is64Bit, List hashes) + protected AssemblyStoreItem(string name, bool is64Bit, List hashes, bool ignore) { Name = name; Hashes = hashes.AsReadOnly(); Is64Bit = is64Bit; + Ignore = ignore; } } diff --git a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs index 834cb2c36d..a3201754e1 100644 --- a/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs +++ b/src/Sentry.Android.AssemblyReader/V2/AssemblyStoreReader.cs @@ -32,23 +32,7 @@ protected AssemblyStoreReader(Stream store, string path, DebugLogger? logger) public static AssemblyStoreReader? Create(Stream store, string path, DebugLogger? logger) { - var reader = MakeReaderReady(new StoreReader_V1(store, path, logger)); - if (reader != null) - { - return reader; - } - - reader = MakeReaderReady(new StoreReaderV2(store, path, logger)); - if (reader != null) - { - return reader; - } - - return null; - } - - private static AssemblyStoreReader? MakeReaderReady(AssemblyStoreReader reader) - { + var reader = new StoreReader(store, path, logger); if (!reader.IsSupported()) { return null; diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs similarity index 80% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs index 85aa91ba89..5ed713b6be 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.Classes.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.Classes.cs @@ -1,11 +1,13 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.Classes.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 +internal partial class StoreReader { private sealed class Header { @@ -33,11 +35,13 @@ private sealed class IndexEntry { public readonly ulong name_hash; public readonly uint descriptor_index; + public readonly bool ignore; - public IndexEntry(ulong name_hash, uint descriptor_index) + public IndexEntry(ulong name_hash, uint descriptor_index, bool ignore) { this.name_hash = name_hash; this.descriptor_index = descriptor_index; + this.ignore = ignore; } } @@ -57,8 +61,9 @@ private sealed class EntryDescriptor private sealed class StoreItemV2 : AssemblyStoreItem { - public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, EntryDescriptor descriptor) - : base(name, is64Bit, IndexToHashes(indexEntries)) + public StoreItemV2(AndroidTargetArch targetArch, string name, bool is64Bit, List indexEntries, + EntryDescriptor descriptor, bool ignore) + : base(name, is64Bit, IndexToHashes(indexEntries), ignore) { DataOffset = descriptor.data_offset; DataSize = descriptor.data_size; @@ -86,11 +91,13 @@ private sealed class TemporaryItem public readonly string Name; public readonly List IndexEntries = new List(); public readonly EntryDescriptor Descriptor; + public readonly bool Ignored; - public TemporaryItem(string name, EntryDescriptor descriptor) + public TemporaryItem(string name, EntryDescriptor descriptor, bool ignored) { Name = name; Descriptor = descriptor; + Ignored = ignored; } } } diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs similarity index 90% rename from src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs rename to src/Sentry.Android.AssemblyReader/V2/StoreReader.cs index 97bac95694..5220bf7dc7 100644 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV2.cs +++ b/src/Sentry.Android.AssemblyReader/V2/StoreReader.cs @@ -1,15 +1,22 @@ /* * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * Updated from https://github.com/dotnet/android/blob/64018e13e53cec7246e54866b520d3284de344e0/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V2.cs + * - Adding support for AssemblyStore v3 format that shipped in .NET 10 (https://github.com/dotnet/android/pull/10249) * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) */ namespace Sentry.Android.AssemblyReader.V2; -internal partial class StoreReaderV2 : AssemblyStoreReader +internal partial class StoreReader : AssemblyStoreReader { // Bit 31 is set for 64-bit platforms, cleared for the 32-bit ones +#if NET9_0 private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000002; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000002; +#else + private const uint ASSEMBLY_STORE_FORMAT_VERSION_64BIT = 0x80000003; // Must match the ASSEMBLY_STORE_FORMAT_VERSION native constant + private const uint ASSEMBLY_STORE_FORMAT_VERSION_32BIT = 0x00000003; +#endif private const uint ASSEMBLY_STORE_FORMAT_VERSION_MASK = 0xF0000000; private const uint ASSEMBLY_STORE_ABI_AARCH64 = 0x00010000; private const uint ASSEMBLY_STORE_ABI_ARM = 0x00020000; @@ -28,7 +35,7 @@ internal partial class StoreReaderV2 : AssemblyStoreReader private Header? header; private ulong elfOffset = 0; - static StoreReaderV2() + static StoreReader() { var paths = new List { GetArchPath (AndroidTargetArch.Arm64), @@ -69,7 +76,7 @@ string GetArchPath(AndroidTargetArch arch, string? root = null) } } - public StoreReaderV2(Stream store, string path, DebugLogger? logger) + public StoreReader(Stream store, string path, DebugLogger? logger) : base(store, path, logger) { supportedVersions = new HashSet { @@ -178,7 +185,12 @@ protected override void Prepare() } uint descriptor_index = reader.ReadUInt32(); - index.Add(new IndexEntry(name_hash, descriptor_index)); +#if NET10_0_OR_GREATER + bool ignore = reader.ReadByte () != 0; +#else + bool ignore = false; +#endif + index.Add(new IndexEntry(name_hash, descriptor_index, ignore)); } var descriptors = new List(); @@ -218,7 +230,7 @@ protected override void Prepare() { if (!tempItems.TryGetValue(ie.descriptor_index, out TemporaryItem? item)) { - item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index]); + item = new TemporaryItem(names[(int)ie.descriptor_index], descriptors[(int)ie.descriptor_index], ie.ignore); tempItems.Add(ie.descriptor_index, item); } item.IndexEntries.Add(ie); @@ -233,7 +245,7 @@ protected override void Prepare() foreach (var kvp in tempItems) { TemporaryItem ti = kvp.Value; - var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor); + var item = new StoreItemV2(TargetArch, ti.Name, Is64Bit, ti.IndexEntries, ti.Descriptor, ti.Ignored); storeItems.Add(item); } Assemblies = storeItems.AsReadOnly(); diff --git a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs b/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs deleted file mode 100644 index c1a3fe3c2d..0000000000 --- a/src/Sentry.Android.AssemblyReader/V2/StoreReaderV1.cs +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Adapted from https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/tools/assembly-store-reader-mk2/AssemblyStore/StoreReader_V1.cs - * Original code licensed under the MIT License (https://github.com/dotnet/android/blob/5ebcb1dd1503648391e3c0548200495f634d90c6/LICENSE.TXT) - */ - -namespace Sentry.Android.AssemblyReader.V2; - -internal class StoreReader_V1 : AssemblyStoreReader -{ - public override string Description => "Assembly store v1"; - public override bool NeedsExtensionInName => false; - - public static IList ApkPaths { get; } - public static IList AabPaths { get; } - public static IList AabBasePaths { get; } - - static StoreReader_V1() - { - ApkPaths = new List().AsReadOnly(); - AabPaths = new List().AsReadOnly(); - AabBasePaths = new List().AsReadOnly(); - } - - public StoreReader_V1(Stream store, string path, DebugLogger? logger) - : base(store, path, logger) - { } - - protected override bool IsSupported() - { - return false; - } - - protected override void Prepare() - { - } - - protected override ulong GetStoreStartDataOffset() => 0; -} diff --git a/test/AndroidTestApp/AndroidTestApp.csproj b/test/AndroidTestApp/AndroidTestApp.csproj index 261fc05b24..cefce5290f 100644 --- a/test/AndroidTestApp/AndroidTestApp.csproj +++ b/test/AndroidTestApp/AndroidTestApp.csproj @@ -2,6 +2,7 @@ net10.0-android;net9.0-android false + false 21 Exe enable @@ -9,5 +10,6 @@ com.companyname.AndroidTestApp 1 1.0 + android-x64 diff --git a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs index f0d1a50c64..60b56ffc54 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs +++ b/test/Sentry.Android.AssemblyReader.Tests/AndroidAssemblyReaderTests.cs @@ -1,4 +1,3 @@ -using Sentry.Android.AssemblyReader.V1; using Sentry.Android.AssemblyReader.V2; namespace Sentry.Android.AssemblyReader.Tests; @@ -6,13 +5,10 @@ namespace Sentry.Android.AssemblyReader.Tests; public class AndroidAssemblyReaderTests { private readonly ITestOutputHelper _output; - #if NET10_0 private static string TargetFramework => "net10.0"; #elif NET9_0 private static string TargetFramework => "net9.0"; -#elif NET8_0 - private static string TargetFramework => "net8.0"; #else // Adding a new TFM to the project? Include it above #error "Target Framework not yet supported for AndroidAssemblyReader" @@ -54,11 +50,11 @@ public void CreatesCorrectStoreReader() using var sut = GetSut(isAot: false, isAssemblyStore: true, isCompressed: true); switch (TargetFramework) { - case "net9.0": - Assert.IsType(sut); + case "net10.0": + Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -74,11 +70,11 @@ public void CreatesCorrectArchiveReader() using var sut = GetSut(isAot: false, isAssemblyStore: false, isCompressed: true); switch (TargetFramework) { - case "net9.0": - Assert.IsType(sut); + case "net10.0": + Assert.IsType(sut); break; - case "net8.0": - Assert.IsType(sut); + case "net9.0": + Assert.IsType(sut); break; default: throw new NotSupportedException($"Unsupported target framework: {TargetFramework}"); @@ -95,11 +91,7 @@ public void ReturnsNullIfAssemblyDoesntExist(bool isAssemblyStore) } public static IEnumerable ReadsAssemblyPermutations => -#if NET8_0 - from isAot in new[] { false } -#else from isAot in new[] { true, false } -#endif from isStore in new[] { true, false } from isCompressed in new[] { true, false } from assemblyName in new[] { "Mono.Android.dll", "System.Private.CoreLib.dll" } diff --git a/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt new file mode 100644 index 0000000000..67bbca78b3 --- /dev/null +++ b/test/Sentry.Android.AssemblyReader.Tests/ApiApprovalTests.Run.DotNet10_0.verified.txt @@ -0,0 +1,20 @@ +namespace Sentry.Android.AssemblyReader +{ + public static class AndroidAssemblyReaderFactory + { + public static Sentry.Android.AssemblyReader.IAndroidAssemblyReader Open(string apkPath, System.Collections.Generic.IList supportedAbis, Sentry.Android.AssemblyReader.DebugLogger? logger = null) { } + } + public delegate void DebugLogger(Sentry.Android.AssemblyReader.DebugLoggerLevel level, string message, params object?[] args); + public enum DebugLoggerLevel : short + { + Debug = 0, + Info = 1, + Warning = 2, + Error = 3, + Fatal = 4, + } + public interface IAndroidAssemblyReader : System.IDisposable + { + System.Reflection.PortableExecutable.PEReader? TryReadAssembly(string name); + } +} \ No newline at end of file diff --git a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj index 8d50df6bec..a892724b49 100644 --- a/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj +++ b/test/Sentry.Android.AssemblyReader.Tests/Sentry.Android.AssemblyReader.Tests.csproj @@ -1,7 +1,7 @@ - net9.0 + $(LatestTfm);$(PreviousTfm) $(TargetFrameworks);$(LatestAndroidTfm);$(PreviousAndroidTfm) enable @@ -20,7 +20,7 @@ - + @@ -42,11 +42,10 @@ <_TestAPK Include="2" Properties="_Aot=False;_Store=False;_Compressed=True" /> <_TestAPK Include="3" Properties="_Aot=False;_Store=True;_Compressed=False" /> <_TestAPK Include="4" Properties="_Aot=False;_Store=True;_Compressed=True" /> - - <_TestAPK Include="5" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=False;_Compressed=False" /> - <_TestAPK Include="6" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=False;_Compressed=True" /> - <_TestAPK Include="7" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=True;_Compressed=False" /> - <_TestAPK Include="8" Condition="!$(TargetFramework.StartsWith('net8'))" Properties="_Aot=True;_Store=True;_Compressed=True" /> + <_TestAPK Include="5" Properties="_Aot=True;_Store=False;_Compressed=False" /> + <_TestAPK Include="6" Properties="_Aot=True;_Store=False;_Compressed=True" /> + <_TestAPK Include="7" Properties="_Aot=True;_Store=True;_Compressed=False" /> + <_TestAPK Include="8" Properties="_Aot=True;_Store=True;_Compressed=True" /> @@ -57,7 +56,8 @@ ..\AndroidTestApp\bin\$(TargetFramework)\$(_ConfigString)\com.companyname.AndroidTestApp-Signed.apk TestAPKs\$(TargetFramework)-$(_ConfigString).apk - + +