diff --git a/Xamarin.Android-Tests.sln b/Xamarin.Android-Tests.sln
index 308a55fd1eb..40807925892 100644
--- a/Xamarin.Android-Tests.sln
+++ b/Xamarin.Android-Tests.sln
@@ -76,6 +76,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Performance-Tests", "Perfor
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "timing", "build-tools\timing\timing.csproj", "{37CAA28C-40BE-4253-BA68-CC5D7316A617}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedDSO", "tests\EmbeddedDSOs\EmbeddedDSO\EmbeddedDSO.csproj", "{056ED976-618F-4A3E-910E-AA25230C2296}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmbeddedDSO-UnitTests", "tests\EmbeddedDSOs\EmbeddedDSO-UnitTests\EmbeddedDSO-UnitTests.csproj", "{B160F0E7-799A-4EB9-92B8-D71623C7674A}"
+EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
tests\Xamarin.Forms-Performance-Integration\Xamarin.Forms.Performance.Integration.projitems*{195be9c2-1f91-40dc-bd6d-de860bf083fb}*SharedItemsImports = 13
@@ -194,6 +198,14 @@ Global
{37CAA28C-40BE-4253-BA68-CC5D7316A617}.Debug|Any CPU.Build.0 = Debug|Any CPU
{37CAA28C-40BE-4253-BA68-CC5D7316A617}.Release|Any CPU.ActiveCfg = Release|Any CPU
{37CAA28C-40BE-4253-BA68-CC5D7316A617}.Release|Any CPU.Build.0 = Release|Any CPU
+ {056ED976-618F-4A3E-910E-AA25230C2296}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {056ED976-618F-4A3E-910E-AA25230C2296}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {056ED976-618F-4A3E-910E-AA25230C2296}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {056ED976-618F-4A3E-910E-AA25230C2296}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B160F0E7-799A-4EB9-92B8-D71623C7674A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/build-tools/scripts/RunTests.targets b/build-tools/scripts/RunTests.targets
index 2543d7674ff..626245cde70 100644
--- a/build-tools/scripts/RunTests.targets
+++ b/build-tools/scripts/RunTests.targets
@@ -17,12 +17,14 @@
<_TestAssembly Include="$(_TopDir)\bin\Test$(Configuration)\Xamarin.Android.Build.Tests.dll" />
<_TestAssembly Include="$(_TopDir)\bin\Test$(Configuration)\CodeBehind\CodeBehindUnitTests.dll" />
+ <_TestAssembly Include="$(_TopDir)\bin\Test$(Configuration)\EmbeddedDSO\EmbeddedDSOUnitTests.dll" />
<_ApkTestProject Include="$(_TopDir)\src\Mono.Android\Test\Mono.Android-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\CodeGen-Binding\Xamarin.Android.JcwGen-Tests\Xamarin.Android.JcwGen-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\CodeGen-MkBundle\Xamarin.Android.MakeBundle-Tests\Xamarin.Android.MakeBundle-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\locales\Xamarin.Android.Locale-Tests\Xamarin.Android.Locale-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\BCL-Tests\Xamarin.Android.Bcl-Tests\Xamarin.Android.Bcl-Tests.csproj" />
<_ApkTestProject Include="$(_TopDir)\tests\Xamarin.Forms-Performance-Integration\Droid\Xamarin.Forms.Performance.Integration.Droid.csproj" />
+ <_ApkTestProject Include="$(_TopDir)\tests\EmbeddedDSOs\EmbeddedDSO\EmbeddedDSO.csproj" />
<_ApkTestProjectAot Include="$(_TopDir)\src\Mono.Android\Test\Mono.Android-Tests.csproj" />
<_ApkTestProjectBundle Include="$(_TopDir)\src\Mono.Android\Test\Mono.Android-Tests.csproj" />
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs
index 57726552c35..54d15e9e7e6 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AndroidZipAlign.cs
@@ -36,7 +36,7 @@ protected override string GenerateCommandLineCommands ()
string sourceFilename = Path.GetFileNameWithoutExtension (Source.ItemSpec);
if (sourceFilename.EndsWith (strSignedUnaligned))
sourceFilename = sourceFilename.Remove (sourceFilename.Length - strSignedUnaligned.Length);
- return string.Format ("{0} \"{1}\" \"{2}{3}{4}-Signed.apk\"",
+ return string.Format ("-p {0} \"{1}\" \"{2}{3}{4}-Signed.apk\"",
Alignment, Source.ItemSpec, DestinationDirectory.ItemSpec, Path.DirectorySeparatorChar, sourceFilename);
}
diff --git a/src/monodroid/jni/dylib-mono.c b/src/monodroid/jni/dylib-mono.c
index 67dd46b566d..871e00f6730 100644
--- a/src/monodroid/jni/dylib-mono.c
+++ b/src/monodroid/jni/dylib-mono.c
@@ -33,25 +33,18 @@ void monodroid_dylib_mono_free (struct DylibMono *mono_imports)
free (mono_imports);
}
-int monodroid_dylib_mono_init (struct DylibMono *mono_imports, const char *libmono_path)
+int monodroid_dylib_mono_init (struct DylibMono *mono_imports, void *libmono_handle)
{
int symbols_missing = FALSE;
if (mono_imports == NULL)
return FALSE;
- memset (mono_imports, 0, sizeof (*mono_imports));
-
- /*
- * We need to use RTLD_GLOBAL so that libmono-profiler-log.so can resolve
- * symbols against the Mono library we're loading.
- */
- mono_imports->dl_handle = dlopen (libmono_path, RTLD_LAZY | RTLD_GLOBAL);
-
- if (!mono_imports->dl_handle) {
+ if (libmono_handle == NULL)
return FALSE;
- }
+ memset (mono_imports, 0, sizeof (*mono_imports));
+ mono_imports->dl_handle = libmono_handle;
mono_imports->version = sizeof (*mono_imports);
log_info (LOG_DEFAULT, "Loading Mono symbols...");
diff --git a/src/monodroid/jni/dylib-mono.h b/src/monodroid/jni/dylib-mono.h
index 9d95e4fb7a3..f32d82c6623 100644
--- a/src/monodroid/jni/dylib-mono.h
+++ b/src/monodroid/jni/dylib-mono.h
@@ -348,6 +348,6 @@ struct DylibMono {
MONO_API struct DylibMono* monodroid_dylib_mono_new (const char *libmono_path);
MONO_API void monodroid_dylib_mono_free (struct DylibMono *mono_imports);
- int monodroid_dylib_mono_init (struct DylibMono *mono_imports, const char *libmono_path);
+ int monodroid_dylib_mono_init (struct DylibMono *mono_imports, void *libmono_handle);
#endif /* INC_MONODROID_DYLIB_MONO_H */
diff --git a/src/monodroid/jni/monodroid-glue.c b/src/monodroid/jni/monodroid-glue.c
index 229376cad94..aaefe486e60 100644
--- a/src/monodroid/jni/monodroid-glue.c
+++ b/src/monodroid/jni/monodroid-glue.c
@@ -72,6 +72,7 @@
#include "ioapi.h"
#include "monodroid-glue.h"
#include "mkbundle-api.h"
+#include "cpu-arch.h"
#ifndef WINDOWS
#include "xamarin_getifaddrs.h"
@@ -100,6 +101,22 @@ static int wait_for_gdb;
static volatile int monodroid_gdb_wait = TRUE;
static int android_api_level = 0;
+// Values correspond to the CPU_KIND_* macros
+static const char* android_abi_names[CPU_KIND_X86_64+1] = {
+ "unknown",
+ [CPU_KIND_ARM] = "armeabi-v7a",
+ [CPU_KIND_ARM64] = "arm64-v8a",
+ [CPU_KIND_MIPS] = "mips",
+ [CPU_KIND_X86] = "x86",
+ [CPU_KIND_X86_64] = "x86_64",
+};
+#define ANDROID_ABI_NAMES_SIZE (sizeof(android_abi_names) / sizeof (android_abi_names[0]))
+
+static void* load_dso (const char *path, int dl_flags, mono_bool skip_exists_check);
+static void* load_dso_from_app_lib_dirs (const char *name, int dl_flags);
+static void* load_dso_from_override_dirs (const char *name, int dl_flags);
+static void* load_dso_from_any_directories (const char *name, int dl_flags);
+
/* Can be called by a native debugger to break the wait on startup */
MONO_API void
monodroid_clear_gdb_wait (void)
@@ -467,7 +484,9 @@ monodroid_get_dylib (void)
return &mono;
}
-static const char *app_libdir;
+static const char **app_lib_directories;
+static size_t app_lib_directories_size = 0;
+static int embedded_dso_mode = 0;
int file_copy(const char *to, const char *from)
{
@@ -639,6 +658,7 @@ static char*
get_libmonosgen_path ()
{
char *libmonoso;
+ int i;
#ifndef RELEASE
// Android 5 includes some restrictions on loading dynamic libraries via dlopen() from
@@ -647,11 +667,16 @@ get_libmonosgen_path ()
copy_monosgen_to_internal_location (primary_override_dir, external_override_dir);
copy_monosgen_to_internal_location (primary_override_dir, external_legacy_override_dir);
- int i;
- for (i = 0; i < MAX_OVERRIDES; ++i)
- TRY_LIBMONOSGEN (override_dirs [i])
+ if (!embedded_dso_mode) {
+ for (i = 0; i < MAX_OVERRIDES; ++i)
+ TRY_LIBMONOSGEN (override_dirs [i]);
+ }
#endif
- TRY_LIBMONOSGEN (app_libdir)
+ if (!embedded_dso_mode) {
+ for (i = 0; i < app_lib_directories_size; i++) {
+ TRY_LIBMONOSGEN (app_lib_directories [i]);
+ }
+ }
libmonoso = runtime_libdir ? monodroid_strdup_printf ("%s" MONODROID_PATH_SEPARATOR MONO_SGEN_ARCH_SO, runtime_libdir, sizeof(void*) == 8 ? "64bit" : "32bit") : NULL;
if (libmonoso && file_exists (libmonoso)) {
@@ -686,13 +711,22 @@ get_libmonosgen_path ()
TRY_LIBMONOSGEN (get_libmonoandroid_directory_path ())
#endif
- TRY_LIBMONOSGEN (SYSTEM_LIB_PATH)
-
-#ifdef RELEASE
- log_fatal (LOG_DEFAULT, "cannot find libmonosgen-2.0.so in app_libdir: %s nor in previously printed locations.", app_libdir);
-#else
- log_fatal (LOG_DEFAULT, "cannot find libmonosgen-2.0.so in override_dir: %s, app_libdir: %s nor in previously printed locations.", override_dirs[0], app_libdir);
+ TRY_LIBMONOSGEN (SYSTEM_LIB_PATH);
+ log_fatal (LOG_DEFAULT, "Cannot find '%s'. Looked in the following locations:", MONO_SGEN_SO);
+
+#ifndef RELEASE
+ if (!embedded_dso_mode) {
+ for (i = 0; i < MAX_OVERRIDES; ++i) {
+ if (override_dirs [i] == NULL)
+ continue;
+ log_fatal (LOG_DEFAULT, " %s", override_dirs [i]);
+ }
+ }
#endif
+ for (i = 0; i < app_lib_directories_size; i++) {
+ log_fatal (LOG_DEFAULT, " %s", app_lib_directories [i]);
+ }
+
log_fatal (LOG_DEFAULT, "Do you have a shared runtime build of your app with AndroidManifest.xml android:minSdkVersion < 10 while running on a 64-bit Android 5.0 target? This combination is not supported.");
log_fatal (LOG_DEFAULT, "Please either set android:minSdkVersion >= 10 or use a build without the shared runtime (like default Release configuration).");
exit (FATAL_EXIT_CANNOT_FIND_LIBMONOSGEN);
@@ -704,53 +738,185 @@ typedef void* (*mono_mkbundle_init_ptr) (void (*)(const MonoBundledAssembly **),
mono_mkbundle_init_ptr mono_mkbundle_init;
void (*mono_mkbundle_initialize_mono_api) (const BundleMonoAPI *info);
-static void
-setup_bundled_app (const char *libappso)
+static char*
+get_full_dso_path (const char *base_dir, const char *dso_path, mono_bool *needs_free)
{
- void *libapp;
+ assert (needs_free);
+
+ *needs_free = FALSE;
+ if (!dso_path)
+ return NULL;
- libapp = dlopen (libappso, RTLD_LAZY);
+ if (base_dir == NULL || is_path_rooted (dso_path))
+ return (char*)dso_path; // Absolute path or no base path, can't do much with it
- if (!libapp) {
- log_fatal (LOG_BUNDLE, "bundled app initialization error: %s", dlerror ());
- exit (FATAL_EXIT_CANNOT_LOAD_BUNDLE);
+ char *full_path = path_combine (base_dir, dso_path);
+ *needs_free = TRUE;
+ return full_path;
+}
+
+static void*
+load_dso (const char *path, int dl_flags, mono_bool skip_exists_check)
+{
+ if (path == NULL)
+ return NULL;
+
+ log_info (LOG_ASSEMBLY, "Trying to load shared library '%s'", path);
+ if (!skip_exists_check && !embedded_dso_mode && !file_exists (path)) {
+ log_info (LOG_ASSEMBLY, "Shared library '%s' not found", path);
+ return NULL;
}
- mono_mkbundle_initialize_mono_api = dlsym (libapp, "initialize_mono_api");
- if (!mono_mkbundle_initialize_mono_api)
- log_error (LOG_BUNDLE, "Missing initialize_mono_api in the application");
+ void *handle = dlopen (path, dl_flags);
+ if (handle == NULL)
+ log_info (LOG_ASSEMBLY, "Failed to load shared library '%s'. %s", path, dlerror ());
+ return handle;
+}
- mono_mkbundle_init = dlsym (libapp, "mono_mkbundle_init");
- if (!mono_mkbundle_init)
- log_error (LOG_BUNDLE, "Missing mono_mkbundle_init in the application");
- log_info (LOG_BUNDLE, "Bundled app loaded: %s", libappso);
+static void*
+load_dso_from_specified_dirs (const char **directories, int num_entries, const char *dso_name, int dl_flags)
+{
+ assert (directories);
+ if (dso_name == NULL)
+ return NULL;
+
+ mono_bool needs_free = FALSE;
+ char *full_path = NULL;
+ for (int i = 0; i < num_entries; i++) {
+ full_path = get_full_dso_path (directories [i], dso_name, &needs_free);
+ void *handle = load_dso (full_path, dl_flags, FALSE);
+ if (needs_free)
+ free (full_path);
+ if (handle != NULL)
+ return handle;
+ }
+
+ return NULL;
+}
+
+static void*
+load_dso_from_app_lib_dirs (const char *name, int dl_flags)
+{
+ return load_dso_from_specified_dirs (app_lib_directories, app_lib_directories_size, name, dl_flags);
+}
+
+static void*
+load_dso_from_override_dirs (const char *name, int dl_flags)
+{
+#ifdef RELEASE
+ return NULL;
+#else
+ return load_dso_from_specified_dirs (override_dirs, MAX_OVERRIDES, name, dl_flags);
+#endif
+}
+
+static void* load_dso_from_any_directories (const char *name, int dl_flags)
+{
+ void *handle = load_dso_from_override_dirs (name, dl_flags);
+ if (handle == NULL)
+ handle = load_dso_from_app_lib_dirs (name, dl_flags);
+ return handle;
}
static char*
-get_bundled_app (JNIEnv *env, jstring dir)
+get_existing_dso_path_on_disk (const char *base_dir, const char *dso_name, mono_bool *needs_free)
{
- const char *v;
- char *libapp;
+ assert (needs_free);
-#ifndef RELEASE
- libapp = path_combine (override_dirs [0], "libmonodroid_bundle_app.so");
+ *needs_free = FALSE;
+ char *dso_path = get_full_dso_path (base_dir, dso_name, needs_free);
+ if (file_exists (dso_path))
+ return dso_path;
- if (file_exists (libapp))
- return libapp;
+ *needs_free = FALSE;
+ free (dso_path);
+ return NULL;
+}
- free (libapp);
+
+static void
+dso_alloc_cleanup (char **dso_path, mono_bool *needs_free)
+{
+ assert (needs_free);
+ if (dso_path != NULL) {
+ if (*needs_free)
+ free (*dso_path);
+ *dso_path = NULL;
+ }
+ *needs_free = FALSE;
+}
+
+static char*
+get_full_dso_path_on_disk (const char *dso_name, mono_bool *needs_free)
+{
+ assert (needs_free);
+
+ *needs_free = FALSE;
+ if (embedded_dso_mode)
+ return NULL;
+#ifndef RELEASE
+ char *dso_path = NULL;
+ for (int i = 0; i < MAX_OVERRIDES; i++) {
+ if (override_dirs [i] == NULL)
+ continue;
+ dso_path = get_existing_dso_path_on_disk (override_dirs [i], dso_name, needs_free);
+ if (dso_path != NULL)
+ return dso_path;
+ dso_alloc_cleanup (&dso_path, needs_free);
+ }
#endif
+ for (int i = 0; i < app_lib_directories_size; i++) {
+ dso_path = get_existing_dso_path_on_disk (app_lib_directories [i], dso_name, needs_free);
+ if (dso_path != NULL)
+ return dso_path;
+ dso_alloc_cleanup (&dso_path, needs_free);
+ }
- if (dir) {
- v = (*env)->GetStringUTFChars (env, dir, NULL);
- if (v) {
- libapp = path_combine (v, "libmonodroid_bundle_app.so");
- (*env)->ReleaseStringUTFChars (env, dir, v);
- if (file_exists (libapp))
- return libapp;
+ return NULL;
+}
+
+// This function could be improved if we somehow marked an apk containing just the bundled app as
+// such - perhaps another __XA* environment variable? Would certainly make code faster.
+static void
+setup_bundled_app (const char *dso_name)
+{
+ static int dlopen_flags = RTLD_LAZY;
+ void *libapp = NULL;
+
+ if (embedded_dso_mode) {
+ log_info (LOG_DEFAULT, "bundle app: embedded DSO mode");
+ libapp = load_dso_from_any_directories (dso_name, dlopen_flags);
+ } else {
+ mono_bool needs_free = FALSE;
+ log_info (LOG_DEFAULT, "bundle app: normal mode");
+ char *bundle_path = get_full_dso_path_on_disk (dso_name, &needs_free);
+ log_info (LOG_DEFAULT, "bundle_path == %s", bundle_path ? bundle_path : "");
+ if (bundle_path == NULL)
+ return;
+ log_info (LOG_BUNDLE, "Attempting to load bundled app from %s", bundle_path);
+ libapp = load_dso (bundle_path, dlopen_flags, TRUE);
+ free (bundle_path);
+ }
+
+ if (libapp == NULL) {
+ log_info (LOG_DEFAULT, "No libapp!");
+ if (!embedded_dso_mode) {
+ log_fatal (LOG_BUNDLE, "bundled app initialization error");
+ exit (FATAL_EXIT_CANNOT_LOAD_BUNDLE);
+ } else {
+ log_info (LOG_BUNDLE, "bundled app not found in the APK, ignoring.");
+ return;
}
}
- return NULL;
+
+ mono_mkbundle_initialize_mono_api = dlsym (libapp, "initialize_mono_api");
+ if (!mono_mkbundle_initialize_mono_api)
+ log_error (LOG_BUNDLE, "Missing initialize_mono_api in the application");
+
+ mono_mkbundle_init = dlsym (libapp, "mono_mkbundle_init");
+ if (!mono_mkbundle_init)
+ log_error (LOG_BUNDLE, "Missing mono_mkbundle_init in the application");
+ log_info (LOG_BUNDLE, "Bundled app loaded: %s", dso_name);
}
static JavaVM *jvm;
@@ -2922,7 +3088,6 @@ init_android_runtime (MonoDomain *domain, JNIEnv *env, jobject loader)
void *args [1];
args [0] = &init;
- android_api_level = GetAndroidSdkVersion (env, loader);
init.javaVm = jvm;
init.env = env;
init.logCategories = log_categories;
@@ -3098,48 +3263,51 @@ convert_dl_flags (int flags)
static void*
monodroid_dlopen (const char *name, int flags, char **err, void *user_data)
{
+ int dl_flags = convert_dl_flags (flags);
+ void *h = NULL;
+ char *full_name = NULL;
+ char *basename = NULL;
+ mono_bool libmonodroid_fallback = FALSE;
+
/* name is NULL when we're P/Invoking __Internal, so remap to libmonodroid */
- char *full_name = path_combine (app_libdir, name ? name : "libmonodroid.so");
- if (!name && !file_exists (full_name)) {
- log_info (LOG_ASSEMBLY, "Trying to load library '%s'", full_name);
- free (full_name);
- full_name = path_combine (SYSTEM_LIB_PATH, "libmonodroid.so");
+ if (name == NULL) {
+ name = "libmonodroid.so";
+ libmonodroid_fallback = TRUE;
}
- int dl_flags = convert_dl_flags (flags);
- void *h = dlopen (full_name, dl_flags);
- log_info (LOG_ASSEMBLY, "Trying to load library '%s'", full_name);
- if (!h && name && (strstr (name, ".dll.so") || strstr (name, ".exe.so"))) {
- char *full_name2;
- const char *basename;
+ h = load_dso_from_any_directories (name, dl_flags);
+ if (h != NULL) {
+ goto done_and_out;
+ }
- if (strrchr (name, '/'))
- basename = strrchr (name, '/') + 1;
- else
- basename = name;
+ if (libmonodroid_fallback) {
+ full_name = path_combine (SYSTEM_LIB_PATH, "libmonodroid.so");
+ h = load_dso (full_name, dl_flags, FALSE);
+ goto done_and_out;
+ }
- /* Try loading AOT modules from the override dir */
- if (override_dirs [0]) {
- full_name2 = monodroid_strdup_printf ("%s" MONODROID_PATH_SEPARATOR "libaot-%s", override_dirs [0], basename);
- h = dlopen (full_name2, dl_flags);
- free (full_name2);
- }
+ if (!strstr (name, ".dll.so") && !strstr (name, ".exe.so")) {
+ goto done_and_out;
+ }
- /* Try loading AOT modules from the lib dir */
- if (!h) {
- full_name2 = monodroid_strdup_printf ("%s" MONODROID_PATH_SEPARATOR "libaot-%s", app_libdir, basename);
- h = dlopen (full_name2, dl_flags);
- free (full_name2);
- }
+ basename = strrchr (name, '/');
+ if (basename != NULL)
+ basename++;
+ else
+ basename = (char*)name;
- if (h)
- log_info (LOG_ASSEMBLY, "Loaded AOT image '%s'", full_name2);
- }
+ basename = monodroid_strdup_printf ("libaot-%s", basename);
+ h = load_dso_from_any_directories (basename, dl_flags);
+
+ if (h != NULL)
+ log_info (LOG_ASSEMBLY, "Loaded AOT image '%s'", basename);
+ done_and_out:
if (!h && err) {
*err = monodroid_strdup_printf ("Could not load library: Library '%s' not found.", full_name);
}
+ free (basename);
free (full_name);
return h;
@@ -3272,57 +3440,18 @@ load_profiler (void *handle, const char *desc, const char *symbol)
}
static mono_bool
-load_embedded_profiler (const char *desc, const char *name)
+load_profiler_from_handle (void *dso_handle, const char *desc, const char *name)
{
- mono_bool result;
-
- char *full_name = path_combine (app_libdir, "libmonodroid.so");
- void *h = dlopen (full_name, RTLD_LAZY);
- const char *e = dlerror ();
-
- log_warn (LOG_DEFAULT, "looking for embedded profiler within '%s': dlopen=%p error=%s",
- full_name,
- h,
- h != NULL ? "" : e);
-
- free (full_name);
-
- if (!h) {
- return 0;
- }
+ if (!dso_handle)
+ return FALSE;
char *symbol = monodroid_strdup_printf ("%s_%s", INITIALIZER_NAME, name);
- if (!(result = load_profiler (h, desc, symbol)))
- dlclose (h);
+ mono_bool result = load_profiler (dso_handle, desc, symbol);
free (symbol);
-
- return result;
-}
-
-static mono_bool
-load_profiler_from_directory (const char *directory, const char *libname, const char *desc, const char *name)
-{
- char *full_name = path_combine (directory, libname);
- int exists = file_exists (full_name);
- void *h = exists ? dlopen (full_name, RTLD_LAZY) : NULL;
- const char *e = exists ? dlerror () : "No such file or directory";
-
- log_warn (LOG_DEFAULT, "Trying to load profiler: %s: dlopen=%p error=%s",
- full_name,
- h,
- h != NULL ? "" : e);
-
- free (full_name);
-
- if (h) {
- char *symbol = monodroid_strdup_printf ("%s_%s", INITIALIZER_NAME, name);
- mono_bool result = load_profiler (h, desc, symbol);
- free (symbol);
- if (result)
- return 1;
- dlclose (h);
- }
- return 0;
+ if (result)
+ return TRUE;
+ dlclose (dso_handle);
+ return FALSE;
}
static void
@@ -3330,7 +3459,6 @@ monodroid_profiler_load (const char *libmono_path, const char *desc, const char
{
const char* col = strchr (desc, ':');
char *mname;
- int oi;
if (col != NULL) {
mname = xmalloc (col - desc + 1);
@@ -3340,29 +3468,19 @@ monodroid_profiler_load (const char *libmono_path, const char *desc, const char
mname = monodroid_strdup_printf ("%s", desc);
}
+ int dlopen_flags = RTLD_LAZY;
char *libname = monodroid_strdup_printf ("libmono-profiler-%s.so", mname);
-
mono_bool found = 0;
-
- for (oi = 0; oi < MAX_OVERRIDES; ++oi) {
- if (!directory_exists (override_dirs [oi]))
- continue;
- if ((found = load_profiler_from_directory (override_dirs [oi], libname, desc, mname)))
- break;
+ void *handle = load_dso_from_any_directories (libname, dlopen_flags);
+ found = load_profiler_from_handle (handle, desc, mname);
+
+ if (!found && libmono_path != NULL) {
+ char *full_path = path_combine (libmono_path, libname);
+ handle = load_dso (full_path, dlopen_flags, FALSE);
+ free (full_path);
+ found = load_profiler_from_handle (handle, desc, mname);
}
- do {
- if (found)
- break;
- if ((found = load_profiler_from_directory (app_libdir, libname, desc, mname)))
- break;
- if ((found = load_embedded_profiler (desc, mname)))
- break;
- if (libmono_path != NULL && (found = load_profiler_from_directory (libmono_path, libname, desc, mname)))
- break;
- } while (0);
-
-
if (found && logfile != NULL)
set_world_accessable (logfile);
@@ -3468,7 +3586,7 @@ setup_environment_from_line (const char *line)
}
static void
-setup_environment_from_file (const char *apk, int index, int apk_count)
+setup_environment_from_file (const char *apk, int index, int apk_count, void *user_data)
{
unzFile file;
if ((file = unzOpen (apk)) == NULL)
@@ -3510,7 +3628,7 @@ setup_environment_from_file (const char *apk, int index, int apk_count)
}
static void
-for_each_apk (JNIEnv *env, jobjectArray runtimeApks, void (*handler) (const char *apk, int index, int apk_count))
+for_each_apk (JNIEnv *env, jobjectArray runtimeApks, void (*handler) (const char *apk, int index, int apk_count, void *user_data), void *user_data)
{
int i;
jsize apksLength = (*env)->GetArrayLength (env, runtimeApks);
@@ -3518,7 +3636,7 @@ for_each_apk (JNIEnv *env, jobjectArray runtimeApks, void (*handler) (const char
jstring e = (*env)->GetObjectArrayElement (env, runtimeApks, i);
const char *apk = (*env)->GetStringUTFChars (env, e, NULL);
- handler (apk, i, apksLength);
+ handler (apk, i, apksLength, user_data);
(*env)->ReleaseStringUTFChars (env, e, apk);
}
}
@@ -3526,11 +3644,11 @@ for_each_apk (JNIEnv *env, jobjectArray runtimeApks, void (*handler) (const char
static void
setup_environment (JNIEnv *env, jobjectArray runtimeApks)
{
- for_each_apk (env, runtimeApks, setup_environment_from_file);
+ for_each_apk (env, runtimeApks, setup_environment_from_file, NULL);
}
static void
-setup_process_args_apk (const char *apk, int index, int apk_count)
+setup_process_args_apk (const char *apk, int index, int apk_count, void *user_data)
{
if (!apk || index != apk_count - 1)
return;
@@ -3542,7 +3660,7 @@ setup_process_args_apk (const char *apk, int index, int apk_count)
static void
setup_process_args (JNIEnv *env, jobjectArray runtimeApks)
{
- for_each_apk (env, runtimeApks, setup_process_args_apk);
+ for_each_apk (env, runtimeApks, setup_process_args_apk, NULL);
}
/*
@@ -3688,7 +3806,7 @@ This is a hack to set llvm::DisablePrettyStackTrace to true and avoid this sourc
static void
disable_external_signal_handlers (void)
{
- void *llvm = dlopen ("libLLVM.so", RTLD_LAZY);
+ void *llvm = load_dso ("libLLVM.so", RTLD_LAZY, TRUE);
if (llvm) {
_Bool *disable_signals = dlsym (llvm, "_ZN4llvm23DisablePrettyStackTraceE");
if (disable_signals) {
@@ -3742,6 +3860,14 @@ create_and_initialize_domain (JNIEnv* env, jobjectArray runtimeApks, jobjectArra
return domain;
}
+static void
+add_apk_libdir (const char *apk, int index, int apk_count, void *user_data)
+{
+ assert (user_data);
+ assert (index >= 0 && index < app_lib_directories_size);
+ app_lib_directories [index] = monodroid_strdup_printf ("%s!/lib/%s", apk, (const char*)user_data);
+}
+
JNIEXPORT void JNICALL
Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobjectArray runtimeApks, jstring runtimeNativeLibDir, jobjectArray appDirs, jobject loader, jobjectArray externalStorageDirs, jobjectArray assemblies, jstring packageName)
{
@@ -3749,13 +3875,13 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
char *connect_args;
jstring libdir_s;
const char *libdir, *esd;
- char *libmonosgen_path;
- char *libmonodroid_bundle_app_path;
char *counters_path;
const char *pkgName;
char *aotMode;
int i;
+ android_api_level = GetAndroidSdkVersion (env, loader);
+
pkgName = (*env)->GetStringUTFChars (env, packageName, NULL);
monodroid_store_package_name (pkgName); /* Will make a copy of the string */
(*env)->ReleaseStringUTFChars (env, packageName, pkgName);
@@ -3772,6 +3898,26 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
setup_environment (env, runtimeApks);
+ if (android_api_level < 23 || getenv ("__XA_DSO_IN_APK") == NULL) {
+ log_info (LOG_DEFAULT, "Setting up for DSO lookup in app data directories");
+ libdir_s = (*env)->GetObjectArrayElement (env, appDirs, 2);
+ libdir = (*env)->GetStringUTFChars (env, libdir_s, NULL);
+ app_lib_directories_size = 1;
+ app_lib_directories = (const char**) xcalloc (app_lib_directories_size, sizeof(char*));
+ app_lib_directories [0] = monodroid_strdup_printf ("%s", libdir);
+ (*env)->ReleaseStringUTFChars (env, libdir_s, libdir);
+ } else {
+ log_info (LOG_DEFAULT, "Setting up for DSO lookup directly in the APK");
+ embedded_dso_mode = 1;
+ app_lib_directories_size = (*env)->GetArrayLength (env, runtimeApks);
+ app_lib_directories = (const char**) xcalloc (app_lib_directories_size, sizeof(char*));
+
+ unsigned short built_for_cpu = 0, running_on_cpu = 0;
+ unsigned char is64bit = 0;
+ _monodroid_detect_cpu_and_architecture (&built_for_cpu, &running_on_cpu, &is64bit);
+ for_each_apk (env, runtimeApks, add_apk_libdir, android_abi_names [running_on_cpu]);
+ }
+
primary_override_dir = get_primary_override_dir (env, (*env)->GetObjectArrayElement (env, appDirs, 0));
esd = (*env)->GetStringUTFChars (env, (*env)->GetObjectArrayElement (env, externalStorageDirs, 0), NULL);
external_override_dir = monodroid_strdup_printf ("%s", esd);
@@ -3799,23 +3945,7 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
log_warn (LOG_DEFAULT, "Using override path: %s", p);
}
#endif
-
- jsize appDirsLength = (*env)->GetArrayLength (env, appDirs);
-
- for (i = 0; i < appDirsLength; ++i) {
- jstring appDir = (*env)->GetObjectArrayElement (env, appDirs, i);
- libmonodroid_bundle_app_path = get_bundled_app (env, appDir);
- if (libmonodroid_bundle_app_path) {
- setup_bundled_app (libmonodroid_bundle_app_path);
- free (libmonodroid_bundle_app_path);
- break;
- }
- }
-
- libdir_s = (*env)->GetObjectArrayElement (env, appDirs, 2);
- libdir = (*env)->GetStringUTFChars (env, libdir_s, NULL);
- app_libdir = monodroid_strdup_printf ("%s", libdir);
- (*env)->ReleaseStringUTFChars (env, libdir_s, libdir);
+ setup_bundled_app ("libmonodroid_bundle_app.so");
if (runtimeNativeLibDir != NULL) {
const char *rd;
@@ -3824,14 +3954,25 @@ Java_mono_android_Runtime_init (JNIEnv *env, jclass klass, jstring lang, jobject
(*env)->ReleaseStringUTFChars (env, runtimeNativeLibDir, rd);
}
- libmonosgen_path = get_libmonosgen_path ();
- if (!monodroid_dylib_mono_init (&mono, libmonosgen_path)) {
+ void *libmonosgen_handle = NULL;
+
+ /*
+ * We need to use RTLD_GLOBAL so that libmono-profiler-log.so can resolve
+ * symbols against the Mono library we're loading.
+ */
+ int sgen_dlopen_flags = RTLD_LAZY | RTLD_GLOBAL;
+ if (embedded_dso_mode) {
+ libmonosgen_handle = load_dso_from_any_directories (MONO_SGEN_SO, sgen_dlopen_flags);
+ }
+
+ if (libmonosgen_handle == NULL)
+ libmonosgen_handle = load_dso (get_libmonosgen_path (), sgen_dlopen_flags, FALSE);
+
+ if (!monodroid_dylib_mono_init (&mono, libmonosgen_handle)) {
log_fatal (LOG_DEFAULT, "shared runtime initialization error: %s", dlerror ());
exit (FATAL_EXIT_CANNOT_FIND_MONO);
}
setup_process_args (env, runtimeApks);
-
- free (libmonosgen_path);
#ifndef WINDOWS
_monodroid_getifaddrs_init ();
#endif
diff --git a/src/monodroid/jni/util.c b/src/monodroid/jni/util.c
index 049adcde107..b0a199e2035 100644
--- a/src/monodroid/jni/util.c
+++ b/src/monodroid/jni/util.c
@@ -14,6 +14,7 @@
#ifdef WINDOWS
#include
+#include
#endif
#include "java-interop-util.h"
@@ -500,3 +501,18 @@ monodroid_dirent_hasextension (monodroid_dirent_t *e, const char *extension)
return result;
#endif
}
+
+mono_bool
+is_path_rooted (const char *path)
+{
+ if (path == NULL)
+ return FALSE;
+#ifdef WINDOWS
+ LPCWSTR wpath = utf8_to_utf16 (path);
+ BOOL ret = !PathIsRelativeW (wpath);
+ free (wpath);
+ return ret;
+#else
+ return path [0] == MONODROID_PATH_SEPARATOR_CHAR;
+#endif
+}
diff --git a/src/monodroid/jni/util.h b/src/monodroid/jni/util.h
index aac332abdfa..79b0091d345 100644
--- a/src/monodroid/jni/util.h
+++ b/src/monodroid/jni/util.h
@@ -10,9 +10,11 @@
#endif
#if WINDOWS
-#define MONODROID_PATH_SEPARATOR "\\"
+#define MONODROID_PATH_SEPARATOR "\\"
+#define MONODROID_PATH_SEPARATOR_CHAR '\\'
#else
-#define MONODROID_PATH_SEPARATOR "/"
+#define MONODROID_PATH_SEPARATOR "/"
+#define MONODROID_PATH_SEPARATOR_CHAR '/'
#endif
#if WINDOWS
@@ -63,5 +65,6 @@ void *monodroid_runtime_invoke (struct DylibMono *mono, MonoDomain *d
MonoClass *monodroid_get_class_from_name (struct DylibMono *mono, MonoDomain *domain, const char* assembly, const char *namespace, const char *type);
MonoDomain *monodroid_create_appdomain (struct DylibMono *mono, MonoDomain *parent_domain, const char *friendly_name, int shadow_copy, const char *shadow_directories);
MonoClass *monodroid_get_class_from_image (struct DylibMono *mono, MonoDomain *domain, MonoImage* image, const char *namespace, const char *type);
+mono_bool is_path_rooted (const char *path);
#endif /* __MONODROID_UTIL_H__ */
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/BuildTests.cs b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/BuildTests.cs
new file mode 100644
index 00000000000..19050a11295
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/BuildTests.cs
@@ -0,0 +1,299 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Xml;
+using System.Xml.XPath;
+
+using Microsoft.Build.Framework;
+using NUnit.Framework;
+using Xamarin.ProjectTools;
+using Xamarin.Tools.Zip;
+
+using XABuildPaths = global::Xamarin.Android.Build.Paths;
+
+namespace EmbeddedDSOUnitTests
+{
+ sealed class LocalBuilder : Builder
+ {
+ public bool Build (string projectOrSolution, string target, string[] parameters = null, Dictionary environmentVariables = null)
+ {
+ return BuildInternal (projectOrSolution, target, parameters, environmentVariables);
+ }
+ }
+
+ [Parallelizable (ParallelScope.Children)]
+ public class BuildTests_EmbeddedDSOBuildTests
+ {
+ const string ProjectName = "EmbeddedDSO";
+ const string ProjectAssemblyName = "Xamarin.Android.EmbeddedDSO_Test";
+
+ static readonly string TestProjectRootDirectory;
+ static readonly string TestOutputDir;
+
+ static readonly List produced_binaries = new List {
+ $"{ProjectAssemblyName}.dll",
+ $"{ProjectAssemblyName}-Signed.apk",
+ $"{ProjectAssemblyName}.apk",
+ };
+
+ static readonly List log_files = new List {
+ "process.log",
+ "msbuild.binlog",
+ };
+
+ string testProjectPath;
+
+ static BuildTests_EmbeddedDSOBuildTests ()
+ {
+ TestProjectRootDirectory = Path.GetFullPath (Path.Combine (XABuildPaths.TopDirectory, "tests", "EmbeddedDSOs", "EmbeddedDSO"));
+ TestOutputDir = Path.Combine (XABuildPaths.TestOutputDirectory, "EmbeddedDSO");
+
+ produced_binaries = new List {
+ $"{ProjectAssemblyName}.dll",
+ $"{ProjectAssemblyName}-Signed.apk",
+ $"{ProjectAssemblyName}.apk",
+ };
+ }
+
+ [TestFixtureSetUp]
+ public void BuildProject ()
+ {
+ testProjectPath = PrepareProject (ProjectName);
+ string projectPath = Path.Combine (testProjectPath, $"{ProjectName}.csproj");
+ LocalBuilder builder = GetBuilder ("EmbeddedDSO");
+ bool success = builder.Build (projectPath, "SignAndroidPackage", new [] { "UnitTestsMode=true" });
+
+ Assert.That (success, Is.True, "Should have been built");
+ }
+
+ [Test]
+ public void BinariesExist ()
+ {
+ foreach (string binary in produced_binaries) {
+ string fp = Path.Combine (testProjectPath, "bin", XABuildPaths.Configuration, binary);
+
+ Assert.That (new FileInfo (fp), Does.Exist, $"File {fp} should exist");
+ }
+ }
+
+ [Test]
+ public void DSOPageAlignment ()
+ {
+ Assert.That (new FileInfo (Config.ZipAlignPath), Does.Exist, $"ZipAlign not found at ${Config.ZipAlignPath}");
+
+ string apk = Path.Combine (testProjectPath, "bin", XABuildPaths.Configuration, $"{ProjectAssemblyName}-Signed.apk");
+ Assert.That (new FileInfo (apk), Does.Exist, $"File {apk} should exist");
+ Assert.That (RunCommand (Config.ZipAlignPath, $"-c -v -p 4 {apk}"), Is.True, $"{ProjectAssemblyName}-Signed.apk does not contain page-aligned .so files");
+ }
+
+ [Test]
+ public void EnvironmentFileContents ()
+ {
+ string apk = Path.Combine (testProjectPath, "bin", XABuildPaths.Configuration, $"{ProjectAssemblyName}-Signed.apk");
+ Assert.That (new FileInfo (apk), Does.Exist, $"File {apk} should exist");
+
+ using (ZipArchive zip = ZipArchive.Open (apk, FileMode.Open)) {
+ Assert.That (zip, Is.Not.Null, $"{apk} couldn't be opened as a zip archive");
+ Assert.That (zip.ContainsEntry ("environment"), Is.True, $"`environment` file not found in {apk}");
+
+ ZipEntry entry = zip.FirstOrDefault (e => String.Compare (e.FullName, "environment", StringComparison.Ordinal) == 0);
+ Assert.That (entry, Is.Not.Null, $"Unable to open the `environment` entry from {apk}");
+
+ string environment = null;
+ using (var ms = new MemoryStream ()) {
+ entry.Extract (ms);
+ environment = Encoding.UTF8.GetString (ms.ToArray ());
+ }
+
+ Assert.That (String.IsNullOrEmpty (environment), Is.False, $"Environment file from {apk} must not be empty");
+
+ string[] envLines = environment.Split (new [] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
+ Assert.That (envLines.Length > 0, Is.True, $"Environment file from {apk} must contain at least one non-empty line");
+
+ bool found = false;
+ foreach (string line in envLines) {
+ string[] ev = line.Split ('=');
+ if (ev.Length != 2)
+ continue;
+
+ if (String.Compare ("__XA_DSO_IN_APK", ev [0].Trim (), StringComparison.Ordinal) == 0) {
+ found = true;
+ break;
+ }
+ }
+
+ Assert.That (found, Is.True, $"The `__XA_DSO_IN_APK` variable wasn't found in the environment file from {apk}");
+ }
+ }
+
+ [Test]
+ public void DSOCompressionMode ()
+ {
+ string apk = Path.Combine (testProjectPath, "bin", XABuildPaths.Configuration, $"{ProjectAssemblyName}-Signed.apk");
+ Assert.That (new FileInfo (apk), Does.Exist, $"File {apk} should exist");
+
+ var bad_dsos = new List ();
+ using (ZipArchive zip = ZipArchive.Open (apk, FileMode.Open)) {
+ Assert.That (zip, Is.Not.Null, $"{apk} couldn't be opened as a zip archive");
+
+ foreach (ZipEntry entry in zip) {
+ if (!entry.FullName.EndsWith (".so", StringComparison.Ordinal))
+ continue;
+
+ if (entry.CompressionMethod == CompressionMethod.Store)
+ continue;
+
+ bad_dsos.Add (entry.FullName);
+ }
+ }
+
+ Assert.That (bad_dsos.Count == 0, Is.True, $"Some DSO entries in {apk} are compressed ({BadDsosString ()})");
+
+ string BadDsosString ()
+ {
+ return String.Join ("; ", bad_dsos);
+ }
+ }
+
+ [Test]
+ public void AndroidManifestHasFlag ()
+ {
+ const string AndroidNS = "http://schemas.android.com/apk/res/android";
+
+ string manifest = Path.Combine (testProjectPath, "obj", XABuildPaths.Configuration, "android", "manifest", "AndroidManifest.xml");
+ Assert.That (new FileInfo (manifest), Does.Exist, $"File {manifest} should exist");
+
+ var doc = new XPathDocument (manifest);
+ XPathNavigator nav = doc.CreateNavigator ();
+
+ var manager = new XmlNamespaceManager (nav.NameTable);
+ manager.AddNamespace ("android", AndroidNS);
+
+ XPathNavigator application = nav.SelectSingleNode ("//manifest/application");
+ Assert.That (application, Is.Not.Null, $"Manifest {manifest} does not contain the `application` node");
+
+ string attr = application.GetAttribute ("extractNativeLibs", AndroidNS)?.Trim ();
+ Assert.That (String.IsNullOrEmpty (attr), Is.False, $"Manifest {manifest} `application` node does not contain the `extractNativeLibs` attribute");
+ Assert.That (String.Compare ("false", attr, StringComparison.OrdinalIgnoreCase), Is.EqualTo (0), $"Manifest {manifest} `application` node's `extractNativeLibs` attribute is not set to `false`");
+ }
+
+ bool RunCommand (string command, string arguments)
+ {
+ var psi = new ProcessStartInfo () {
+ FileName = command,
+ Arguments = arguments,
+ UseShellExecute = false,
+ RedirectStandardInput = false,
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ CreateNoWindow = true,
+ WindowStyle = ProcessWindowStyle.Hidden,
+ };
+
+ var stderr_completed = new ManualResetEvent (false);
+ var stdout_completed = new ManualResetEvent (false);
+
+ var p = new Process () {
+ StartInfo = psi,
+ };
+
+ p.ErrorDataReceived += (sender, e) => {
+ if (e.Data == null)
+ stderr_completed.Set ();
+ else
+ Console.WriteLine (e.Data);
+ };
+
+ p.OutputDataReceived += (sender, e) => {
+ if (e.Data == null)
+ stdout_completed.Set ();
+ else
+ Console.WriteLine (e.Data);
+ };
+
+ using (p) {
+ p.StartInfo = psi;
+ p.Start ();
+ p.BeginOutputReadLine ();
+ p.BeginErrorReadLine ();
+
+ bool success = p.WaitForExit (60000);
+
+ // We need to call the parameter-less WaitForExit only if any of the standard
+ // streams have been redirected (see
+ // https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.waitforexit?view=netframework-4.7.2#System_Diagnostics_Process_WaitForExit)
+ //
+ p.WaitForExit ();
+ stderr_completed.WaitOne (TimeSpan.FromSeconds (60));
+ stdout_completed.WaitOne (TimeSpan.FromSeconds (60));
+
+ if (!success || p.ExitCode != 0) {
+ Console.Error.WriteLine ($"Process `{command} {arguments}` exited with value {p.ExitCode}.");
+ return false;
+ }
+
+ return true;
+ }
+ }
+
+ string PrepareProject (string testName)
+ {
+ string tempRoot = Path.Combine (TestOutputDir, $"{testName}.build", XABuildPaths.Configuration);
+ string temporaryProjectPath = Path.Combine (tempRoot, "project");
+
+ var ignore = new HashSet {
+ Path.Combine (TestProjectRootDirectory, "bin"),
+ Path.Combine (TestProjectRootDirectory, "obj"),
+ };
+
+ CopyRecursively (TestProjectRootDirectory, temporaryProjectPath, ignore);
+ return temporaryProjectPath;
+ }
+
+ void CopyRecursively (string fromDir, string toDir, HashSet ignoreDirs)
+ {
+ if (String.IsNullOrEmpty (fromDir))
+ throw new ArgumentException ($"{nameof (fromDir)} is must have a non-empty value");
+ if (String.IsNullOrEmpty (toDir))
+ throw new ArgumentException ($"{nameof (toDir)} is must have a non-empty value");
+
+ if (ignoreDirs.Contains (fromDir))
+ return;
+
+ var fdi = new DirectoryInfo (fromDir);
+ if (!fdi.Exists)
+ throw new InvalidOperationException ($"Source directory '{fromDir}' does not exist");
+
+ if (Directory.Exists (toDir))
+ Directory.Delete (toDir, true);
+
+ foreach (FileSystemInfo fsi in fdi.EnumerateFileSystemInfos ("*", SearchOption.TopDirectoryOnly)) {
+ if (fsi is FileInfo finfo)
+ CopyFile (fsi.FullName, Path.Combine (toDir, finfo.Name));
+ else
+ CopyRecursively (fsi.FullName, Path.Combine (toDir, fsi.Name), ignoreDirs);
+ }
+ }
+
+ void CopyFile (string from, string to)
+ {
+ string dir = Path.GetDirectoryName (to);
+ if (!Directory.Exists (dir))
+ Directory.CreateDirectory (dir);
+ File.Copy (from, to, true);
+ }
+
+ LocalBuilder GetBuilder (string baseLogFileName)
+ {
+ return new LocalBuilder {
+ Verbosity = LoggerVerbosity.Diagnostic,
+ BuildLogFile = $"{baseLogFileName}.log"
+ };
+ }
+ }
+}
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Config.cs.in b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Config.cs.in
new file mode 100644
index 00000000000..a2b23e66cd3
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Config.cs.in
@@ -0,0 +1,10 @@
+using System;
+using System.IO;
+
+namespace EmbeddedDSOUnitTests
+{
+ public static class Config
+ {
+ public static readonly string ZipAlignPath = Path.Combine ("@ANDROID_SDK_DIRECTORY@", "build-tools", "@BUILD_TOOLS_FOLDER@", "zipalign@EXECUTABLE_EXTENSION@");
+ }
+}
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/EmbeddedDSO-UnitTests.csproj b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/EmbeddedDSO-UnitTests.csproj
new file mode 100644
index 00000000000..fef6dacb7f1
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/EmbeddedDSO-UnitTests.csproj
@@ -0,0 +1,73 @@
+
+
+
+ Debug
+ AnyCPU
+ {8B5E63B7-8C18-4BA7-BAAB-A1955B257F5E}
+ Library
+ EmbeddedDSOUnitTests
+ EmbeddedDSOUnitTests
+ v4.7
+
+
+ true
+ full
+ false
+ ..\..\..\bin\TestDebug\EmbeddedDSO
+ DEBUG;
+ prompt
+ 4
+
+
+ true
+ ..\..\..\bin\TestRelease\EmbeddedDSO
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+ ..\..\..\packages\NUnit.3.7.1\lib\net45\nunit.framework.dll
+
+
+
+
+
+
+ XABuildPaths.cs
+
+
+
+
+
+
+
+
+ {2DD1EE75-6D8D-4653-A800-0A24367F7F38}
+ Xamarin.ProjectTools
+
+
+ {E248B2CA-303B-4645-ADDC-9D4459D550FD}
+ libZipSharp
+
+
+
+
+
+ .exe
+
+
+
+
+
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Properties/AssemblyInfo.cs b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Properties/AssemblyInfo.cs
new file mode 100644
index 00000000000..f95d796870d
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/Properties/AssemblyInfo.cs
@@ -0,0 +1,26 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+
+// Information about this assembly is defined by the following attributes.
+// Change them to the values specific to your project.
+
+[assembly: AssemblyTitle ("EmbeddedDSO-UnitTests")]
+[assembly: AssemblyDescription ("")]
+[assembly: AssemblyConfiguration ("")]
+[assembly: AssemblyCompany ("")]
+[assembly: AssemblyProduct ("")]
+[assembly: AssemblyCopyright ("Marek Habersack")]
+[assembly: AssemblyTrademark ("")]
+[assembly: AssemblyCulture ("")]
+
+// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
+// The form "{Major}.{Minor}.*" will automatically update the build and revision,
+// and "{Major}.{Minor}.{Build}.*" will update just the revision.
+
+[assembly: AssemblyVersion ("1.0.*")]
+
+// The following attributes are used to specify the signing key for the assembly,
+// if desired. See the Mono documentation for more information about signing.
+
+//[assembly: AssemblyDelaySign(false)]
+//[assembly: AssemblyKeyFile("")]
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/packages.config b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/packages.config
new file mode 100644
index 00000000000..d9c5868ddbf
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/run.sh b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/run.sh
new file mode 100755
index 00000000000..7f928c5f9f8
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO-UnitTests/run.sh
@@ -0,0 +1,7 @@
+#!/bin/bash -e
+export MONO_ENV_OPTIONS="--debug"
+export USE_MSBUILD=1
+export MSBUILD=msbuild
+msbuild EmbeddedDSO-UnitTests.csproj
+cd ../../../
+exec mono --debug packages/NUnit.ConsoleRunner.3.7.0/tools/nunit3-console.exe bin/TestDebug/EmbeddedDSO/EmbeddedDSOUnitTests.dll
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/Assets/AboutAssets.txt b/tests/EmbeddedDSOs/EmbeddedDSO/Assets/AboutAssets.txt
new file mode 100644
index 00000000000..a9b0638eb1b
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/Assets/AboutAssets.txt
@@ -0,0 +1,19 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories) and given a Build Action of "AndroidAsset".
+
+These files will be deployed with your package and will be accessible using Android's
+AssetManager, like this:
+
+public class ReadAsset : Activity
+{
+ protected override void OnCreate (Bundle bundle)
+ {
+ base.OnCreate (bundle);
+
+ InputStream input = Assets.Open ("my_asset.txt");
+ }
+}
+
+Additionally, some Android functions will automatically load asset files:
+
+Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj
new file mode 100644
index 00000000000..dbcadd5f71e
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.csproj
@@ -0,0 +1,118 @@
+
+
+
+ Debug
+ AnyCPU
+ {056ED976-618F-4A3E-910E-AA25230C2296}
+ {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
+ Library
+ EmbeddedDSO
+ Xamarin.Android.EmbeddedDSO_Test
+ v8.1
+ True
+ Resources\Resource.designer.cs
+ Resource
+ Properties\AndroidManifest.xml
+ Resources
+ Assets
+ true
+ false
+ armeabi-v7a;x86
+
+
+ .so
+
+
+
+ ..\..\..\..\..\..
+
+
+
+ ..\..\..
+
+
+
+
+ true
+ full
+ false
+ $(RelativeRootPath)\bin\TestDebug
+ bin\Debug
+ DEBUG;
+ prompt
+ 4
+ None
+
+
+
+ true
+ pdbonly
+ true
+ $(RelativeRootPath)\bin\TestRelease
+ bin\Release
+ prompt
+ 4
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {3CC4E384-4985-4D93-A34C-73F69A379FA7}
+ TestRunner.Core
+
+
+ {CB2335CB-0050-4020-8A05-E9614EDAA05E}
+ TestRunner.NUnit
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(InstallDependsOnTargets);
+ _GrantPermissions
+
+
+
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.projitems b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.projitems
new file mode 100644
index 00000000000..f8c3205813f
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/EmbeddedDSO.projitems
@@ -0,0 +1,34 @@
+
+
+
+ <_PackageName>Xamarin.Android.EmbeddedDSO_Test
+
+
+
+
+ Xamarin.Android.EmbeddedDSO_Test
+ xamarin.android.embeddeddso_test.NUnitInstrumentation
+ $(MSBuildThisFileDirectory)..\..\..\build-tools\scripts\TimingDefinitions.txt
+ $(MSBuildThisFileDirectory)..\..\..\TestResult-Xamarin.Android.EmbeddedDSO_Test-times.csv
+
+
+
+
+
+ Xamarin.Android.EmbeddedDSO_Test
+ $(OutputPath)TestResult-Xamarin.Android.EmbeddedDSO_Test.nunit.xml
+
+
+
+ Xamarin.Android.EmbeddedDSO_Test
+
+
+
+ Xamarin.Android.EmbeddedDSO_Test
+
+
+
+ Xamarin.Android.EmbeddedDSO_Test
+
+
+
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt b/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt
new file mode 100644
index 00000000000..966b2ab27fb
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/Environment.txt
@@ -0,0 +1,5 @@
+debug.mono.debug=1
+MONO_LOG_LEVEL=debug
+MONO_LOG_MASK=asm
+MONO_XDEBUG=1
+__XA_DSO_IN_APK=1
diff --git a/tests/EmbeddedDSOs/EmbeddedDSO/MainActivity.cs b/tests/EmbeddedDSOs/EmbeddedDSO/MainActivity.cs
new file mode 100644
index 00000000000..74538f5fa87
--- /dev/null
+++ b/tests/EmbeddedDSOs/EmbeddedDSO/MainActivity.cs
@@ -0,0 +1,25 @@
+using Android.App;
+using Android.Widget;
+using Android.OS;
+
+namespace EmbeddedDSO {
+ [Activity (Label = "EmbeddedDSO", MainLauncher = true, Icon = "@mipmap/icon")]
+ public class MainActivity : Activity {
+ int count = 1;
+
+ protected override void OnCreate (Bundle savedInstanceState)
+ {
+ base.OnCreate (savedInstanceState);
+
+ // Set our view from the "main" layout resource
+ SetContentView (Resource.Layout.Main);
+
+ // Get our button from the layout resource,
+ // and attach an event to it
+ Button button = FindViewById