vectorgrp / sil-kit

Vector SIL Kit – Open-Source Library for Connecting Software-in-the-Loop Environments
https://vectorgrp.github.io/sil-kit-docs
MIT License
106 stars 32 forks source link

Android build #142

Open kocho1984 opened 1 week ago

kocho1984 commented 1 week ago

Hello,

Does SIL Kit support build for Android? I mean if it is possible to build SIL Kit as a static or shared lib and use it by the application written in C++ in Android?

If not, do you have maybe any hints how could this be achieved?

MariusBgm commented 1 week ago

Hi @kocho1984, we currently do not officially support Android - that is, we haven't tested it or integrated it into our CI.

However, there is no reason why SIL Kit shouldn't run on Android with little to no changes (it is really portable). The CMake build will probably work the same as on any Linux. Android is using LLVM's standard C++ library, this should support all features that we use.

Have you already tried building SIL Kit using Android NDK? can you shed some more light on the use cases involving Android?

kocho1984 commented 1 week ago

@MariusBgm, I'll try to build it with Android NDK. Thank you for your hint.

Regarding use case in Android: basically it is to test communication (send/receive) between two apps using SIL Kit.

kocho1984 commented 1 week ago

@MariusBgm, I was able to build libSilKit.a with NDK. Due to the fact that SIL Kit uses exceptions, RTTI (for dynamic_cast), and has some cases where destructor is not virtual even if other methods are virtual, I had to enable following build options, as default compiler options for Android don't allow e.g. using exceptions. -fexceptions, -frtti, -Wno-error=non-virtual-dtor

However, when I'm trying to build a lib which is trying to use functionality from libSilKit.a, I get tons of linker errors which seem to be related to STL, e.g. shared_ptr, basic_string, etc.

Some examples:

ld.lld: error: undefined symbol: std::__ndk1::__shared_weak_count::__release_weak()
referenced by shared_ptr.h:184 (./workspace/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:184)
CapiParticipant.cpp.o:(SilKit_Participant_Create) in archive ./libSilKit.a

ld.lld: error: undefined symbol: std::__ndk1::basic_string<char, std::__ndk1::char_traits<char>, std::__ndk1::allocator<char>>::rfind(char, unsigned long) const
referenced by Filesystem.cpp:149 (./workspace/sil-kit-android/SilKit/source/util/Filesystem.cpp:149)
               Filesystem.cpp.o:(SilKit::Filesystem::parent_path(SilKit::Filesystem::path const&)) in archive ./libSilKit.a

You mentioned that you don't have an official Android build in your CI, but have you tried building it locally by any chance, or encountered similar linker errors in the past when working on Android builds?

MariusBgm commented 1 week ago

@kocho1984 can you share the complete linker command line that cmake used for linking?

I suppose the libc++.so is not linked properly, see https://developer.android.com/ndk/guides/cpp-support You may need to set the CMAKE_ANDROID_STL_STYLE to c++_shared , refer to https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-for-android-with-the-ndk

kocho1984 commented 1 week ago

@MariusBgm , If you are referring to the linker command line used for building SILKit lib

  1. I used following cmake command and then start build with make cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=android-34 -DANDROID_ABI=arm64-v8a -DANDROID_NDK=$ANDROID_NDK_HOME -DSILKIT_BUILD_DEMOS=OFF -DSILKIT_BUILD_STATIC=ON -DSILKIT_BUILD_TESTS=OFF -DCMAKE_ANDROID_STL_TYPE=c++_shared --trace ..

  2. This is an output from the build. Command which starts linking libSilKit.a is in line 991 in silkit_a_make_output.txt

[ 96%] Linking CXX static library ../../Release/libSilKit.a

Or you ask for the linker command used to build a lib which uses libSilKit.a?

MariusBgm commented 1 week ago

@kocho1984 thanks for the feedback. I can see that SilKit builds cleanly. However, you are using the static library type. Have you tried using the shared library build of silkit?

