Search code examples
androidcflutterdart-ffi

How to create a shared library in android and use it in flutter?


TL;DR

I have this C function in meaning_of_life.c:

#define FFI_PLUGIN_EXPORT

FFI_PLUGIN_EXPORT int getMeaningOfLife(){
    return 42;
}

I tried to compile it to a shared library (.so) using alot of ways but I keep getting errors when I try to use it on android like incompatible target and file in wrong format. Can you tell me the right way to do it?

What I tried:

  1. create the above C file
  2. create a flutter_ffi plugin that will use it like this(this is somewhat irrelevant here):
add_library(meaningoflife SHARED IMPORTED )
set_target_properties(meaningoflife
        PROPERTIES
        IMPORTED_LOCATION
        /home/haidar/dev_haidar/c++/meaning_of_life/Src/libLife.so
        )

target_link_libraries(my_plugin meaningoflife)
  1. Read alot, alot... of docs, and searched alot here on SO but most related questions and answers are very old and use old ways ( they don't use cmake )
  2. Create a flutter app that will use the plugin
  3. Create a .so shared library from the plugin, first I tried the method on this page (using the NDK compilers and toolchain):
cd /home/haidar/dev_haidar/c++/meaning_of_life/Src
comp=/home/haidar/Android/Sdk/ndk/25.1.8937393/toolchains/llvm/prebuilt/linux-x86_64/bin
$comp/aarch64-linux-android26-clang  -fPIC meaning_of_life.c -o libLife.so -shared // also tried with other compilers as armv7a-linux-androideabi26-clang

The library is generated, but when I use it, I get one of the errors mentioned above (depending on the compiler used to create it), for example, here is some part of the error:

[        ] FAILURE: Build failed with an exception.
[        ] * What went wrong:
[        ] Execution failed for task ':image_magick_ffi:buildCMakeDebug[armeabi-v7a]'.
[        ] > Build command failed.
[        ]   Error while executing process /home/haidar/Android/Sdk/cmake/3.18.1/bin/ninja with arguments {-C /home/haidar/dev_haidar/flutter/image_magick_ffi/android/.cxx/Debug/b5x10294/armeabi-v7a image_magick_ffi}
[        ]   ninja: Entering directory `/home/haidar/dev_haidar/flutter/image_magick_ffi/android/.cxx/Debug/b5x10294/armeabi-v7a'
[        ]   [1/1] Linking C shared library /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so
[        ]   FAILED: /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so 
[        ]   : && /home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=armv7-none-linux-androideabi16 --gcc-toolchain=/home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64 --sysroot=/home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security  -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libimage_magick_ffi.so -o /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so CMakeFiles/image_magick_ffi.dir/image_magick_ffi.c.o  /home/haidar/dev_haidar/c++/meaning_of_life/Src/libLife.so  -latomic -lm && :
[        ]   /home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: /home/haidar/dev_haidar/c++/meaning_of_life/Src/libLife.so: incompatible target
[        ]   clang: error: linker command failed with exit code 1 (use -v to see invocation)
[        ]   ninja: build stopped: subcommand failed.
[        ] * Try:
[        ] > Run with --debug option to get more log output.
[        ] > Run with --scan to get full insights.
[        ] * Exception is:
[        ] org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':image_magick_ffi:buildCMakeDebug[armeabi-v7a]'.
[        ]  at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.lambda$executeIfValid$1(ExecuteActionsTaskExecuter.java:147)
[        ]  at org.gradle.internal.Try$Failure.ifSuccessfulOrElse(Try.java:282)
[        ]  at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:145)
[        ]  at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:133)
[        ]  at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
[        ]  at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
[        ]  at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
[        ]  at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
[        ]  at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
[        ]  at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
[        ]  at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:333)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:320)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:313)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:299)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:143)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:227)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:218)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
[        ]  at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
[        ]  at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
[        ]  at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[        ]  at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[        ]  at java.base/java.lang.Thread.run(Thread.java:829)
[        ] Caused by: org.gradle.internal.UncheckedException: Build command failed.
[        ] Error while executing process /home/haidar/Android/Sdk/cmake/3.18.1/bin/ninja with arguments {-C /home/haidar/dev_haidar/flutter/image_magick_ffi/android/.cxx/Debug/b5x10294/armeabi-v7a image_magick_ffi}
[        ] ninja: Entering directory `/home/haidar/dev_haidar/flutter/image_magick_ffi/android/.cxx/Debug/b5x10294/armeabi-v7a'
[        ] [1/1] Linking C shared library /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so
[        ] FAILED: /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so 
[        ] : && /home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/bin/clang --target=armv7-none-linux-androideabi16 --gcc-toolchain=/home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64 --sysroot=/home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/sysroot -fPIC -g -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a -mthumb -Wformat -Werror=format-security  -O0 -fno-limit-debug-info  -Wl,--exclude-libs,libgcc.a -Wl,--exclude-libs,libgcc_real.a -Wl,--exclude-libs,libatomic.a -static-libstdc++ -Wl,--build-id -Wl,--fatal-warnings -Wl,--exclude-libs,libunwind.a -Wl,--no-undefined -Qunused-arguments -shared -Wl,-soname,libimage_magick_ffi.so -o /home/haidar/dev_haidar/flutter/magick_app/build/image_magick_ffi/intermediates/cxx/Debug/b5x10294/obj/armeabi-v7a/libimage_magick_ffi.so CMakeFiles/image_magick_ffi.dir/image_magick_ffi.c.o  /home/haidar/dev_haidar/c++/meaning_of_life/Src/libLife.so  -latomic -lm && :
[        ] /home/haidar/Android/Sdk/ndk/21.1.6352462/toolchains/llvm/prebuilt/linux-x86_64/lib/gcc/arm-linux-androideabi/4.9.x/../../../../arm-linux-androideabi/bin/ld: error: /home/haidar/dev_haidar/c++/meaning_of_life/Src/libLife.so: incompatible target
[        ] clang: error: linker command failed with exit code 1 (use -v to see invocation)
[        ] ninja: build stopped: subcommand failed.
[        ]  at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:68)
[        ]  at org.gradle.internal.UncheckedException.throwAsUncheckedException(UncheckedException.java:41)
[        ]  at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:107)
[        ]  at org.gradle.api.internal.project.taskfactory.StandardTaskAction.doExecute(StandardTaskAction.java:58)
[        ]  at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:51)
[        ]  at org.gradle.api.internal.project.taskfactory.StandardTaskAction.execute(StandardTaskAction.java:29)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution$3.run(TaskExecution.java:242)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:29)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$1.execute(DefaultBuildOperationRunner.java:26)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.run(DefaultBuildOperationRunner.java:47)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationExecutor.run(DefaultBuildOperationExecutor.java:68)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution.executeAction(TaskExecution.java:227)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution.executeActions(TaskExecution.java:210)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution.executeWithPreviousOutputFiles(TaskExecution.java:193)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution.execute(TaskExecution.java:171)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep.executeInternal(ExecuteStep.java:89)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep.access$000(ExecuteStep.java:40)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:53)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep$1.call(ExecuteStep.java:50)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:50)
[        ]  at org.gradle.internal.execution.steps.ExecuteStep.execute(ExecuteStep.java:40)
[        ]  at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:68)
[        ]  at org.gradle.internal.execution.steps.RemovePreviousOutputsStep.execute(RemovePreviousOutputsStep.java:38)
[        ]  at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:48)
[        ]  at org.gradle.internal.execution.steps.ResolveInputChangesStep.execute(ResolveInputChangesStep.java:36)
[        ]  at org.gradle.internal.execution.steps.CancelExecutionStep.execute(CancelExecutionStep.java:41)
[        ]  at org.gradle.internal.execution.steps.TimeoutStep.executeWithoutTimeout(TimeoutStep.java:74)
[        ]  at org.gradle.internal.execution.steps.TimeoutStep.execute(TimeoutStep.java:55)
[        ]  at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:51)
[        ]  at org.gradle.internal.execution.steps.CreateOutputsStep.execute(CreateOutputsStep.java:29)
[        ]  at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:61)
[        ]  at org.gradle.internal.execution.steps.CaptureStateAfterExecutionStep.execute(CaptureStateAfterExecutionStep.java:42)
[        ]  at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:60)
[        ]  at org.gradle.internal.execution.steps.BroadcastChangingOutputsStep.execute(BroadcastChangingOutputsStep.java:27)
[        ]  at org.gradle.internal.execution.steps.BuildCacheStep.executeWithoutCache(BuildCacheStep.java:180)
[        ]  at org.gradle.internal.execution.steps.BuildCacheStep.lambda$execute$1(BuildCacheStep.java:75)
[        ]  at org.gradle.internal.Either$Right.fold(Either.java:175)
[        ]  at org.gradle.internal.execution.caching.CachingState.fold(CachingState.java:59)
[        ]  at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:73)
[        ]  at org.gradle.internal.execution.steps.BuildCacheStep.execute(BuildCacheStep.java:48)
[        ]  at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:36)
[        ]  at org.gradle.internal.execution.steps.StoreExecutionStateStep.execute(StoreExecutionStateStep.java:25)
[        ]  at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:36)
[        ]  at org.gradle.internal.execution.steps.RecordOutputsStep.execute(RecordOutputsStep.java:22)
[        ]  at org.gradle.internal.execution.steps.SkipUpToDateStep.executeBecause(SkipUpToDateStep.java:110)
[        ]  at org.gradle.internal.execution.steps.SkipUpToDateStep.lambda$execute$2(SkipUpToDateStep.java:56)
[        ]  at java.base/java.util.Optional.orElseGet(Optional.java:369)
[        ]  at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:56)
[        ]  at org.gradle.internal.execution.steps.SkipUpToDateStep.execute(SkipUpToDateStep.java:38)
[        ]  at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:73)
[        ]  at org.gradle.internal.execution.steps.ResolveChangesStep.execute(ResolveChangesStep.java:44)
[        ]  at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:37)
[        ]  at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsFinishedStep.execute(MarkSnapshottingInputsFinishedStep.java:27)
[        ]  at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:89)
[        ]  at org.gradle.internal.execution.steps.ResolveCachingStateStep.execute(ResolveCachingStateStep.java:50)
[        ]  at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:114)
[        ]  at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:57)
[        ]  at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:76)
[        ]  at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)
[        ]  at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:249)
[        ]  at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:86)
[        ]  at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:54)
[        ]  at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32)
[        ]  at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21)
[        ]  at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
[        ]  at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:43)
[        ]  at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)
[        ]  at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)
[        ]  at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:287)
[        ]  at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)
[        ]  at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
[        ]  at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
[        ]  at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
[        ]  at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:44)
[        ]  at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)
[        ]  at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)
[        ]  at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:144)
[        ]  at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:133)
[        ]  at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)
[        ]  at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)
[        ]  at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)
[        ]  at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)
[        ]  at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)
[        ]  at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
[        ]  at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
[        ]  at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)
[        ]  at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:333)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:320)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:313)
[        ]  at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:299)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:143)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:227)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:218)
[        ]  at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)
[        ]  at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
[        ]  at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
[        ]  at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
[        ]  at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
[        ]  at java.base/java.lang.Thread.run(Thread.java:829)
[        ] Caused by: Build command failed.
...
  1. Create a .so shared library from the plugin, This time I tried the method on the official android docs page, I used this command which generated a json file with another command to compile the C file above, but I don't understand how to create the shared library in this case (the template of the command is from the same page):
