Compile BundledSQLiteDriver's JNI for Windows

Adds MINGW_X64 as a compilation target for sqlite-bundled. Fixes androidx Gradle logic to find Windows's JNI and refactor the location of the native artifacts to the '/natives/<platform>' folder in the jar (instead of top-level platform folders). Finally, fix a bit NativeLibraryLoader to support Window's '.dll' files.

Bug: 335521661
Test: Validated sqlite-bundled usage in sample JVM app in a Windows host.
Change-Id: I03c10d3437b5d16bcb42520b9f5d0f3807718b23
diff --git a/sqlite/sqlite-bundled/build.gradle b/sqlite/sqlite-bundled/build.gradle
index 8df8903..1f3d1d3 100644
--- a/sqlite/sqlite-bundled/build.gradle
+++ b/sqlite/sqlite-bundled/build.gradle
@@ -106,6 +106,7 @@
             KonanTarget.ANDROID_X86,
             KonanTarget.MACOS_ARM64,
             KonanTarget.MACOS_X64,
+            KonanTarget.MINGW_X64,
             KonanTarget.LINUX_X64,
     ].collect { it.INSTANCE } // Use INSTANCE to get object class instance from kotlin
 
@@ -144,7 +145,7 @@
     )
 
     // Define C++ compilation of JNI
-    def jvmArtJniImplementation = createNativeCompilation("jvmArtJniImplementation") {
+    def jvmArtJniImplementation = createNativeCompilation("sqliteJni") {
         configureEachTarget { nativeCompilation ->
             // add JNI headers as sources
             nativeCompilation.addJniHeaders()
diff --git a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
index 4b0ec4b..22b981a 100644
--- a/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
+++ b/sqlite/sqlite-bundled/src/androidJvmCommonMain/kotlin/androidx/sqlite/driver/bundled/BundledSQLiteDriver.androidJvmCommon.kt
@@ -33,7 +33,7 @@
 
     private companion object {
         init {
-            NativeLibraryLoader.loadLibrary("jvmArtJniImplementation")
+            NativeLibraryLoader.loadLibrary("sqliteJni")
         }
     }
 }
diff --git a/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt b/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
index c54b9a6..9edb7a3 100644
--- a/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
+++ b/sqlite/sqlite-bundled/src/androidMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.android.kt
@@ -16,8 +16,11 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
-    actual fun loadLibrary(name: String) {
+    actual fun loadLibrary(name: String): Unit = synchronized(this) {
         System.loadLibrary(name)
     }
 }
diff --git a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
index 6930b5f..5482ca3 100644
--- a/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
+++ b/sqlite/sqlite-bundled/src/commonMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.kt
@@ -16,6 +16,9 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal expect object NativeLibraryLoader {
     /**
      * Loads the given native library via JNI (if running on JVM or Android).
diff --git a/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt b/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
index cae82a8..79eb81a 100644
--- a/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
+++ b/sqlite/sqlite-bundled/src/jvmMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.jvm.kt
@@ -20,16 +20,40 @@
 import java.nio.file.StandardCopyOption
 import java.util.Locale
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
     // TODO(b/304281116): Generate this via Gradle so it is consistent.
-    actual fun loadLibrary(name: String) {
+    actual fun loadLibrary(name: String): Unit = synchronized(this) {
         try {
             System.loadLibrary(name)
             return
-        } catch (error: Throwable) {
-            // looks like we are not on Android, continue
+        } catch (error: UnsatisfiedLinkError) {
+            // Likely not on Android, continue...
         }
-        // TODO(b/304281116): Temporary loading implementation
+        // TODO(b/304281116): Improve loading implementation
+        val libResourceName = getResourceName(name)
+        val libTempCopy = Files.createTempFile("androidx_$name", null)
+            .apply { toFile().deleteOnExit() }
+        NativeLibraryLoader::class.java.classLoader!!.getResourceAsStream(
+            libResourceName
+        ).use { resourceStream ->
+            checkNotNull(resourceStream) {
+                "Cannot find a suitable SQLite binary for ${System.getProperty("os.name")} | " +
+                    "${System.getProperty("os.arch")}. Please file a bug at " +
+                    "https://issuetracker.google.com/issues/new?component=460784"
+            }
+            Files.copy(resourceStream, libTempCopy, StandardCopyOption.REPLACE_EXISTING)
+        }
+        @Suppress("UnsafeDynamicallyLoadedCode")
+        System.load(libTempCopy.toFile().canonicalPath)
+    }
+
+    /**
+     * Gets the JAR's resource file path to the native library.
+     */
+    private fun getResourceName(name: String): String {
         val osName =
             System.getProperty("os.name")?.lowercase(Locale.US) ?: error("Cannot read osName")
         val osArch =
@@ -37,6 +61,7 @@
         val osPrefix = when {
             osName.contains("linux") -> "linux"
             osName.contains("mac") || osName.contains("osx") -> "osx"
+            osName.contains("windows") -> "windows"
             else -> error("Unsupported operating system: $osName")
         }
         val archSuffix = when {
@@ -50,19 +75,12 @@
             else -> error("Unsupported architecture: $osArch")
         }
         val resourceFolder = "${osPrefix}_$archSuffix"
-        val ext = if (osPrefix == "linux") { "so" } else { "dylib" }
-        val resourceName = "$resourceFolder/lib$name.$ext"
-        val nativeLibCopy = Files.createTempFile("androidx_$name", null)
-        nativeLibCopy.toFile().deleteOnExit()
-        NativeLibraryLoader::class.java.classLoader!!.getResourceAsStream(
-            resourceName
-        ).use { resourceStream ->
-            checkNotNull(resourceStream) {
-                "Cannot find resource $resourceName"
-            }
-            Files.copy(resourceStream, nativeLibCopy, StandardCopyOption.REPLACE_EXISTING)
+        val extension = when (osPrefix) {
+            "linux" -> "so"
+            "osx" -> "dylib"
+            "windows" -> "dll"
+            else -> error("Unsupported operating system: $osName")
         }
-        @Suppress("UnsafeDynamicallyLoadedCode")
-        System.load(nativeLibCopy.toFile().canonicalPath)
+        return "natives/$resourceFolder/lib$name.$extension"
     }
 }
diff --git a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
index 1ae2bf8..c1cf339 100644
--- a/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
+++ b/sqlite/sqlite-bundled/src/nativeMain/kotlin/androidx/sqlite/driver/bundled/NativeLibraryLoader.nativeCommon.kt
@@ -16,6 +16,9 @@
 
 package androidx.sqlite.driver.bundled
 
+/**
+ * Helper class to load native libraries based on the host platform.
+ */
 internal actual object NativeLibraryLoader {
     actual fun loadLibrary(name: String) {
         // no-op, we are already in native code