[mkbundle] support satellite assemblies (#3448)
authorMarek Habersack <grendel@twistedcode.net>
Mon, 19 Sep 2016 09:51:24 +0000 (11:51 +0200)
committerGitHub <noreply@github.com>
Mon, 19 Sep 2016 09:51:24 +0000 (11:51 +0200)
Satellite assemblies are special in that they all share the same file
name, but they live in separate directories named after the locale the
satellite assemblies pack. mkbundle currently doesn't support "clashing"
assembly filenames and will not be able to store the satellite
assemblies in the bundle for that reason.

This patch adds support for storing satellite assemblies with their
culture path prefix (e.g. en-US/MyAssembly.resources.dll) in the bundle
and also special code in the assembly loader to support such filenames
in the code which loads assemblies from the bundle.

Fixes https://bugzilla.xamarin.com/show_bug.cgi?id=27061

mcs/class/corlib/System.Reflection/Assembly.cs
mcs/tools/mkbundle/mkbundle.cs
mono/metadata/assembly.c

index 092ea767526688303dc675d67579d7a3cdbd4889..c5cecb3858e7d17ef3db6c34fb55db48b3ea6ba2 100644 (file)
@@ -507,10 +507,14 @@ namespace System.Reflection {
                        // Try the assembly directory
                        string location = Path.GetDirectoryName (Location);
                        string fullName = Path.Combine (location, Path.Combine (culture.Name, an.Name + ".dll"));
-                       if (!throwOnFileNotFound && !File.Exists (fullName))
-                               return null;
 
-                       return (RuntimeAssembly)LoadFrom (fullName);
+                       try {
+                               return (RuntimeAssembly)LoadFrom (fullName);
+                       } catch {
+                               if (!throwOnFileNotFound && !File.Exists (fullName))
+                                       return null;
+                               throw;
+                       }
                }
 
 #if !MOBILE
index 702d74d16a7dd43ecab4b9be6f71f9017fe6f755..ef681913e28e9d6fb4edd83d9f970e2b5748803a 100755 (executable)
@@ -585,7 +585,7 @@ class MakeBundle {
                
                foreach (var url in files){
                        string fname = LocateFile (new Uri (url).LocalPath);
-                       string aname = Path.GetFileName (fname);
+                       string aname = MakeBundle.GetAssemblyName (fname);
 
                        maker.Add ("assembly:" + aname, fname);
                        if (File.Exists (fname + ".config"))
@@ -695,7 +695,7 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
                        var symbolEscapeRE = new System.Text.RegularExpressions.Regex ("[^\\w_]");
                        foreach (var url in files) {
                                string fname = LocateFile (new Uri (url).LocalPath);
-                               string aname = Path.GetFileName (fname);
+                               string aname = MakeBundle.GetAssemblyName (fname);
                                string encoded = symbolEscapeRE.Replace (aname, "_");
 
                                if (prog == null)
@@ -1007,7 +1007,27 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
        
        static readonly Universe universe = new Universe ();
        static readonly Dictionary<string, string> loaded_assemblies = new Dictionary<string, string> ();
-       
+
+       public static string GetAssemblyName (string path)
+       {
+               string name = Path.GetFileName (path);
+
+               // A bit of a hack to support satellite assemblies. They all share the same name but
+               // are placed in subdirectories named after the locale they implement. Also, all of
+               // them end in .resources.dll, therefore we can use that to detect the circumstances.
+               if (name.EndsWith (".resources.dll", StringComparison.OrdinalIgnoreCase)) {
+                       string dir = Path.GetDirectoryName (path);
+                       int idx = dir.LastIndexOf (Path.DirectorySeparatorChar);
+                       if (idx >= 0) {
+                               name = dir.Substring (idx + 1) + Path.DirectorySeparatorChar + name;
+                               Console.WriteLine ($"Storing satellite assembly '{path}' with name '{name}'");
+                       } else if (!quiet)
+                               Console.WriteLine ($"Warning: satellite assembly {path} doesn't have locale path prefix, name conflicts possible");
+               }
+
+               return name;
+       }
+
        static bool QueueAssembly (List<string> files, string codebase)
        {
                //Console.WriteLine ("CODE BASE IS {0}", codebase);
@@ -1015,7 +1035,7 @@ void          mono_register_config_for_assembly (const char* assembly_name, cons
                        return true;
 
                var path = new Uri(codebase).LocalPath;
-               var name = Path.GetFileName (path);
+               var name = GetAssemblyName (path);
                string found;
                if (loaded_assemblies.TryGetValue (name, out found)) {
                        Error (string.Format ("Duplicate assembly name `{0}'. Both `{1}' and `{2}' use same assembly name.", name, path, found));
index 2510a4a79d3e7d2664703f113f9dcc4b6401b8a8..af7cd6dfaa165462cfe81477fd198930b0464e9e 100644 (file)
@@ -1545,8 +1545,9 @@ mono_assembly_open_from_bundle (const char *filename, MonoImageOpenStatus *statu
 {
        int i;
        char *name;
+       gchar *lowercase_filename;
        MonoImage *image = NULL;
-
+       gboolean is_satellite = FALSE;
        /*
         * we do a very simple search for bundled assemblies: it's not a general 
         * purpose assembly loading mechanism.
@@ -1555,11 +1556,13 @@ mono_assembly_open_from_bundle (const char *filename, MonoImageOpenStatus *statu
        if (!bundles)
                return NULL;
 
+       lowercase_filename = g_utf8_strdown (filename, -1);
+       is_satellite = g_str_has_suffix (lowercase_filename, ".resources.dll");
+       g_free (lowercase_filename);
        name = g_path_get_basename (filename);
-
        mono_assemblies_lock ();
        for (i = 0; !image && bundles [i]; ++i) {
-               if (strcmp (bundles [i]->name, name) == 0) {
+               if (strcmp (bundles [i]->name, is_satellite ? filename : name) == 0) {
                        image = mono_image_open_from_data_with_name ((char*)bundles [i]->data, bundles [i]->size, FALSE, status, refonly, name);
                        break;
                }
@@ -1567,7 +1570,7 @@ mono_assembly_open_from_bundle (const char *filename, MonoImageOpenStatus *statu
        mono_assemblies_unlock ();
        if (image) {
                mono_image_addref (image);
-               mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY, "Assembly Loader loaded assembly from bundle: '%s'.", name);
+               mono_trace (G_LOG_LEVEL_INFO, MONO_TRACE_ASSEMBLY, "Assembly Loader loaded assembly from bundle: '%s'.", is_satellite ? filename : name);
                g_free (name);
                return image;
        }