Swiften is a XMPP client library and my objective was to build it for a ARM-embedded target running Linux.
I hacked my way to a successful cross-compile with little knowledge of SCons. I'll lay out my hack here with the hope that someone can point me to a maintainable solution using the two makery files, SConscript.boot and SConstruct.
I had two tasks (neither accomplished satisfactorily):
Successfully switching the tool-chain from native-compile to cross-compile
Ensuring that OpenSSL libraries were successfully linked (not supplied by the swiftim project; they has to be installed and built in the 3rdParty folder).
Switching the tool-chain from native-compile to cross-compile for ARM
My ARM cross tool-chain components, gcc, g++, ld, etc are located here.
/opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/arm-linux-gnueabihf/bin/
I couldn't find a way to tell scons to use the cross tool-chain (from the above location) instead of the native tool (in the usual place, /usr/bin). Prefacing the invocation (./scons Swiften) with the fully-qualified values for the environment variables, CC and CXX didn't work (while not recommended, its alluded to in one place).
Scons would only pick up the native tool-chain even after many ad hoc changes to the makery.
So, as a hack, I had to change the native tool-chain to point to the cross tool-chain.
/usr/bin/gcc -> /opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/bin/arm-linux-gnueabihf-gcc-4.7.3*
/usr/bin/g++ -> /opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/bin/arm-linux-gnueabihf-g++*
The first compile-break for ARM was fixed by adding the line below to the default portion of the build script, SConscript.boot.
env.Append(CPPDEFINES = ["_LITTLE_ENDIAN"])
The next compile-break has to do with the OpenSSL header files not being found. To fix the location issue, I had to introduce the line below into SConscript.boot
vars.Add(PackageVariable("openssl", "OpenSSL location", "/home/auro-tripathy/swiftim/swift/3rdParty/OpenSSL/openssl-1.0.1c/"))
Linking with OpenSSL
For the sample Switften programs to link with the OpenSSL libraries, I had to move libssl.a and libcrypto.a (built separately) from the location they were built to the toolchain library-location like so.
mv ~/swiftim/swift/3rdParty/OpenSSL/openssl-1.0.1c/libcrypto.a /opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/lib/gcc/arm-linux-gnueabihf/4.7.3/.
Help
Not understanding of the working of scons, I've made some hacks to get it to work.
I’d like some help to:
Introduce a new target called ARM-embedded, just like other targets; iPhone, android, etc
Clean way to integrate OpenSSL into the build .
Update
Per dirkbaechle, retried the script below and it works
export CC=/opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/arm-linux-gnueabihf/bin/gcc
export CXX=/opt/toolchain/gcc-linaro-arm-linux-gnueabihf-4.7-2013.01-20130125_linux/arm-linux-gnueabihf/bin/g++
./scons Swiften
Brady's answer is correct, regarding how you'd do it in plain SCons. I'd just like to mention that the top-level SConstruct of Swiften already provides arguments like "cc=" and "cxx=" for using local toolchains.
You might want to inspect the ouput of scons -h for a complete list of available options.
In addition, the SConscript for the OpenSSL build expects the sources to be located in the relative folder named "openssl", not "openssl-1.0.1c" as in your case. Maybe that's where your build problems are mainly coming from.
I left a comment above regarding the cross-compilation. Its already been answered in the link provided, but basically you just need to set the appropriate construction variables: CC, CXX, LINK, etc.
As for a "Clean way to integrate OpenSSL into the build" this can be performed simply by adding library and include paths appropriately as follows replacing the quoted values appropriately:
(without having to copy/move the original files)
# This sets the location of the OpenSSL Include paths
env.Append(CPPPATH="path/to/openssl/includes")
# This sets the location of the OpenSSL Libraries
env.Append(LIBPATH="path/to/openssl/libraries")
# These are the OpenSSL libraries to be linked into the binary
env.Append(LIBS=["OpenSSL_lib", "OpenSSL_lib2"])
The choice of compiler, and additional flags, can all be set in Swift's config.py file. A snippet from config.py using a custom compiler and flags is below (the one I use on one of my dev boxes):
cc = link = "/usr/local/llvm-git/bin/clang"
cxx = "/usr/local/llvm-git/bin/clang++"
bothflags = " -std=c++11 -stdlib=libc++ -nostdinc++"
cxxflags = bothflags + " -I/usr/local/libcxx/include -Wno-deprecated"
linkflags = bothflags + " -L/usr/local/libcxx/lib"
This should work for cross-compiling in the same manner.
To use a bundled openssl, you should just be able to extract into 3rdParty/OpenSSL, and add openssl_force_bundled = True to your config.py. You should not need to fiddle with setting include paths to this yourself. It's conceivable that this is tied to a particular openssl release as I've not compiled a bundled openssl since 1.0.0a, but if it doesn't work with the current version it's probably a bug that ought to be fixed. You could also cross-compile openssl yourself and use openssl='/path/to/openssl', but that's a little more of a nuisance for you.
Related
I am trying to build an executable for Android with cross compiling, everything works but the executable complains that it could not find the .so file it needs, which is in the same directory as the executable.
So what I did is to add the following lines
set(TARGET myapp)
# following 4 lines added to add RPATH of ./ to the binary
# so it searches the .so in the same directory
SET(CMAKE_SKIP_BUILD_RPATH FALSE)
SET(CMAKE_SKIP_RPATH FALSE)
set(CMAKE_INSTALL_RPATH $ORIGIN)
SET(CMAKE_BUILD_WITH_INSTALL_RPATH ON)
# add source code to target
add_executable(${TARGET} src.cpp)
...
However, it builds the executable, but RPATH seems not working no matter how I play with the four lines above, I just could not find any RPATH info in the binary using readelf or objdump.
I also tried set_target_properties(${TARGET} PROPERTIES INSTALL_RPATH $ORIGIN) but still not working.
Did I miss use anything here for RPATH configuration?
update
just to note that if I build the app for host(Linux) (using the same cmake file except using the android ndk tool chain) then everything is fine, I see $ORIGIN in the binary RPATH using readelf.
although i dont know what is been done in android ndk tool chain
This is probably not what you want:
(I am mentioning it just to be complete with my answer)
I assume that $ORIGIN is an environment variable. If that is the case you need to explain to CMake that it is such an variable. You can use $ENV{VAR} to do this, e.g.:
set(CMAKE_INSTALL_RPATH $ENV{ORIGIN})
This is probably what you want:
Ofcourse if the variable is not accessible during CMake generation step. You can try to use bracket arguments, however I do not think that alone would work (see last note at the bottom). Bracket arguments [=[...]=] tell CMake to skip the evaluation, because $ is a special character. e.g.:
set(CMAKE_INSTALL_RPATH [=[$ORIGIN]=])
To understand what [=[]=] do here is a simple example:
set(FOO "bar")
message(STATUS ${FOO})
message(STATUS [=[${FOO}]=])
Should output
bar
${FOO} #<-- evaluation of ${FOO} was skipped
Also if I'm not mistaken you also need to pass $ORIGIN to linker with single quotes so that it doesn't get evaluated during linking, i.e.
'$ORIGIN'
#and not $ORIGIN
Looking around on the net I have seen a lot of code like this:
include(FindPkgConfig)
pkg_search_module(SDL2 REQUIRED sdl2)
target_include_directories(app SYSTEM PUBLIC ${SDL2_INCLUDE_DIRS})
target_link_libraries(app ${SDL2_LIBRARIES})
However that seems to be the wrong way about doing it, as it only uses the include directories and libraries, but ignored defines, library paths and other flags that might be returned by pkg-config.
What would be the correct way to do this and ensure that all compile and link flags returned by pkg-config are used by the compiled app? And is there a single command to accomplish this, i.e. something like target_use(app SDL2)?
ref:
include()
FindPkgConfig
First of, the call:
include(FindPkgConfig)
should be replaced with:
find_package(PkgConfig)
The find_package() call is more flexible and allows options such as REQUIRED, that do things automatically that one would have to do manually with include().
Secondly, manually calling pkg-config should be avoid when possible. CMake comes with a rich set of package definitions, found in Linux under /usr/share/cmake-3.0/Modules/Find*cmake. These provide more options and choice for the user than a raw call to pkg_search_module().
As for the mentioned hypothetical target_use() command, CMake already has that built-in in a way with PUBLIC|PRIVATE|INTERFACE. A call like target_include_directories(mytarget PUBLIC ...) will cause the include directories to be automatically used in every target that uses mytarget, e.g. target_link_libraries(myapp mytarget). However this mechanism seems to be only for libraries created within the CMakeLists.txt file and does not work for libraries acquired with pkg_search_module(). The call add_library(bar SHARED IMPORTED) might be used for that, but I haven't yet looked into that.
As for the main question, this here works in most cases:
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2)
...
target_link_libraries(testapp ${SDL2_LIBRARIES})
target_include_directories(testapp PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(testapp PUBLIC ${SDL2_CFLAGS_OTHER})
The SDL2_CFLAGS_OTHER contains defines and other flags necessary for a successful compile. The flags SDL2_LIBRARY_DIRS and SDL2_LDFLAGS_OTHER are however still ignored, no idea how often that would become a problem.
More documentation here http://www.cmake.org/cmake/help/latest/module/FindPkgConfig.html
If you're using cmake and pkg-config in a pretty normal way, this solution works.
If, however, you have a library that exists in some development directory (such as /home/me/hack/lib), then using other methods seen here fail to configure the linker paths. Libraries that are not found under the typical install locations would result in linker errors, like /usr/bin/ld: cannot find -lmy-hacking-library-1.0. This solution fixes the linker error for that case.
Another issue could be that the pkg-config files are not installed in the normal place, and the pkg-config paths for the project need to be added using the PKG_CONFIG_PATH environment variable while cmake is running (see other Stack Overflow questions regarding this). This solution also works well when you use the correct pkg-config path.
Using IMPORTED_TARGET is key to solving the issues above. This solution is an improvement on this earlier answer and boils down to this final version of a working CMakeLists.txt:
cmake_minimum_required(VERSION 3.14)
project(ya-project C)
# the `pkg_check_modules` function is created with this call
find_package(PkgConfig REQUIRED)
# these calls create special `PkgConfig::<MODULE>` variables
pkg_check_modules(MY_PKG REQUIRED IMPORTED_TARGET any-package)
pkg_check_modules(YOUR_PKG REQUIRED IMPORTED_TARGET ya-package)
add_executable(program-name file.c ya.c)
target_link_libraries(program-name PUBLIC
PkgConfig::MY_PKG
PkgConfig::YOUR_PKG)
Note that target_link_libraries does more than change the linker commands. It also propagates other PUBLIC properties of specified targets like compiler flags, compiler defines, include paths, etc., so, use the PUBLIC keyword with caution.
It's rare that one would only need to link with SDL2. The currently popular answer uses pkg_search_module() which checks for given modules and uses the first working one.
It is more likely that you want to link with SDL2 and SDL2_Mixer and SDL2_TTF, etc... pkg_check_modules() checks for all the given modules.
# sdl2 linking variables
find_package(PkgConfig REQUIRED)
pkg_check_modules(SDL2 REQUIRED sdl2 SDL2_ttf SDL2_mixer SDL2_image)
# your app
file(GLOB SRC "my_app/*.c")
add_executable(my_app ${SRC})
target_link_libraries(my_app ${SDL2_LIBRARIES})
target_include_directories(my_app PUBLIC ${SDL2_INCLUDE_DIRS})
target_compile_options(my_app PUBLIC ${SDL2_CFLAGS_OTHER})
Disclaimer: I would have simply commented on Grumbel's self answer if I had enough street creds with stackoverflow.
Most of the available answers fail to configure the headers for the pkg-config library. After meditating on the Documentation for FindPkgConfig I came up with a solution that provides those also:
include(FindPkgConfig)
if(NOT PKG_CONFIG_FOUND)
message(FATAL_ERROR "pkg-config not found!" )
endif()
pkg_check_modules(<some-lib> REQUIRED IMPORTED_TARGET <some-lib>)
target_link_libraries(<my-target> PkgConfig::<some-lib>)
(Substitute your target in place of <my-target> and whatever library in place of <some-lib>, accordingly.)
The IMPORTED_TARGET option seems to be key and makes everything then available under the PkgConfig:: namespace. This was all that was required and also all that should be required.
There is no such command as target_use. But I know several projects that have written such a command for their internal use. But every project want to pass additional flags or defines, thus it does not make sense to have it in general CMake. Another reason not to have it are C++ templated libraries like Eigen, there is no library but you only have a bunch of include files.
The described way is often correct. It might differ for some libraries, then you'll have to add _LDFLAGS or _CFLAGS. One more reason for not having target_use. If it does not work for you, ask a new question specific about SDL2 or whatever library you want use.
If you are looking to add definitions from the library as well, the add_definitions instruction is there for that. Documentation can be found here, along with more ways to add compiler flags.
The following code snippet uses this instruction to add GTKGL to the project:
pkg_check_modules(GTKGL REQUIRED gtkglext-1.0)
include_directories(${GTKGL_INCLUDE_DIRS})
link_directories(${GTKGL_LIBRARY_DIRS})
add_definitions(${GTKGL_CFLAGS_OTHER})
set(LIBS ${LIBS} ${GTKGL_LIBRARIES})
target_link_libraries([insert name of program] ${LIBS})
I am trying to add a strip debug symbols step for my Android library which includes native shared libraries for different ABIs, e.g. x86/native-lib.so, x86_64/native-lib.so, arm64-v8a/native-lib.so, etc.
I understand that the strip command must be respective to each ABI. So, I need to invoke the correct strip command, for which I need to know its correct path during build time.
For example, for ABI x86_64, I need to have below path setting:
set(STRIP ~/Library/Android/android-ndk-r16b/toolchains/x86_64-4.9/prebuilt/darwin-x86_64/bin/x86_64-linux-android-strip)
add_custom_command(TARGET ${SHARED_LIBRARY_NAME} POST_BUILD
COMMAND ${STRIP}
"${DIST_LIBS_DIR}/${LIB_BUILD_TYPE}/${ANDROID_ABI}/lib${SHARED_LIBRARY_NAME}.so"
COMMENT "Strip debug symbols done on final binary.")
The path I need is illustrated like below:
So, my questions are:
Is there an existing CMake variable to point at this path, i.e. /android-ndk-r16b/toolchains/???/prebuilt/???/bin/???-???-???-strip?
If not, is there a way to form this path utilising other known Android CMake variable, e.g. ANDROID_NDK, ANDROID_ABI, etc?
Thanks #Alex Cohn a lot for pointing out the file android.toolchain.cmake which usually exists at directory ~/Library/Android/sdk/cmake/cmake_version_xxx/android.toolchain.cmake on macOS.
There are many useful Android CMake variables already configured inside, e.g.
ANDROID_NDK
ANDROID_TOOLCHAIN
ANDROID_ABI
ANDROID_PLATFORM
ANDROID_STL
ANDROID_PIE
ANDROID_CPP_FEATURES
ANDROID_ALLOW_UNDEFINED_SYMBOLS
ANDROID_ARM_MODE
ANDROID_ARM_NEON
ANDROID_DISABLE_NO_EXECUTE
ANDROID_DISABLE_RELRO
ANDROID_DISABLE_FORMAT_STRING_CHECKS
ANDROID_CCACHE
And the one ANDROID_TOOLCHAIN_PREFIX is exactly what I looked for, so my final CMake script comes into below:
add_custom_command(TARGET ${SHARED_LIBRARY_NAME} POST_BUILD
COMMAND "${ANDROID_TOOLCHAIN_PREFIX}strip" -g -S -d --strip-debug --verbose
"${DIST_LIBS_DIR}/${LIB_BUILD_TYPE}/${ANDROID_ABI}/lib${SHARED_LIBRARY_NAME}.so"
COMMENT "Strip debug symbols done on final binary.")
And I don't need to explicitly pass any additional arguments, i.e. DCMAKE_TOOLCHAIN_FILE=android.toolchain.cmake, from command line to the build process. Because, this file, i.e. android.toolchain.cmake, was already taken into account automatically by Android native build system.
You can use ${CMAKE_STRIP}. It is set appropriately when you use -DCMAKE_TOOLCHAIN_FILE=android.toolchain.cmake. I hope it is OK also if you work with 'built-in' Android support with supported NDK version.
I'm pretty new to Android with NDK / CMake. However, I'm trying to embed a native CMake library into an Android Application. However, this library depends on OpenSSL.
That's why I downloaded a precompiled version of OpenSSL for Android.
However, when I try to sync the project I get the following error:
Could NOT find OpenSSL, try to set the path to OpenSSL root folder
in the system variable OPENSSL_ROOT_DIR (missing: OPENSSL_LIBRARIES)
(found version "1.1.0f")
Here is my (minimal) project structure
<app-name>
-app
- src
- main
- cpp
- library
- CMakeLists.txt
CMakeLists.txt
- distribution
- openssl
- armeabi
- include
- openssl
...
- lib
libcrypto.a, libssl.a
In my build.gradle I've defined the following:
externalNativeBuild {
cmake {
path 'src/main/cpp/CMakeLists.txt'
}
}
The /app/src/main/cpp/CmakeLists.txt looks as follows:
cmake_minimum_required(VERSION 3.4.1)
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
set(OPENSSL_ROOT_DIR ${distribution_DIR}/openssl/${ANDROID_ABI})
set(OPENSSL_LIBRARIES "${OPENSSL_ROOT_DIR}/lib")
set(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include)
message("OPENSSL_LIBRARIES ${OPENSSL_LIBRARIES}")
message("OPENSSL_INCLUDE_DIR ${OPENSSL_INCLUDE_DIR}")
find_package(OpenSSL REQUIRED)
add_subdirectory(library)
find_package(...) searches for libraries in a few standard locations (read here - search for "Search paths specified in cmake"). In your case, it fails because it can't find OpenSSL on the machine you're trying to cross-compile the code for Android.
I know I also had various attempts on linking OpenSSL with my native c++ Android code, and the only way I managed it make it work, was the following:
SET(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
SET(OPENSSL_ROOT_DIR ${distribution_DIR}/openssl/${ANDROID_ABI})
SET(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
SET(OPENSSL_INCLUDE_DIR ${OPENSSL_ROOT_DIR}/include)
SET(OPENSSL_LIBRARIES "ssl" "crypto")
#<----Other cmake code/defines here--->
LINK_DIRECTORIES(${OPENSSL_LIBRARIES_DIR})
ADD_LIBRARY(library #your other params here#)
TARGET_INCLUDE_DIRECTORIES(library PUBLIC ${OPENSSL_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(library ${OPENSSL_LIBRARIES})
I know I also tried to make find_package work correctly, by playing with some of it configuration properties, such as CMAKE_FIND_ROOT_PATH, and some other approaches, but I couldn't get it done.
Don't know if the solution I provided is the best approach, cmake-wise. Maybe someone has a better way of doing it, but alas, it solved my problem at the time.
Hope this helps
The reason OpenSSL is not found is because all the find_*() commands also rely on the variable CMAKE_SYSROOT variable, which is used to prefix paths searched by those commands. This is used when cross-compiling to point to the root directory of the target environment.
A solution is to add the path where OpenSSL is located to CMAKE_FIND_ROOT_PATH; the documentation of find_library() states:
The CMake variable CMAKE_FIND_ROOT_PATH specifies one or more
directories to be prepended to all other search directories. This
effectively “re-roots” the entire search under given locations.
This solution works for me:
set(OPENSSL_ROOT_DIR "/path/to/openssl/for/android")
list(APPEND CMAKE_FIND_ROOT_PATH "${OPENSSL_ROOT_DIR}")
find_package(OpenSSL)
For me it worked to import openssl via gradle and then linking the fetched lib like described here
Either append NO_CMAKE_FIND_ROOT_PATH to your find_* command, e.g.
find_package(OpenSSL REQUIRED NO_CMAKE_FIND_ROOT_PATH)
Or set CMAKE_FIND_ROOT_PATH_MODE_* variables (like this) to NEVER. The following options fixed the Android and iOS builds on macOS for me:
-DOPENSSL_USE_STATIC_LIBS=TRUE \
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=NEVER \
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=NEVER
I have representations of all my dependencies and my library in LLVM IR forms. How to cross-compile my library into a shared object for iOS, Android, Windows and Mac platforms from Linux ( Ubuntu for example )?
Please provide a single example script that would compile any example library with at least one dependency on to another library of your choice to all 4 platforms ( for example OpenCV or ZeroMQ 4+ ).
Using the LLVM static compiler (llc), you can compile the LLVM IR into object files for a specific target triple. Though the target triples are not documented very well, the LLVM infrastructure is all open source, so a quick search through the source code will lead you here.
Unfortunately, there is no documentation for a discrete list of possible target triples you can use. However, if you know exactly what system you're targeting, constructing a triple is fairly easy. Taken from the target triple documentation, you can see :
The triple has the general format <arch><sub>-<vendor>-<sys>-<abi>,
where:
arch = x86_64, i386, arm, thumb, mips, etc.
sub = for ex. on ARM: v5, v6m, v7a, v7m, etc.
vendor = pc, apple, nvidia, ibm, etc.
sys = none, linux, win32, darwin, cuda, etc.
abi = eabi, gnu, android, macho, elf, etc.
Once you figure out what target triple you're using, you specify it as a string using the -mtriple flag. Here are some examples:
Windows: -mtriple=i686-pc-win32-gnu
Linux: -mtriple=i686-pc-linux-gnu
IOS: -mtriple=armv7-apple-ios
Android: -mtriple=arm-linux-androideabi
Next, you need to specify that you want to compile an object file using the filetype flag:
-filetype=obj
This should be enough if I understand your question correctly.
If you're expecting to use a single file on all platforms and operating systems, while this is possible, it would take a lot of work and I wouldn't expect an answer regarding that here on stackoverflow.
From this link, There is a Variable LLVM_TARGETS_TO_BUILD and the definition says that
A semicolon delimited list controlling which targets will be built and linked into llc. This is equivalent to the --enable-targets option in the configure script. The default list is defined as LLVM_ALL_TARGETS, and can be set to include out-of-tree targets. The default value includes: AArch64, AMDGPU, ARM, BPF, Hexagon, Mips, MSP430, NVPTX, PowerPC, Sparc, SystemZ, X86, XCore.
You should add the X86 and ARM is present in it. you need to add support for 64 and Apple
From this link
It is possible to cross compile
The example command looks like
% cmake -G "Ninja" -DCMAKE_OSX_ARCHITECTURES="armv7;armv7s;arm64"
-DCMAKE_TOOLCHAIN_FILE=<PATH_TO_LLVM>/cmake/platforms/iOS.cmake
-DCMAKE_BUILD_TYPE=Release -DLLVM_BUILD_RUNTIME=Off -DLLVM_INCLUDE_TESTS=Off
-DLLVM_INCLUDE_EXAMPLES=Off -DLLVM_ENABLE_BACKTRACES=Off [options]
<PATH_TO_LLVM>
Also I would like to share this link. It says
The basic option is to define the target architecture. For that, use -target . If you don’t specify the target, CPU names won’t match (since Clang assumes the host triple), and the compilation will go ahead, creating code for the host platform, which will break later on when assembling or linking.
The triple has the general format <arch><sub>-<vendor>-<sys>-<abi>, where:
arch = x86_64, i386, arm, thumb, mips, etc.
sub = for ex. on ARM: v5, v6m, v7a, v7m, etc.
vendor = pc, apple, nvidia, ibm, etc.
sys = none, linux, win32, darwin, cuda, etc.
abi = eabi, gnu, android, macho, elf, etc.