cmake -H/home/haidar/dev_haidar/c++/meaning_of_life -DCMAKE_FIND_ROOT_PATH=/home/haidar/dev_haidar/c++/meaning_of_life -DCMAKE_BUILD_TYPE=Debug -DCMAKE_TOOLCHAIN_FILE=/home/haidar/Android/Sdk/ndk/25.1.8937393/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a -DANDROID_NDK=/home/haidar/Android/Sdk/ndk/25.1.8937393 -DANDROID_PLATFORM=android-23 -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_ANDROID_NDK=/home/haidar/Android/Sdk/ndk/25.1.8937393 -DCMAKE_EXPORT_COMPILE_COMMANDS=ON -DCMAKE_LIBRARY_OUTPUT_DIRECTORY=/home/haidar/dev_haidar/c++/meaning_of_life -DCMAKE_RUNTIME_OUTPUT_DIRECTORY=/home/haidar/dev_haidar/c++/meaning_of_life -DCMAKE_MAKE_PROGRAM=/home/haidar/Android/Sdk/cmake/3.22.1/bin/ninja -DCMAKE_SYSTEM_NAME=Android -DCMAKE_SYSTEM_VERSION=26 -B/home/haidar/dev_haidar/c++/meaning_of_life -GNinja

Note: The device I tried to run the flutter app (that uses the plugin, that uses the .so library) on is a Huawei p10 lite, which has a CPU that supports arm63-v8a, armeabi-v7a, armeabi.

