diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
index e83e7120ff6..553ba08972f 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ManifestGenerator.cs
@@ -148,23 +148,23 @@ XDocument CreateDefaultManifest ()
///
/// Manifest templates may use compat JNI names (e.g., "android.apptests.App")
- /// but the trimmable path generates JCWs with CRC-based names (e.g., "crc64.../App").
+ /// but the trimmable path generates JCWs with hashed package names (e.g., "crc64.../App").
/// This method rewrites any compat name references to the actual JCW name so the
/// Android runtime can find the class.
///
void RewriteCompatNames (XElement manifest, IReadOnlyList allPeers)
{
- // Build mapping: fully-qualified compat Java name → CRC Java name
- var compatToCrc = new Dictionary (allPeers.Count, StringComparer.Ordinal);
+ // Build mapping: fully-qualified compat Java name → hashed Java name
+ var compatToHashed = new Dictionary (allPeers.Count, StringComparer.Ordinal);
foreach (var peer in allPeers) {
string javaName = JniSignatureHelper.JniNameToJavaName (peer.JavaName);
string compatName = JniSignatureHelper.JniNameToJavaName (peer.CompatJniName);
if (javaName != compatName) {
- compatToCrc [compatName] = javaName;
+ compatToHashed [compatName] = javaName;
}
}
- if (compatToCrc.Count == 0) {
+ if (compatToHashed.Count == 0) {
return;
}
@@ -173,7 +173,7 @@ void RewriteCompatNames (XElement manifest, IReadOnlyList allPeers
// - fully qualified ("com.example.app.MainActivity")
// - relative to the manifest package, starting with '.' (".MainActivity")
// - bare, with no '.' at all ("MainActivity"), also relative to the package
- // Resolve to the fully-qualified form before the lookup, then write the CRC
+ // Resolve to the fully-qualified form before the lookup, then write the hashed
// name back so duplicate detection later in the pipeline works correctly.
var packageName = (string?) manifest.Attribute ("package") ?? "";
@@ -187,8 +187,8 @@ void RewriteCompatNames (XElement manifest, IReadOnlyList allPeers
continue;
}
var resolved = ManifestNameResolver.Resolve (nameAttr.Value, packageName);
- if (compatToCrc.TryGetValue (resolved, out var crcName)) {
- nameAttr.Value = crcName;
+ if (compatToHashed.TryGetValue (resolved, out var hashedName)) {
+ nameAttr.Value = hashedName;
}
}
}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
index 249bdc8def1..99399656317 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Microsoft.Android.Sdk.TrimmableTypeMap.csproj
@@ -5,6 +5,7 @@
$(TargetFrameworkNETStandard)
enable
Nullable
+ true
Microsoft.Android.Sdk.TrimmableTypeMap
true
..\..\product.snk
@@ -18,6 +19,8 @@
+
+
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
index 08ee7cd337d..0f2e782a200 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs
@@ -6,6 +6,7 @@
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Reflection.PortableExecutable;
+using Java.Interop.Tools.JavaCallableWrappers;
namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
@@ -16,8 +17,19 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
///
public sealed class JavaPeerScanner : IDisposable
{
+ enum HashedPackageNamingPolicy {
+ Crc64,
+ LowercaseCrc64,
+ }
+
readonly Dictionary assemblyCache = new (StringComparer.Ordinal);
readonly Dictionary<(string typeName, string assemblyName), ActivationCtorInfo> activationCtorCache = new ();
+ readonly HashedPackageNamingPolicy packageNamingPolicy;
+
+ public JavaPeerScanner (string? packageNamingPolicy = null)
+ {
+ this.packageNamingPolicy = ParsePackageNamingPolicy (packageNamingPolicy);
+ }
///
/// Resolves a type name + assembly name to a TypeDefinitionHandle + AssemblyIndex.
@@ -913,7 +925,7 @@ static string GetJavaAccess (MethodAttributes access)
return registerJniName;
}
- // Fall back to already-scanned results (component-attributed or CRC64-computed peers)
+ // Fall back to already-scanned results (component-attributed or hashed-package peers)
if (results.TryGetValue (baseTypeName, out var basePeer)) {
return basePeer.JavaName;
}
@@ -1331,12 +1343,12 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
///
/// Compute both JNI name and compat JNI name for a type without [Register] or component Name.
- /// JNI name uses CRC64 hash of "namespace:assemblyName" for the package.
+ /// JNI name uses the selected package naming policy hash for "namespace:assemblyName".
/// Compat JNI name uses the raw managed namespace (lowercased).
/// If a declaring type has [Register], its JNI name is used as prefix for both.
/// Generic backticks are replaced with _.
///
- static (string jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index)
+ (string jniName, string compatJniName) ComputeAutoJniNames (TypeDefinition typeDef, AssemblyIndex index)
{
var (typeName, parentJniName, ns) = ComputeTypeNameParts (typeDef, index);
@@ -1345,7 +1357,7 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
return (name, name);
}
- var packageName = GetCrc64PackageName (ns, index.AssemblyName);
+ var packageName = GetHashedPackageName (ns, index.AssemblyName);
var jniName = $"{packageName}/{typeName}";
string compatName = ns.Length == 0
@@ -1360,7 +1372,7 @@ bool ExtendsJavaPeer (TypeDefinition typeDef, AssemblyIndex index)
/// registered JNI name or the outermost namespace.
/// Matches JavaNativeTypeManager.ToJniName behavior: walks up declaring types
/// and if a parent has [Register] or a component attribute JNI name, uses that
- /// as prefix instead of computing CRC64 from the namespace.
+ /// as prefix instead of computing hashed package names from the namespace.
///
static (string typeName, string? parentJniName, string ns) ComputeTypeNameParts (TypeDefinition typeDef, AssemblyIndex index)
{
@@ -1465,16 +1477,26 @@ static void ParseConnectorDeclaringType (string? connector, out string declaring
declaringAssemblyName = nextComma >= 0 ? rest.Substring (0, nextComma).Trim () : rest.Trim ();
}
- static string GetCrc64PackageName (string ns, string assemblyName)
+ string GetHashedPackageName (string ns, string assemblyName)
{
// Only Mono.Android preserves the namespace directly
if (assemblyName == "Mono.Android") {
return ns.ToLowerInvariant ().Replace ('.', '/');
}
- var data = System.Text.Encoding.UTF8.GetBytes ($"{ns}:{assemblyName}");
- var hash = System.IO.Hashing.Crc64.Hash (data);
- return $"crc64{BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant ()}";
+ return packageNamingPolicy switch {
+ HashedPackageNamingPolicy.LowercaseCrc64 => "crc64" + ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName),
+ HashedPackageNamingPolicy.Crc64 => "crc64" + ScannerHashingHelper.ToCrc64 (ns, assemblyName),
+ _ => throw new InvalidOperationException ($"Unsupported package naming policy: {packageNamingPolicy}"),
+ };
+ }
+
+ static HashedPackageNamingPolicy ParsePackageNamingPolicy (string? packageNamingPolicy)
+ {
+ if (string.Equals (packageNamingPolicy, "LowercaseCrc64", StringComparison.OrdinalIgnoreCase)) {
+ return HashedPackageNamingPolicy.LowercaseCrc64;
+ }
+ return HashedPackageNamingPolicy.Crc64;
}
static string ExtractNamespace (string fullName)
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs
new file mode 100644
index 00000000000..b57c78bbb82
--- /dev/null
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/ScannerHashingHelper.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Buffers;
+using Java.Interop.Tools.JavaCallableWrappers;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap;
+
+internal static class ScannerHashingHelper
+{
+ internal static string ToLegacyCrc64 (string ns, string assemblyName)
+ {
+ int byteCount = GetNamespaceAssemblyUtf8ByteCount (ns, assemblyName);
+ byte[] rented = ArrayPool.Shared.Rent (byteCount);
+ try {
+ int bytesWritten = GetNamespaceAssemblyUtf8Bytes (ns, assemblyName, rented.AsSpan (0, byteCount));
+ ulong crc = ulong.MaxValue;
+ ulong length = 0;
+ Crc64Helper.HashCore (rented, 0, bytesWritten, ref crc, ref length);
+ Span hash = stackalloc byte [8];
+ WriteUInt64LittleEndian (hash, crc ^ length);
+ return ToHexString (hash, lowercase: true);
+ } finally {
+ ArrayPool.Shared.Return (rented);
+ }
+ }
+
+ internal static string ToCrc64 (string ns, string assemblyName)
+ {
+ const int stackallocThresholdBytes = 256;
+ int byteCount = GetNamespaceAssemblyUtf8ByteCount (ns, assemblyName);
+ Span utf8Buffer = byteCount <= stackallocThresholdBytes
+ ? stackalloc byte [stackallocThresholdBytes]
+ : new byte [byteCount];
+
+ int bytesWritten = GetNamespaceAssemblyUtf8Bytes (ns, assemblyName, utf8Buffer.Slice (0, byteCount));
+ Span hash = stackalloc byte [8];
+ System.IO.Hashing.Crc64.Hash (utf8Buffer.Slice (0, bytesWritten), hash);
+ return ToHexString (hash, lowercase: true);
+ }
+
+ static int GetNamespaceAssemblyUtf8ByteCount (string ns, string assemblyName)
+ {
+ return System.Text.Encoding.UTF8.GetByteCount (ns) + 1 + System.Text.Encoding.UTF8.GetByteCount (assemblyName);
+ }
+
+ static unsafe int GetNamespaceAssemblyUtf8Bytes (string ns, string assemblyName, Span destination)
+ {
+ int bytesWritten = 0;
+ fixed (char* nsPtr = ns)
+ fixed (byte* destinationPtr = destination) {
+ bytesWritten += System.Text.Encoding.UTF8.GetBytes (nsPtr, ns.Length, destinationPtr, destination.Length);
+ }
+
+ destination [bytesWritten++] = (byte) ':';
+
+ fixed (char* assemblyNamePtr = assemblyName)
+ fixed (byte* destinationPtr = destination) {
+ bytesWritten += System.Text.Encoding.UTF8.GetBytes (assemblyNamePtr, assemblyName.Length, destinationPtr + bytesWritten, destination.Length - bytesWritten);
+ }
+
+ return bytesWritten;
+ }
+
+ static string ToHexString (ReadOnlySpan hash, bool lowercase)
+ {
+ const int maxStackCharLength = 128;
+ int charLength = hash.Length * 2;
+ Span chars = charLength <= maxStackCharLength
+ ? stackalloc char [charLength]
+ : new char [charLength];
+
+ for (int i = 0, j = 0; i < hash.Length; i += 1, j += 2) {
+ byte b = hash [i];
+ chars [j] = GetHexValue (b / 16, lowercase);
+ chars [j + 1] = GetHexValue (b % 16, lowercase);
+ }
+
+ return ((ReadOnlySpan) chars).ToString ();
+ }
+
+ static void WriteUInt64LittleEndian (Span destination, ulong value)
+ {
+ destination [0] = (byte) value;
+ destination [1] = (byte) (value >> 8);
+ destination [2] = (byte) (value >> 16);
+ destination [3] = (byte) (value >> 24);
+ destination [4] = (byte) (value >> 32);
+ destination [5] = (byte) (value >> 40);
+ destination [6] = (byte) (value >> 48);
+ destination [7] = (byte) (value >> 56);
+ }
+
+ static char GetHexValue (int value, bool lowercase)
+ {
+ return (char) (value < 10
+ ? value + '0'
+ : value - 10 + (lowercase ? 'a' : 'A'));
+ }
+}
diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
index 5d14490503a..d877a70b028 100644
--- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
+++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
@@ -27,13 +27,14 @@ public TrimmableTypeMapResult Execute (
HashSet frameworkAssemblyNames,
bool useSharedTypemapUniverse = false,
ManifestConfig? manifestConfig = null,
- XDocument? manifestTemplate = null)
+ XDocument? manifestTemplate = null,
+ string? packageNamingPolicy = null)
{
_ = assemblies ?? throw new ArgumentNullException (nameof (assemblies));
_ = systemRuntimeVersion ?? throw new ArgumentNullException (nameof (systemRuntimeVersion));
_ = frameworkAssemblyNames ?? throw new ArgumentNullException (nameof (frameworkAssemblyNames));
- var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies);
+ var (allPeers, assemblyManifestInfo) = ScanAssemblies (assemblies, packageNamingPolicy);
if (allPeers.Count == 0) {
logger.LogNoJavaPeerTypesFound ();
return new TrimmableTypeMapResult ([], [], allPeers);
@@ -104,9 +105,9 @@ GeneratedManifest GenerateManifest (List allPeers, AssemblyManifes
return new GeneratedManifest (doc, providerNames.Count > 0 ? providerNames.ToArray () : []);
}
- (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies)
+ (List peers, AssemblyManifestInfo manifestInfo) ScanAssemblies (IReadOnlyList<(string Name, PEReader Reader)> assemblies, string? packageNamingPolicy)
{
- using var scanner = new JavaPeerScanner ();
+ using var scanner = new JavaPeerScanner (packageNamingPolicy);
var peers = scanner.Scan (assemblies);
var manifestInfo = scanner.ScanAssemblyManifestInfo ();
logger.LogJavaPeerScanInfo (assemblies.Count, peers.Count);
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
index 52ce49be5cf..04135852229 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.Trimmable.targets
@@ -11,6 +11,8 @@
<_TypeMapAssemblyName>_Microsoft.Android.TypeMaps
+ <_TrimmableTypeMapPackageNamingPolicy Condition=" '$(_AndroidPackageNamingPolicySetByUser)' == 'true' ">$(AndroidPackageNamingPolicy)
+ <_TrimmableTypeMapPackageNamingPolicy Condition=" '$(_TrimmableTypeMapPackageNamingPolicy)' == '' ">Crc64
@@ -73,6 +75,7 @@
Debug="$(AndroidIncludeDebugSymbols)"
NeedsInternet="$(AndroidNeedsInternetPermission)"
EmbedAssemblies="$(EmbedAssembliesIntoApk)"
+ PackageNamingPolicy="$(_TrimmableTypeMapPackageNamingPolicy)"
ManifestPlaceholders="$(AndroidManifestPlaceholders)"
CheckedBuild="$(_AndroidCheckedBuild)"
ApplicationJavaClass="$(AndroidApplicationJavaClass)"
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
index f80a14157f1..cb4d51021d3 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateTrimmableTypeMap.cs
@@ -68,6 +68,7 @@ public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) =>
public bool Debug { get; set; }
public bool NeedsInternet { get; set; }
public bool EmbedAssemblies { get; set; }
+ public string? PackageNamingPolicy { get; set; }
public string? ManifestPlaceholders { get; set; }
public string? CheckedBuild { get; set; }
public string? ApplicationJavaClass { get; set; }
@@ -131,7 +132,8 @@ public override bool RunTask ()
frameworkAssemblyNames,
useSharedTypemapUniverse: !Debug,
manifestConfig,
- manifestTemplate);
+ manifestTemplate,
+ PackageNamingPolicy);
GeneratedAssemblies = WriteAssembliesToDisk (result.GeneratedAssemblies, assemblyPaths);
GeneratedJavaFiles = WriteJavaSourcesToDisk (result.GeneratedJavaSources);
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in
index 02589ee9384..b13f3abe411 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in
@@ -21,6 +21,7 @@
{abi}{versionCode:D5}
UpdateGeneratedFiles
True
+ <_AndroidPackageNamingPolicySetByUser Condition=" '$(AndroidPackageNamingPolicy)' != '' ">true
LowercaseCrc64
False
@BUNDLETOOL_VERSION@
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.Helpers.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.Helpers.cs
index 5014fe28a4c..c08c1b95f7e 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.Helpers.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.Helpers.cs
@@ -72,12 +72,12 @@ static string[]? AllUserTypesAssemblyPaths {
}
}
- static string NormalizeCrc64 (string javaName)
+ static string NormalizeHashedPackageName (string javaName)
{
- if (javaName.StartsWith ("crc64", StringComparison.Ordinal)) {
+ if (javaName.StartsWith ("crc64", StringComparison.Ordinal) || javaName.StartsWith ("xx64", StringComparison.Ordinal)) {
int slash = javaName.IndexOf ('/');
if (slash > 0) {
- return "crc64.../" + javaName.Substring (slash + 1);
+ return "hash.../" + javaName.Substring (slash + 1);
}
}
return javaName;
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs
index fcfe51cb1df..1628af0140c 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/ScannerComparisonTests.cs
@@ -115,8 +115,8 @@ public void ExactTypeMap_UserTypesFixture ()
var fixturePath = paths! [0];
var (legacy, _) = ScannerRunner.RunLegacy (fixturePath);
var (newEntries, _) = ScannerRunner.RunNew (paths);
-var legacyNormalized = legacy.Select (e => e with { JavaName = NormalizeCrc64 (e.JavaName) }).ToList ();
-var newNormalized = newEntries.Select (e => e with { JavaName = NormalizeCrc64 (e.JavaName) }).ToList ();
+var legacyNormalized = legacy.Select (e => e with { JavaName = NormalizeHashedPackageName (e.JavaName) }).ToList ();
+var newNormalized = newEntries.Select (e => e with { JavaName = NormalizeHashedPackageName (e.JavaName) }).ToList ();
AssertTypeMapMatch (legacyNormalized, newNormalized);
}
@@ -132,9 +132,9 @@ public void ExactMarshalMethods_UserTypesFixture ()
var (_, newMethods) = ScannerRunner.RunNew (paths);
var legacyNormalized = legacyMethods
-.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => kvp.Value);
+.ToDictionary (kvp => NormalizeHashedPackageName (kvp.Key), kvp => kvp.Value);
var newNormalized = newMethods
-.ToDictionary (kvp => NormalizeCrc64 (kvp.Key), kvp => kvp.Value);
+.ToDictionary (kvp => NormalizeHashedPackageName (kvp.Key), kvp => kvp.Value);
var result = MarshalMethodDiffHelper.CompareUserTypeMarshalMethods (legacyNormalized, newNormalized);
AssertNoDiffs ("MISSING from new scanner", result.Missing);
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
index 98b28be0591..bf997025616 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs
@@ -36,6 +36,18 @@ private protected static string TestFixtureAssemblyPath {
private protected static List ScanFixtures () => _cachedScanResult.Value.peers;
+ private protected static List ScanFixtures (string packageNamingPolicy)
+ {
+ using var scanner = new JavaPeerScanner (packageNamingPolicy);
+ var peReader = new PEReader (File.OpenRead (TestFixtureAssemblyPath));
+ var mdReader = peReader.GetMetadataReader ();
+ var assemblyName = mdReader.GetString (mdReader.GetAssemblyDefinition ().Name);
+ var assemblies = new [] { (assemblyName, peReader) };
+ var peers = scanner.Scan (assemblies);
+ peReader.Dispose ();
+ return peers;
+ }
+
private protected static AssemblyManifestInfo ScanAssemblyManifestInfo () => _cachedScanResult.Value.manifestInfo;
private protected static JavaPeerInfo FindFixtureByJavaName (string javaName)
@@ -54,6 +66,14 @@ private protected static JavaPeerInfo FindFixtureByManagedName (string managedNa
return peer;
}
+ private protected static JavaPeerInfo FindFixtureByManagedName (string managedName, string packageNamingPolicy)
+ {
+ var peers = ScanFixtures (packageNamingPolicy);
+ var peer = peers.FirstOrDefault (p => p.ManagedTypeName == managedName);
+ Assert.NotNull (peer);
+ return peer;
+ }
+
static (string ns, string shortName) ParseManagedTypeName (string managedName)
{
var ns = managedName.Contains ('.') ? managedName.Substring (0, managedName.LastIndexOf ('.')) : "";
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
index bd9d1ab6b0d..b8fc18064d0 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapModelBuilderTests.cs
@@ -267,7 +267,7 @@ public void Build_PeerWithInvoker_CreatesProxy ()
[InlineData ("MyApp.UnregisteredExporter")]
[InlineData ("MyApp.UnregisteredHelper")]
[InlineData ("MyApp.DerivedFromComponentBase")]
- public void Build_Crc64RenamedPeer_StoresFinalJavaNameOnProxy (string managedName)
+ public void Build_HashedRenamedPeer_StoresFinalJavaNameOnProxy (string managedName)
{
var peer = FindFixtureByManagedName (managedName);
Assert.StartsWith ("crc64", peer.JavaName);
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
index b1f96d6d320..6db3290c015 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.EdgeCases.cs
@@ -54,7 +54,7 @@ public void Scan_EmptyNamespace_Handled ()
[InlineData ("MyApp.UnnamedActivity")]
[InlineData ("MyApp.UnregisteredClickListener")]
[InlineData ("MyApp.UnregisteredExporter")]
- public void Scan_UnregisteredType_DiscoveredWithCrc64Name (string managedName)
+ public void Scan_UnregisteredType_DiscoveredWithHashedName (string managedName)
{
Assert.StartsWith ("crc64", FindFixtureByManagedName (managedName).JavaName);
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs
index a3b725caecb..8be0d078cf4 100644
--- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/JavaPeerScannerTests.cs
@@ -88,11 +88,25 @@ public void Scan_RegisterAttribute_DotFormat_NormalizedToSlashes ()
}
[Theory]
- [InlineData ("MyApp.PlainActivitySubclass", "crc64eb3df85c64aa1af6/PlainActivitySubclass")]
- [InlineData ("MyApp.UnregisteredClickListener", "crc64eb3df85c64aa1af6/UnregisteredClickListener")]
- [InlineData ("MyApp.UnregisteredExporter", "crc64eb3df85c64aa1af6/UnregisteredExporter")]
- public void Scan_UnregisteredType_UsesCrc64PackageName (string managedName, string expectedJavaName)
+ [InlineData ("MyApp.PlainActivitySubclass", "PlainActivitySubclass")]
+ [InlineData ("MyApp.UnregisteredClickListener", "UnregisteredClickListener")]
+ [InlineData ("MyApp.UnregisteredExporter", "UnregisteredExporter")]
+ public void Scan_UnregisteredType_UsesHashedPackageName (string managedName, string expectedJavaName)
{
- Assert.Equal (expectedJavaName, FindFixtureByManagedName (managedName).JavaName);
+ Assert.Equal ($"crc64{ScannerHashingHelper.ToCrc64 ("MyApp", "TestFixtures")}/{expectedJavaName}",
+ FindFixtureByManagedName (managedName).JavaName);
}
+
+ [Fact]
+ public void Scan_UnregisteredType_Crc64Default_DiffersFromLegacyLowercaseCrc64Policy ()
+ {
+ const string managedName = "MyApp.PlainActivitySubclass";
+ var withCrc64 = FindFixtureByManagedName (managedName).JavaName;
+ var withLowercaseCrc64 = FindFixtureByManagedName (managedName, "LowercaseCrc64").JavaName;
+
+ Assert.Equal ($"crc64{ScannerHashingHelper.ToCrc64 ("MyApp", "TestFixtures")}/PlainActivitySubclass", withCrc64);
+ Assert.Equal ("crc64ec59e927bc71f4d8/PlainActivitySubclass", withLowercaseCrc64);
+ Assert.NotEqual (withCrc64, withLowercaseCrc64);
+ }
+
}
diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs
new file mode 100644
index 00000000000..d6fdb902b7a
--- /dev/null
+++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Scanner/ScannerHashingHelperTests.cs
@@ -0,0 +1,24 @@
+using Xunit;
+
+namespace Microsoft.Android.Sdk.TrimmableTypeMap.Tests;
+
+public class ScannerHashingHelperTests
+{
+ [Theory]
+ [InlineData ("MyApp", "TestFixtures", "ec59e927bc71f4d8")]
+ [InlineData ("System.Collections.Generic", "My.Assembly", "9ff866e93b19f500")]
+ [InlineData ("Hello", "World", "f6bdbfa73a558c54")]
+ public void ToLegacyCrc64_KnownInputs_HaveStableOutput (string ns, string assemblyName, string expected)
+ {
+ Assert.Equal (expected, ScannerHashingHelper.ToLegacyCrc64 (ns, assemblyName));
+ }
+
+ [Theory]
+ [InlineData ("MyApp", "TestFixtures", "eb3df85c64aa1af6")]
+ [InlineData ("System.Collections.Generic", "My.Assembly", "403b37c9b3a5014d")]
+ [InlineData ("Hello", "World", "4f2a517f331e7a2c")]
+ public void ToCrc64_KnownInputs_HaveStableOutput (string ns, string assemblyName, string expected)
+ {
+ Assert.Equal (expected, ScannerHashingHelper.ToCrc64 (ns, assemblyName));
+ }
+}