On your application side, where the linking fails, you might lack linking against libc++_shared.so. In the silkit_a_make_output.txt, I can see that the silkit executables are linked using the -static-libstdc++. You can try adding that flag if you insist on linking against a static SilKit.a

kocho1984 commented 1 week ago

@MariusBgm, thank you for quick response.

Static SilKit.a is intentional. Regarding -static-libstdc++ you mean that I can try to build static SilKit.a with that flag? Is my understanding correct?

MariusBgm commented 1 week ago

@kocho1984 no, I mean your test application that links against the libSilKit.a. You mentioned you are trying to build a lib which contains the static libSilKit.a (which is just an archive of object files):

However, when I'm trying to build a lib which is trying to use functionality from libSilKit.a, I get tons of linker errors which seem to be related to STL, e.g. shared_ptr, basic_string, etc.

you need to link your own library ( or test application for that matter) using --whole-archive and properly link the application where you use your own library against the stdc++ library.

kocho1984 commented 1 week ago

Have you tried using the shared library build of silkit?

@MariusBgm I would like to use it as a static lib, but just in case, I tried to build shared SilKit lib. The build fails unfortunately. I used following cmake command. cmake -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_PLATFORM=android-34 -DANDROID_ABI=arm64-v8a -DANDROID_NDK=$ANDROID_NDK_HOME -DSILKIT_BUILD_DEMOS=OFF -DSILKIT_BUILD_STATIC=OFF -DSILKIT_BUILD_TESTS=OFF ..

There are following linker errors. image silkit_so_ndk_build_failed.txt

MariusBgm commented 1 week ago

@kocho1984 I reproduced your issue locally. It's caused by our use of the linker script silkit.map. Can you try the following patch? (or just remove the linker script from the cmake)

diff --git a/SilKit/source/CMakeLists.txt b/SilKit/source/CMakeLists.txt
index 322639c43..ea85d8e9c 100644
--- a/SilKit/source/CMakeLists.txt
+++ b/SilKit/source/CMakeLists.txt
@@ -243,7 +243,7 @@ if (MSVC)
             PDB_OUTPUT_DIRECTORY_DEBUG ${SILKIT_SYMBOLS_DIR}/Debug
             LINK_FLAGS "/DEBUG" #make sure the resulting .dll has a .pdb file
     )
-elseif(UNIX AND NOT APPLE)
+elseif(UNIX AND NOT APPLE AND NOT (CMAKE_SYSTEM_NAME MATCHES Android))
kocho1984 commented 4 days ago

@MariusBgm

  1. I confirm that SilKit shared lib can be built when I removed from CMakeLists.txt line with "silkit.map".

  2. I was unable to get my shared library's build working with the SilKit static library built with NDK. Whether the SilKit library is built as shared or static, when it's compiled with the NDK, the NDK appends __ndk1 to the signature of STL functions, as in the below example:

    ld.lld: error: undefined symbol: std::__ndk1::__shared_weak_count::__release_weak()
    referenced by shared_ptr.h:184 (./workspace/android-ndk-r27c/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__memory/shared_ptr.h:184)
    CapiParticipant.cpp.o:(SilKit_Participant_Create) in archive ./libSilKit.a

My shared library that relies on the SilKit library is an Android platform library built using the Android platform's Soong build system (which uses blueprints: Android.bp files rather than CMakeLists.txt).

This caused linker errors because the Android platform's libc++ doesn’t include __ndk1 in the STL function signatures, resulting in missing symbol definitions. If I could build the SilKit library within the Android platform using Soong instead of "cmake + NDK", the STL symbols should match and the issue would likely be resolved. However, SilKit and its dependencies are based entirely on cmake, with no support for Soong blueprints, so only a "cmake + NDK" build is currently feasible.

This post on android-ndk forum also confirms that build with NDK should be used only for apps, not for libs. https://groups.google.com/g/android-ndk/c/OIinzHaWs0w image