Listing the steps to solve this or tips would be much appreciated. Thanks.

Update

I found this shell script on github (which is very similar to what I did in step 6 above), I tweaked it to fit my paths and it successfully generated the .so files for each architecture. I created a pure android app and called it from the app and everything worked fine.

add_library(getnumber SHARED IMPORTED)
set_target_properties(getnumber PROPERTIES IMPORTED_LOCATION
        /home/haidar/dev_haidar/c++/getnumber/Src/prebuilt/release/arm64-v8a/libgetnumber.so
        )
target_link_libraries(imagemagicknativeapp getnumber)

and everything worked fine.

When I depended on it in the flutter plugin in the exact same way

add_library(getnumber SHARED IMPORTED )
set_target_properties(getnumber
        PROPERTIES
        IMPORTED_LOCATION
        /home/haidar/dev_haidar/flutter/image_magick_ffi/src/Dependencies/ImageMagick/lib/android/arm64-v8a/libgetnumber.so
        )

target_link_libraries(image_magick_ffi getnumber)

I still get an error

ld: error: /home/haidar/dev_haidar/flutter/image_magick_ffi/src/Dependencies/ImageMagick/lib/android/arm64-v8a/libgetnumber.so is incompatible with armelf_linux_eabi

I know the problem is related to the compilation/linking target being different than the actual target (my phone), but how can I tell flutter to change this behavior?


