Skip to content

Commit

Permalink
Support loading ICU data from managed Interop (#49406)
Browse files Browse the repository at this point in the history
In iOS we support loading a custom dat file when working with ICU.  The way this worked originally was the mono runtime exported a function that native code would call into (similar to wasm).  After thinking about it a bit, it makes more sense to load this the same way we do on desktop, but with the ability to provide the path to an ICU dat file via an AppContext key `ICU_DAT_FILE_PATH`.  This can be provided before Xamarin iOS calls `monovm_initialize` and they won't have to worry about calling some special function.
  • Loading branch information
steveisok authored Mar 13, 2021
1 parent 6791d05 commit 1d9c402
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 50 deletions.
14 changes: 14 additions & 0 deletions src/libraries/Common/src/Interop/Interop.ICU.iOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.InteropServices;

internal static partial class Interop
{
internal static partial class Globalization
{
[DllImport(Libraries.GlobalizationNative, EntryPoint = "GlobalizationNative_LoadICUData")]
internal static extern int LoadICUData(string path);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ static const Entry s_globalizationNative[] =
DllImportEntry(GlobalizationNative_IsPredefinedLocale)
DllImportEntry(GlobalizationNative_LastIndexOf)
DllImportEntry(GlobalizationNative_LoadICU)
#if defined(STATIC_ICU)
DllImportEntry(GlobalizationNative_LoadICUData)
#endif
DllImportEntry(GlobalizationNative_NormalizeString)
DllImportEntry(GlobalizationNative_StartsWith)
DllImportEntry(GlobalizationNative_ToAscii)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ PALEXPORT int32_t GlobalizationNative_GetICUVersion(void);

#if defined(STATIC_ICU)

PALEXPORT int32_t GlobalizationNative_LoadICUData(char* path);
PALEXPORT int32_t GlobalizationNative_LoadICUData(const char* path);

PALEXPORT const char* GlobalizationNative_GetICUDTName(const char* culture);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,19 @@
static int32_t isLoaded = 0;
static int32_t isDataSet = 0;

static void log_shim_error(const char* format, ...)
{
va_list args;

va_start(args, format);
vfprintf(stderr, format, args);
va_end(args);
}

static void log_icu_error(const char* name, UErrorCode status)
{
const char * statusText = u_errorName(status);
fprintf(stderr, "ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
log_shim_error("ICU call %s failed with error #%d '%s'.\n", name, status, statusText);
}

static void U_CALLCONV icu_trace_data(const void* context, int32_t fnNumber, int32_t level, const char* fmt, va_list args)
Expand Down Expand Up @@ -89,45 +98,60 @@ static int32_t load_icu_data(void* pData)
}
}

int32_t GlobalizationNative_LoadICUData(char* path)
int32_t GlobalizationNative_LoadICUData(const char* path)
{
int32_t ret = -1;
char* icu_data;

FILE *fp = fopen (path, "rb");
FILE *fp = fopen(path, "rb");
if (fp == NULL) {
fprintf (stderr, "Unable to load ICU dat file '%s'.", path);
log_shim_error("Unable to load ICU dat file '%s'.", path);
return ret;
}

if (fseek (fp, 0L, SEEK_END) != 0) {
fprintf (stderr, "Unable to determine size of the dat file");
if (fseek(fp, 0L, SEEK_END) != 0) {
fclose(fp);
log_shim_error("Unable to determine size of the dat file");
return ret;
}

long bufsize = ftell (fp);
long bufsize = ftell(fp);

if (bufsize == -1) {
fprintf (stderr, "Unable to determine size of the ICU dat file.");
fclose(fp);
log_shim_error("Unable to determine size of the ICU dat file.");
return ret;
}

icu_data = malloc (sizeof (char) * (bufsize + 1));
icu_data = malloc(sizeof(char) * (bufsize + 1));

if (icu_data == NULL) {
fclose(fp);
log_shim_error("Unable to allocate enough to read the ICU dat file");
return ret;
}

if (fseek (fp, 0L, SEEK_SET) != 0) {
fprintf (stderr, "Unable to seek ICU dat file.");
if (fseek(fp, 0L, SEEK_SET) != 0) {
fclose(fp);
log_shim_error("Unable to seek ICU dat file.");
return ret;
}

fread (icu_data, sizeof (char), bufsize, fp);
if (ferror ( fp ) != 0 ) {
fprintf (stderr, "Unable to read ICU dat file");
fread(icu_data, sizeof(char), bufsize, fp);
if (ferror( fp ) != 0 ) {
fclose(fp);
log_shim_error("Unable to read ICU dat file");
return ret;
}

fclose (fp);
fclose(fp);

if (load_icu_data(icu_data) == 0) {
log_shim_error("ICU BAD EXIT %d.", ret);
return ret;
}

return load_icu_data (icu_data);
return GlobalizationNative_LoadICU();
}

const char* GlobalizationNative_GetICUDTName(const char* culture)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<IsOSXLike Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsOSXLike>
<IsiOSLike Condition="'$(TargetsMacCatalyst)' == 'true' or '$(TargetsiOS)' == 'true' or '$(TargetstvOS)' == 'true'">true</IsiOSLike>
<SupportsArmIntrinsics Condition="'$(Platform)' == 'arm64'">true</SupportsArmIntrinsics>
<SupportsX86Intrinsics Condition="'$(Platform)' == 'x64' or ('$(Platform)' == 'x86' and '$(TargetsUnix)' != 'true')">true</SupportsX86Intrinsics>
<ILLinkSharedDirectory>$(MSBuildThisFileDirectory)ILLink\</ILLinkSharedDirectory>
Expand Down Expand Up @@ -1082,6 +1083,9 @@
<Compile Include="$(CommonPath)Interop\Interop.ICU.cs">
<Link>Common\Interop\Interop.ICU.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.ICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'">
<Link>Common\Interop\Interop.ICU.iOS.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Interop.Idna.cs">
<Link>Common\Interop\Interop.Idna.cs</Link>
</Compile>
Expand Down Expand Up @@ -1831,11 +1835,13 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.NoRegistry.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.UnixOrBrowser.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.OSVersion.OSX.cs" Condition="'$(IsOSXLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(TargetsMacCatalyst)' != 'true' and '$(TargetsiOS)' != 'true' and '$(TargetstvOS)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.GetFolderPathCore.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CalendarData.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureData.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\CultureInfo.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.Unix.cs" Condition="'$(IsiOSLike)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\GlobalizationMode.LoadICU.iOS.cs" Condition="'$(IsiOSLike)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Globalization\HijriCalendar.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Guid.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\FileStreamHelpers.Unix.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Globalization
{
internal static partial class GlobalizationMode
{
private static int LoadICU() => Interop.Globalization.LoadICU();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Globalization
{
internal static partial class GlobalizationMode
{
private static int LoadICU()
{
object? datPath = AppContext.GetData("ICU_DAT_FILE_PATH");
return (datPath != null) ? Interop.Globalization.LoadICUData(datPath!.ToString()!) : Interop.Globalization.LoadICU();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ private static bool GetGlobalizationInvariantMode()
}
else
{
int loaded = Interop.Globalization.LoadICU();
int loaded = LoadICU();
if (loaded == 0 && !OperatingSystem.IsBrowser())
{
// This can't go into resources, because a resource lookup requires globalization, which requires ICU
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/mini/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ if(HAVE_SYS_ICU)
if(STATIC_ICU)
set(pal_icushim_sources_base
pal_icushim_static.c)
add_definitions(-DSTATIC_ICU=1)
else()
set(pal_icushim_sources_base
pal_icushim.c)
Expand Down
2 changes: 1 addition & 1 deletion src/tasks/AppleAppBuilder/AppleAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public override bool Execute()

if (GenerateXcodeProject)
{
Xcode generator = new Xcode(TargetOS);
Xcode generator = new Xcode(TargetOS, Arch);
generator.EnableRuntimeLogging = EnableRuntimeLogging;

XcodeProjectPath = generator.GenerateXCode(ProjectName, MainLibraryFileName, assemblerFiles,
Expand Down
54 changes: 25 additions & 29 deletions src/tasks/AppleAppBuilder/Templates/runtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
#define MONO_ENTER_GC_UNSAFE
#define MONO_EXIT_GC_UNSAFE

#define APPLE_RUNTIME_IDENTIFIER "//%APPLE_RUNTIME_IDENTIFIER%"

const char *
get_bundle_path (void)
{
Expand Down Expand Up @@ -203,23 +205,6 @@
//%DllMap%
}

int32_t GlobalizationNative_LoadICUData(char *path);

static int32_t load_icu_data ()
{
char path [1024];
int res;

const char *dname = "icudt.dat";
const char *bundle = get_bundle_path ();

os_log_info (OS_LOG_DEFAULT, "Loading ICU data file '%s'.", dname);
res = snprintf (path, sizeof (path) - 1, "%s/%s", bundle, dname);
assert (res > 0);

return GlobalizationNative_LoadICUData(path);
}

#if FORCE_INTERPRETER || FORCE_AOT || (!TARGET_OS_SIMULATOR && !TARGET_OS_MACCATALYST)
void mono_jit_set_aot_mode (MonoAotMode mode);
void register_aot_modules (void);
Expand All @@ -237,16 +222,6 @@ static int32_t load_icu_data ()
setenv ("MONO_LOG_MASK", "all", TRUE);
#endif

#if !INVARIANT_GLOBALIZATION
int32_t ret = load_icu_data ();

if (ret == 0) {
os_log_info (OS_LOG_DEFAULT, "ICU BAD EXIT %d.", ret);
exit (ret);
return;
}
#endif

id args_array = [[NSProcessInfo processInfo] arguments];
assert ([args_array count] <= 128);
const char *managed_argv [128];
Expand All @@ -261,8 +236,29 @@ static int32_t load_icu_data ()
const char* bundle = get_bundle_path ();
chdir (bundle);

char icu_dat_path [1024];
int res;

res = snprintf (icu_dat_path, sizeof (icu_dat_path) - 1, "%s/%s", bundle, "icudt.dat");
assert (res > 0);

// TODO: set TRUSTED_PLATFORM_ASSEMBLIES, APP_PATHS and NATIVE_DLL_SEARCH_DIRECTORIES
monovm_initialize(0, NULL, NULL);
const char *appctx_keys [] = {
"RUNTIME_IDENTIFIER",
"APP_CONTEXT_BASE_DIRECTORY",
#ifndef INVARIANT_GLOBALIZATION
"ICU_DAT_FILE_PATH"
#endif
};
const char *appctx_values [] = {
APPLE_RUNTIME_IDENTIFIER,
bundle,
#ifndef INVARIANT_GLOBALIZATION
icu_dat_path
#endif
};

monovm_initialize (sizeof (appctx_keys) / sizeof (appctx_keys [0]), appctx_keys, appctx_values);

#if FORCE_INTERPRETER
os_log_info (OS_LOG_DEFAULT, "INTERP Enabled");
Expand Down Expand Up @@ -300,7 +296,7 @@ static int32_t load_icu_data ()
assert (assembly);
os_log_info (OS_LOG_DEFAULT, "Executable: %{public}s", executable);

int res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
res = mono_jit_exec (mono_domain_get (), assembly, argi, managed_argv);
// Print this so apps parsing logs can detect when we exited
os_log_info (OS_LOG_DEFAULT, "Exit code: %d.", res);

Expand Down
6 changes: 5 additions & 1 deletion src/tasks/AppleAppBuilder/Xcode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@

internal class Xcode
{
private string RuntimeIdentifier { get; set; }
private string SysRoot { get; set; }
private string Target { get; set; }

public Xcode(string target)
public Xcode(string target, string arch)
{
Target = target;
switch (Target)
Expand All @@ -27,6 +28,8 @@ public Xcode(string target)
SysRoot = Utils.RunProcess("xcrun", "--sdk macosx --show-sdk-path");
break;
}

RuntimeIdentifier = $"{Target}-{arch}";
}

public bool EnableRuntimeLogging { get; set; }
Expand Down Expand Up @@ -175,6 +178,7 @@ public string GenerateXCode(
File.WriteAllText(Path.Combine(binDir, "runtime.m"),
Utils.GetEmbeddedResource("runtime.m")
.Replace("//%DllMap%", dllMap.ToString())
.Replace("//%APPLE_RUNTIME_IDENTIFIER%", RuntimeIdentifier)
.Replace("%EntryPointLibName%", Path.GetFileName(entryPointLib)));

Utils.RunProcess("cmake", cmakeArgs.ToString(), workingDir: binDir);
Expand Down

0 comments on commit 1d9c402

Please sign in to comment.