MariusBgm commented 4 days ago

@kocho1984 thanks for the feedback. Indeed, Android platform is a different environment than Android Apps. Currently we do not support Soong / Bazel builds, CMake is the only supported build system.

Writing your own Android.bp blueprint files should be straight forward though.

However, out of curiosity: SilKit exposes a stable C ABI, so if the libc++ is statically linked, it might work. If you are feeling lucky, you might try building it with the whole libc++_static included, however this is just for experimentation and not really supported:

cmake \
    -D CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
    -D ANDROID_PLATFORM=android-34 \
    -D ANDROID_ABI=arm64-v8a \
    -D ANDROID_NDK=$ANDROID_NDK_HOME \
    -D SILKIT_BUILD_DEMOS=OFF \
    -D SILKIT_BUILD_STATIC=OFF \
    -D CMAKE_ANDROID_STL_TYPE=c++_static \
    -D SILKIT_BUILD_TESTS=OFF  \
    -D CMAKE_SHARED_LINKER_FLAGS="-Wl,--whole-archive -lc++_static -Wl,--no-whole-archive" \
    -B _build/android -S .

The --whole-archive linker flag is crucial here.

kocho1984 commented 4 days ago

@MariusBgm,

Writing your own Android.bp blueprint files should be straight forward though.

SilKit is big project with more than 1000 cpp files and more than 70 CMakeLists.txt files (including its dependencies). That means that all of those CMakeLists.txt should be rewritten as Android.bp. I believe it is complex and time-consuming task.

However, out of curiosity: SilKit exposes a stable C ABI, so if the libc++ is statically linked, it might work. If you are feeling lucky, you might try building it with the whole libc++_static included, however this is just for experimentation and not really supported:

Thank you, I will probably check it. However I think that mixing different libc++ (NDK and platform in this case) is risky and usually not recommended because it may lead to unpredicted behavior and runtime errors. As it is also stated in the android-ndk group, building all the components on the platform is much safer because one libc++ is used in such case.

kocho1984 commented 4 days ago

@MariusBgm Regarding following command you put

make \
    -D CMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake \
    -D ANDROID_PLATFORM=android-34 \
    -D ANDROID_ABI=arm64-v8a \
    -D ANDROID_NDK=$ANDROID_NDK_HOME \
    -D SILKIT_BUILD_DEMOS=OFF \
    -D SILKIT_BUILD_STATIC=OFF \
    -D CMAKE_ANDROID_STL_TYPE=c++_static \
    -D SILKIT_BUILD_TESTS=OFF  \
    -D CMAKE_SHARED_LINKER_FLAGS="-Wl,--whole-archive -lc++_static -Wl,--no-whole-archive" \
    -B _build/android -S .

This line contains both --whole-archive and --no-whole-archive. Is this correct? -D CMAKE_SHARED_LINKER_FLAGS="-Wl,--whole-archive -lc++_static -Wl,--no-whole-archive"

Do you know maybe how that command should look in case I would like to build SilKit as a static lib?

MariusBgm commented 4 days ago

@kocho1984

Is this correct?

Yes, it tells the linker to contain the whole archive named libc++static.a into your target shared library, in this case libSilKit.so. You can add any number of libraries to be included, and mark the end of the linker input using the no-whole-archive: -Wl, --whole-archive A.a B.A C.a ... -Wl,--no-whole-archive.

Do you know maybe how that command should look in case I would like to build SilKit as a static lib?

A static library is just an archive of object files -- the linker doesn't resolve symbols, and thus adding --whole-archive makes no sense here. You have to add the linker flags to your target library, not to libSilKit.a. So you have to modify your target library (in case you want to include the static silkit library and the static libc++_static.a). Hope this helps.

MariusBgm commented 3 days ago

@kocho1984 if you want to discuss this further or how to integrate SIL Kit into different build systems, feel free to contact me using my work emailadress which you can find in the git log.