Solution

  • Thanks to Piero5W11 from FlutterDev on discord, I was able to solve this particular problem.

    The solution is to remove the part related to adding the library from CMake (don't add the library from CMake, we will add it from somewhere else), so remove this part:

    add_library(getnumber SHARED IMPORTED)
    set_target_properties(getnumber PROPERTIES IMPORTED_LOCATION
            /home/haidar/dev_haidar/c++/getnumber/Src/prebuilt/release/arm64-v8a/libgetnumber.so
            )
    target_link_libraries(imagemagicknativeapp getnumber)
    

    from CMakeLists.txt, and instead, copy the shared library to {PLUGIN_PROJECT_ROOT}/android/src/main/jniLibs/{ABI_VERSION}/libgetnumber.so (replace the wildcards)

    Just do this and do zero other configuration in the CMakeLists or gradle files.

    As for the dart side, this library will be bundled with the app as any other library so you can load it by its name getnumber using the dart dynamic loader, and then use it to lookup symbols in it.

    This is how my plugin's project structure looks like now:

    enter image description here

    and you can how see the generated apk contains the library just bcz it was put in jniLibs:

    enter image description here

    Note: I generated libgetnumber using the script from the link in the question, it is perfect for creating ABIs for all targets, just tweak it to fit your paths.

    Update: Even better, You can use CMake:

    To add the shared libraries through CMakeLists.txt of the flutter ffi plugin:

    • first remove the shared libraries from the jniLibs folder (source)
    • Put the libraries anywhere you want inside your plugin project, for me, I put them in the same directory as CMakeLists.txt, in ${CMAKE_CURRENT_SOURCE_DIR}/Dependencies/ImageMagick/lib/android/${ANDROID_ABI}/libc++_shared.so (put your own paths instead)
    • add this to android/build.gradle in the plugin:
            ndk {
                abiFilters "arm64-v8a" // put only the architectures that your lib is targeting
            }
    

    note: if you have .so versions for all ABIs, then you will not need this step.

    • Now you can add your .so files as libraries in CMake and link them to your flutter plugin library:

    CMakeLists.txt:

    project(my_ffi_library VERSION 0.0.1 LANGUAGES C)
    
    add_library(my_ffi_library SHARED
            "myclass.c"
            )
    ...
    # Add your library
    add_library(my-lib SHARED IMPORTED)
    set_target_properties(
            my-lib
            PROPERTIES IMPORTED_LOCATION
            path/to/your/lib.so
    )
    # Link your library to the flutter plugin library
    target_link_libraries(my_ffi_library my-lib)
    
    • And done, now you can use the implementations from the prebuilt shared library.