diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs index f9616ef190d..83594593445 100644 --- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs +++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs @@ -265,7 +265,9 @@ void AddEnvironment () HashSet archAssemblyNames = null; HashSet uniqueAssemblyNames = new HashSet (StringComparer.OrdinalIgnoreCase); Action updateAssemblyCount = (ITaskItem assembly) => { - string assemblyName = Path.GetFileName (assembly.ItemSpec); + // We need to use the 'RelativePath' metadata, if found, because it will give us the correct path for satellite assemblies - with the culture in the path. + string? relativePath = assembly.GetMetadata ("RelativePath"); + string assemblyName = String.IsNullOrEmpty (relativePath) ? Path.GetFileName (assembly.ItemSpec) : relativePath; if (!uniqueAssemblyNames.Contains (assemblyName)) { uniqueAssemblyNames.Add (assemblyName); } diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs index f69eeb8b5c1..ef3db27cad6 100644 --- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs +++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs @@ -807,12 +807,18 @@ public void MissingSatelliteAssemblyInLibrary () new BuildItem ("EmbeddedResource", "Foo.resx") { TextContent = () => InlineData.ResxWithContents ("Cancel") }, - new BuildItem ("EmbeddedResource", "Foo.es.resx") { - TextContent = () => InlineData.ResxWithContents ("Cancelar") - } } }; + var languages = new string[] {"es", "de", "fr", "he", "it", "pl", "pt", "ru", "sl" }; + foreach (string lang in languages) { + lib.OtherBuildItems.Add ( + new BuildItem ("EmbeddedResource", $"Foo.{lang}.resx") { + TextContent = () => InlineData.ResxWithContents ($"{lang}") + } + ); + } + var app = new XamarinAndroidApplicationProject { IsRelease = true, }; @@ -829,7 +835,10 @@ public void MissingSatelliteAssemblyInLibrary () var apk = Path.Combine (Root, appBuilder.ProjectDirectory, app.OutputPath, $"{app.PackageName}-Signed.apk"); var helper = new ArchiveAssemblyHelper (apk); - Assert.IsTrue (helper.Exists ($"assemblies/es/{lib.ProjectName}.resources.dll"), "Apk should contain satellite assemblies!"); + + foreach (string lang in languages) { + Assert.IsTrue (helper.Exists ($"assemblies/{lang}/{lib.ProjectName}.resources.dll"), $"Apk should contain satellite assembly for language '{lang}'!"); + } } } diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs index 5c94e9b6e27..50aec79c9ed 100644 --- a/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs +++ b/src/Xamarin.Android.Build.Tasks/Utilities/MarshalMethodsNativeAssemblyGenerator.cs @@ -816,7 +816,8 @@ void WriteHashes () where T: struct uint index = 0; foreach (string name in uniqueAssemblyNames) { - string clippedName = Path.GetFileNameWithoutExtension (name); + // We must make sure we keep the possible culture prefix, which will be treated as "directory" path here + string clippedName = Path.Combine (Path.GetDirectoryName (name) ?? String.Empty, Path.GetFileNameWithoutExtension (name)); ulong hashFull = HashName (name, is64Bit); ulong hashClipped = HashName (clippedName, is64Bit); diff --git a/src/monodroid/jni/xamarin-android-app-context.cc b/src/monodroid/jni/xamarin-android-app-context.cc index 3654491b944..c1c401880e9 100644 --- a/src/monodroid/jni/xamarin-android-app-context.cc +++ b/src/monodroid/jni/xamarin-android-app-context.cc @@ -13,7 +13,7 @@ MonodroidRuntime::get_method_name (uint32_t mono_image_index, uint32_t method_to { uint64_t id = (static_cast(mono_image_index) << 32) | method_token; - log_debug (LOG_ASSEMBLY, "Looking for name of method with id 0x%llx, in mono image at index %u", id, mono_image_index); + log_debug (LOG_ASSEMBLY, "MM: looking for name of method with id 0x%llx, in mono image at index %u", id, mono_image_index); size_t i = 0; while (mm_method_names[i].id != 0) { if (mm_method_names[i].id == id) { @@ -58,19 +58,15 @@ MonodroidRuntime::get_function_pointer (uint32_t mono_image_index, uint32_t clas // We need to do that, as Mono APIs cannot be invoked from threads that aren't attached to the runtime. mono_thread_attach (mono_get_root_domain ()); - // We don't check for valid return values from image loader, class and method lookup because if any - // of them fails to find the requested entity, they will return `null`. In consequence, we can pass - // these pointers without checking all the way to `mono_method_get_unmanaged_callers_only_ftnptr`, after - // which call we check for errors. This saves some time (not much, but definitely more than zero) MonoImage *image = MonoImageLoader::get_from_index (mono_image_index); MarshalMethodsManagedClass &klass = marshal_methods_class_cache[class_index]; if (klass.klass == nullptr) { - klass.klass = mono_class_get (image, klass.token); + klass.klass = image != nullptr ? mono_class_get (image, klass.token) : nullptr; } - MonoMethod *method = mono_get_method (image, method_token, klass.klass); + MonoMethod *method = klass.klass != nullptr ? mono_get_method (image, method_token, klass.klass) : nullptr; MonoError error; - void *ret = mono_method_get_unmanaged_callers_only_ftnptr (method, &error); + void *ret = method != nullptr ? mono_method_get_unmanaged_callers_only_ftnptr (method, &error) : nullptr; if (XA_LIKELY (ret != nullptr)) { if constexpr (NeedsLocking) { diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs index f1ab011b0ee..8d46154edb4 100644 --- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs +++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs @@ -25,9 +25,56 @@ public void Teardown () Directory.Delete (builder.ProjectDirectory, recursive: true); builder?.Dispose (); + builder = null; proj = null; } + [Test] + public void NativeAssemblyCacheWithSatelliteAssemblies () + { + var path = Path.Combine ("temp", TestName); + var lib = new XamarinAndroidLibraryProject { + ProjectName = "Localization", + OtherBuildItems = { + new BuildItem ("EmbeddedResource", "Foo.resx") { + TextContent = () => InlineData.ResxWithContents ("Cancel") + }, + } + }; + + var languages = new string[] {"es", "de", "fr", "he", "it", "pl", "pt", "ru", "sl" }; + foreach (string lang in languages) { + lib.OtherBuildItems.Add ( + new BuildItem ("EmbeddedResource", $"Foo.{lang}.resx") { + TextContent = () => InlineData.ResxWithContents ($"{lang}") + } + ); + } + + proj = new XamarinAndroidApplicationProject { + IsRelease = true, + }; + proj.References.Add (new BuildItem.ProjectReference ($"..\\{lib.ProjectName}\\{lib.ProjectName}.csproj", lib.ProjectName, lib.ProjectGuid)); + proj.SetAndroidSupportedAbis ("armeabi-v7a", "arm64-v8a", "x86", "x86_64"); + + using (var libBuilder = CreateDllBuilder (Path.Combine (path, lib.ProjectName))) { + builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName)); + Assert.IsTrue (libBuilder.Build (lib), "Library Build should have succeeded."); + Assert.IsTrue (builder.Install (proj), "Install should have succeeded."); + + var apk = Path.Combine (Root, builder.ProjectDirectory, proj.OutputPath, $"{proj.PackageName}-Signed.apk"); + var helper = new ArchiveAssemblyHelper (apk); + + foreach (string lang in languages) { + Assert.IsTrue (helper.Exists ($"assemblies/{lang}/{lib.ProjectName}.resources.dll"), $"Apk should contain satellite assembly for language '{lang}'!"); + } + + Assert.True (builder.RunTarget (proj, "_Run"), "Project should have run."); + Assert.True (WaitForActivityToStart (proj.PackageName, "MainActivity", + Path.Combine (Root, builder.ProjectDirectory, "logcat.log"), 30), "Activity should have started."); + } + } + [Test] public void GlobalLayoutEvent_ShouldRegisterAndFire_OnActivityLaunch ([Values (false, true)] bool isRelease) {