gnustep / tools-android

Objective-C on Android with Foundation, CoreFoundation, and libdispatch.
MIT License
48 stars 12 forks source link

Missing: How to configure Android project for GNUstep use #7

Closed mehlkelm closed 4 years ago

mehlkelm commented 4 years ago

As an iOS developer with little Android experience (which makes GNUstep especially tempting) I had no problem going from the toolchain setup instructions to the sample project. However, configuring your own new or existing Android project for GNUstep is not yet documented.

Looking at the linked android-examples I tried the following (maybe this can be used as a draft for a new section at the end of the existing README, once it works):

  1. New project in Android Studio a. Basic Fragment with View Model b. Enable Kotlin

  2. Create folder app/src/main/cpp

  3. Add CMakeLists.txt () to cpp folder

  4. Edit CMakeLists.txt to include your Objective-C implementation (.m) files in OBJECTIVEC_SRCS

  5. Edit app/build.gradle. Add:

    externalNativeBuild { cmake { cppFlags "" } } ndk { // ABIs supported by GNUstep toolchain abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64" }

  6. Edit MainActivity class: Add

    init { System.loadLibrary(„native-lib“) }

So far this fails at System.loadLibrary("native-lib") with following error:

2020-02-12 12:32:19.933 985-985/zoziapps.ch.sousvidecelsius E/AndroidRuntime: FATAL EXCEPTION: main Process: zoziapps.ch.sousvidecelsius, PID: 985 java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "_ZNSt6ndk111call_onceERVmPvPFvS2_E" referenced by "/data/app/zoziapps.ch.sousvidecelsius-qgnwdnfRmeXSFCUffVd7bw==/lib/x86/libgnustep-base.so"... at java.lang.Runtime.loadLibrary0(Runtime.java:1016) at java.lang.System.loadLibrary(System.java:1669) at zoziapps.ch.sousvidecelsius.MainActivity.(MainActivity.kt:15) at java.lang.Class.newInstance(Native Method) at android.app.AppComponentFactory.instantiateActivity(AppComponentFactory.java:69) at androidx.core.app.CoreComponentFactory.instantiateActivity(CoreComponentFactory.java:43) at android.app.Instrumentation.newActivity(Instrumentation.java:1215) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2831) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048) at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loop(Looper.java:193) at android.app.ActivityThread.main(ActivityThread.java:6669) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

triplef commented 4 years ago

Thanks for your feedback! One step that’s missing is setting the NDK path in the app settings > SDK Location. If you’re on a Mac you should set it to the custom NDK that is installed by the toolchain in: ~/Library/Android/android-ndk-r20-clang-r353983c1

Can you check if that helps with the linker error?

mehlkelm commented 4 years ago

I can't find "App settings". Do you mean "Android NDK location" in File > Project Structure… > SDK Location? Setting it there doesn't help.

triplef commented 4 years ago

Yeah that’s the right setting.

I found that for me, if I understand it correctly _ZNSt6__ndk111__call_onceERVmPvPFvS2_E is defined in (not referenced by) libgnustep-base.so. Can you run the following and see if you get the same result?

$ nm ~/Library/Android/GNUstep/x86/lib/libgnustep-base.so | grep _ZNSt6__ndk111__call_onceERVmPvPFvS2_E
00758380 T _ZNSt6__ndk111__call_onceERVmPvPFvS2_E
triplef commented 4 years ago

Also, does the hello-objectivec example work for you?

mehlkelm commented 4 years ago

The hello-objectivec example works perfectly, and that without any "Android NDK location" setting!

My result:

$ nm ~/Library/Android/GNUstep/x86/lib/libgnustep-base.so | grep _ZNSt6__ndk111__call_onceERVmPvPFvS2_E
         U _ZNSt6__ndk111__call_onceERVmPvPFvS2_E
triplef commented 4 years ago

Hmm interesting. Looks like your libgnustep-base.so (unlike mine) doesn’t define the function. Could you attach your (zipped) ~/Library/Android/GNUstep/build.log?

That being said, question is why the example still works... I noticed in step 6 above you wrote that you call System.loadLibrary() in an init method. Can you try putting it in a static initializer as in the example?

static {
    System.loadLibrary("native-lib");
}
mehlkelm commented 4 years ago

I'm on Kotlin, so static won't work here. I tried to do it according to the "Native C++" Android Studio template (like below) which also doesn't work:

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        initializeGNUstep(this);

        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val navView: BottomNavigationView = findViewById(R.id.nav_view)

        val navController = findNavController(R.id.nav_host_fragment)
        // Passing each menu ID as a set of Ids because each
        // menu should be considered as top level destinations.
        val appBarConfiguration = AppBarConfiguration(
            setOf(
                R.id.navigation_home, R.id.navigation_dashboard, R.id.navigation_notifications, R.id.navigation_temperatures
            )
        )
        setupActionBarWithNavController(navController, appBarConfiguration)
        navView.setupWithNavController(navController)
    }

    external fun initializeGNUstep(context: Context?)

    companion object {

        // Used to load the 'native-lib' library on application startup.
        init {
            System.loadLibrary("native-lib")
        }
    }
}
triplef commented 4 years ago

I’ve never used Kotlin – could you try it with a Java project just to narrow it down further?

mehlkelm commented 4 years ago

Kotlin can't be the issue: a) I get the same problem with a brand new Java Project (no Kotlin, no AndroidX) b) I can convert your hello-objectivec and its MainActivity to Kotlin while still being able to load native-lib

triplef commented 4 years ago

I see. I have no idea why the example project works and a new project doesn’t, and I cannot reproduce the issue. But the core issue seems to be that your GNUstep library file doesn’t define some symbols that mine does. If you could send me your build.log I can see if I can spot some differences in your build process that might be specific to your machine/setup.

mehlkelm commented 4 years ago

The good news: I got it working! The bad news: I still have to find out how… Will analyze a bit more and then post step by step how it works reproducibly for me.

mehlkelm commented 4 years ago

This pull request would document what worked for me: https://github.com/gnustep/tools-android/pull/8.

triplef commented 4 years ago

Thanks for the PR! Can you tell what the missing bit was to get it working in the end?

mehlkelm commented 4 years ago

In the end I tried so much stuff that I'm not sure what exactly worked but I suspect step 6: "Link to C++" was missing.

triplef commented 4 years ago

I fixed the underlying issue causing the linker error with _ZNSt6__ndk111__call_onceERVmPvPFvS2_E: the toolchain libraries were not correctly linking against the shared C++ runtime library.

Please try rebuilding the toolchain with the latest changes, and also update your Gradle setup to use the shared C++ runtime library like this: https://github.com/gnustep/android-examples/commit/d3f5aba353718cd8c6a50ce56c1083b79993f8c7

Regarding instructions to integrate this in your own project I hope to be able to create a standalone CMake toolchain file that can simply be included in Android Studio (that’s why I haven’t merged your PR yet).