From db41697dbfab2e611f92a4897a34fcf0942ebc06 Mon Sep 17 00:00:00 2001 From: Johan Ouwerkerk Date: Thu, 7 May 2020 22:08:47 +0200 Subject: [PATCH] Build libsodium as a CMake external project --- CMakeLists.txt | 14 +++- android/CMakeLists.txt | 12 +++ android/android-export.in.sh | 134 ++++++++++++++++++++++++++++++++++ cmake/external/configure-autotools.sh | 19 +++++ cmake/external/make.sh | 23 ++++++ src/secrets/CMakeLists.txt | 103 ++++++++++++++++++++++++++ 6 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 android/CMakeLists.txt create mode 100644 android/android-export.in.sh create mode 100755 cmake/external/configure-autotools.sh create mode 100755 cmake/external/make.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 1572301..1f4aa8f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,7 +27,7 @@ include(FeatureSummary) find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) # where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked -set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) include(KDEInstallDirs) include(KDECMakeSettings) @@ -42,10 +42,16 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) ################# Find dependencies ################# find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg QuickControls2) -find_package(sodium ${SODIUM_MIN_VERSION} REQUIRED) find_package(KF5Kirigami2 ${KF5_MIN_VERSION} REQUIRED) find_package(KF5I18n ${KF5_MIN_VERSION} REQUIRED) +if(NOT BUILD_EXTERNAL AND (NOT ANDROID OR DEFINED BUILD_EXTERNAL)) + set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/") + + message(STATUS "Will build against system version of: libsodium") + find_package(sodium ${SODIUM_MIN_VERSION} REQUIRED) +endif() + ################ Find testing dependencies ########## if (BUILD_TESTING) @@ -55,6 +61,10 @@ endif() ################# build and install ################# add_subdirectory(src) +if (ANDROID) + add_subdirectory(android) +endif() + if (BUILD_TESTING) add_subdirectory(autotests) endif() diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 0000000..784c6d8 --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk +# + +configure_file ("${CMAKE_CURRENT_SOURCE_DIR}/android-export.in.sh" "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android-export.sh" @ONLY ESCAPE_QUOTES) + +file ( + COPY "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/android-export.sh" + DESTINATION "${CMAKE_CURRENT_BINARY_DIR}" + FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE +) diff --git a/android/android-export.in.sh b/android/android-export.in.sh new file mode 100644 index 0000000..6537370 --- /dev/null +++ b/android/android-export.in.sh @@ -0,0 +1,134 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk +# + +set -e + +cat << INTRO +Running android-export.sh wrapper script: $0 +Build info + +== target: + + - api : @CMAKE_ANDROID_API@ + - arch : @CMAKE_ANDROID_ARCH@ + - abi : @CMAKE_ANDROID_ARCH_ABI@ + - stl : @CMAKE_ANDROID_STL_TYPE@ + +== build host: + + - host: @CMAKE_HOST_SYSTEM_NAME@ + - arch: @CMAKE_HOST_SYSTEM_PROCESSOR@ + +== ndk: + + - path : @CMAKE_ANDROID_NDK@ + +INTRO + +tool_prefix () +{ + local binutils="$1" + # see: https://developer.android.com/ndk/guides/other_build_systems + case "@CMAKE_ANDROID_ARCH@" in + arm) + if [ -n "$binutils" ] + then + echo -n "arm-linux-androideabi" + else + echo -n "armv7a-linux-androideabi" + fi + ;; + arm64) + echo -n "aarch64-linux-android" + ;; + x86) + echo -n "i686-linux-android" + ;; + x86-64|x86_64) # not sure which will be passed, accept both + echo -n "x86_64-linux-android" + ;; + *) + echo "Unable to continue: unknown/undefined/unsupported Android architecture: '@CMAKE_ANDROID_ARCH@'" >&2 + exit 254 + ;; + esac +} + +clang_toolname_prefix () +{ + echo -n "$(tool_prefix)@CMAKE_ANDROID_API@" +} + +toolchain_host () +{ + echo -n "@CMAKE_HOST_SYSTEM_NAME@-@CMAKE_HOST_SYSTEM_PROCESSOR@" | tr [:upper:] [:lower:] +} + +toolchain_path () +{ + echo -n "@CMAKE_ANDROID_NDK@/toolchains/llvm/prebuilt/$(toolchain_host)" +} + +get_stl () +{ + case "@CMAKE_ANDROID_STL_TYPE@" in + c++_shared) + echo -n "libc++" + ;; + *) + echo "Unable to continue: unknown/undefined/unsupported STL: '@CMAKE_ANDROID_STL_TYPE@'" >&2 + exit 254 + ;; + esac +} + +get_cxx_flags () +{ + case "$CXXFLAGS" in + *"--stl"*) + echo -n "$CXXFLAGS" + ;; + *) + echo -n "$CXXFLAGS --stl=$(get_stl)" + ;; + esac +} + + +# see: https://developer.android.com/ndk/guides/other_build_systems +BINUTILS_PREFIX="$(tool_prefix "true")" +CLANG_PREFIX="$(clang_toolname_prefix)" +TOOLCHAIN_BINDIR="$(toolchain_path)/bin" +CXXFLAGS="$(get_cxx_flags)" + +AR="$TOOLCHAIN_BINDIR/$BINUTILS_PREFIX-ar" +AS="$TOOLCHAIN_BINDIR/$BINUTILS_PREFIX-as" +CC="$TOOLCHAIN_BINDIR/$CLANG_PREFIX-clang" +LD="$TOOLCHAIN_BINDIR/$BINUTILS_PREFIX-ld" +CXX="$TOOLCHAIN_BINDIR/$CLANG_PREFIX-clang++" +STRIP="$TOOLCHAIN_BINDIR/$BINUTILS_PREFIX-strip" +RANLIB="$TOOLCHAIN_BINDIR/$BINUTILS_PREFIX-ranlib" + +if [ ! -d "$TOOLCHAIN_BINDIR" ] +then + echo "Unable to continue: tools directory not found: $TOOLCHAIN_BINDIR" >&2 + echo "Android NDK root directory is supposed to be: @CMAKE_ANDROID_NDK@" >&2 + exit 1 +fi + +set -x + +export PATH="$TOOLCHAIN_BINDIR:$PATH" \ + CXXFLAGS="$CXXFLAGS" \ + AR="$AR" \ + AS="$AS" \ + CC="$CC" \ + LD="$LD" \ + CXX="$CXX" \ + STRIP="$STRIP" \ + RANLIB="$RANLIB" + +$@ diff --git a/cmake/external/configure-autotools.sh b/cmake/external/configure-autotools.sh new file mode 100755 index 0000000..8b0910f --- /dev/null +++ b/cmake/external/configure-autotools.sh @@ -0,0 +1,19 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk +# + +set -ex + +# +# Autotools builds are in-source which is messy. In particular, it can +# cause conflicts if builds have to be run with different ./configure +# settings one after another +# +# Impose some sanity by ensuring builds/configure always start from scratch +# +git clean -fdx + +./autogen.sh +./configure "$@" diff --git a/cmake/external/make.sh b/cmake/external/make.sh new file mode 100755 index 0000000..d614cc4 --- /dev/null +++ b/cmake/external/make.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# +# SPDX-License-Identifier: BSD-2-Clause +# SPDX-FileCopyrightText: 2020 Johan Ouwerkerk +# + +set -e + +# +# Try to speed up builds by using parallel make. Assume that if MAKEFLAGS +# is set, someone is trying to control make behaviour explicitly and do not +# second-guess that. +# +if [ -z "$MAKEFLAGS" ] +then + cores="$(nproc --all)" + jobs="$(($cores * 5 / 4))" + set -x + make -j "$jobs" "$@" +else + set -x + make "$@" +fi diff --git a/src/secrets/CMakeLists.txt b/src/secrets/CMakeLists.txt index 963177c..b096063 100644 --- a/src/secrets/CMakeLists.txt +++ b/src/secrets/CMakeLists.txt @@ -6,4 +6,107 @@ set(secret_SRCS secrets.cpp) add_library(secrets_lib STATIC ${secret_SRCS}) + +if (BUILD_EXTERNAL OR (NOT DEFINED BUILD_EXTERNAL AND ANDROID)) + message(STATUS "Will build external project: libsodium") + include(ExternalProject) + + # + # FIXME: for now assume that ANDROID means cross-compiling. + # Technically this is not quite true when running on Android itself. + # + # As a work-around, a user may set an empty AUTOTOOLS_HOST to disable this. + # + if (ANDROID AND NOT DEFINED AUTOTOOLS_HOST) + # see: https://developer.android.com/ndk/guides/other_build_systems + set(AUTOTOOLS_HOST "") + if ("${CMAKE_ANDROID_ARCH}" STREQUAL "arm") + # note: this is the binutils name, armv7a-linux-androideabi is the name for clang + set(AUTOTOOLS_HOST "arm-linux-androideabi") + elseif ("${CMAKE_ANDROID_ARCH}" STREQUAL "arm64") + set(AUTOTOOLS_HOST "aarch64-linux-android") + elseif("${CMAKE_ANDROID_ARCH}" STREQUAL "x86") + set(AUTOTOOLS_HOST "i686-linux-android") + # not sure which will be passed, accept both + elseif("${CMAKE_ANDROID_ARCH}" STREQUAL "x86_64" OR "${CMAKE_ANDROID_ARCH}" STREQUAL "x86-64") + set(AUTOTOOLS_HOST "x86_64-linux-android") + else() + message(FATAL_ERROR "Building for Android but got an unknown/undefined/unsupported Android architecture (ANDROID_ARCH): '${ANDROID_ARCH}'") + endif() + endif() + + if (AUTOTOOLS_HOST) + set(AUTOTOOLS_HOST_OPTION "--host=${AUTOTOOLS_HOST}") + else() + set(AUTOTOOLS_HOST_OPTION "") + endif() + + set(sodium_LIBRARY_PATH "lib/libsodium.so") + set(sodium_INCLUDE_PATH "include") + + # + # Use a wrapper script for Android to pre-populate the environment with necessary environment variables. + # This ensures the right toolchain will be picked up by the autotools build system. + # + set(ENV_WRAPPER "") + if (ANDROID) + set(ENV_WRAPPER "${CMAKE_BINARY_DIR}/android/android-export.sh") + endif() + + # + # Make gets confused if there is another make process somewhere in the process tree above it. This prevents it from + # doing parallel make with the "jobserver is unavailable" warning instead of assuming it is an actual top-level make + # process as it were. + # + # The work-around is to use $(MAKE), but that doesn't work if the parent process is not itself a make (e.g. ninja). + # So the work-around for that is to use a wrapper script by default that does the right thing, except when CMake is + # configured to use the Unix Makefiles generator in which case $(MAKE) should be used instead. + # + set(MAKE_SH "${CMAKE_SOURCE_DIR}/cmake/external/make.sh") + if ("${CMAKE_GENERATOR}" STREQUAL "Unix Makefiles") + set(MAKE_SH "$(MAKE)") + endif() + set(CONFIGURE_SH "${CMAKE_SOURCE_DIR}/cmake/external/configure-autotools.sh" "${AUTOTOOLS_HOST_OPTION}" "--prefix=") + + # + # Unfortunately CMake generates a build system (make/ninja) which does not check whether the library already exists. + # This causes the external project to be fully rebuilt always. In turn, that triggers a rebuild of everything from + # the external project upwards as well because the library file has changed. + # + # CHECK_SH is a prefix/wrapper for actual commands that short-circuits the build if the library already exists. + # + set(CHECK_SH test -e "/${sodium_LIBRARY_PATH}" ||) + + externalProject_add( + ext_libsodium + PREFIX "${CMAKE_BINARY_DIR}/external" + GIT_REPOSITORY https://github.com/jedisct1/libsodium.git + GIT_TAG 1.0.18 # ${sodium_VERSION_STRING} + GIT_SHALLOW ON + GIT_PROGRESS ON + CONFIGURE_COMMAND ${CHECK_SH} ${ENV_WRAPPER} ${CONFIGURE_SH} + BUILD_COMMAND ${CHECK_SH} ${ENV_WRAPPER} ${MAKE_SH} + BUILD_IN_SOURCE ON + BUILD_ALWAYS OFF + INSTALL_COMMAND ${CHECK_SH} ${ENV_WRAPPER} ${MAKE_SH} install + # register the lib manually, otherwise cmake gets very confused about depending on this file + BUILD_BYPRODUCTS /${sodium_LIBRARY_PATH} + ) + + add_library(sodium SHARED IMPORTED) + + externalProject_get_property(ext_libsodium INSTALL_DIR) + + # + # INTERFACE_INCLUDE_DIRECTORIES cannot take a non-existent directory. However, nearly everything external projects do + # is done *after* cmake configure stage is over, so the directory simply won't be there. + # + # Apply the popular work-around and create it up front. + # + file(MAKE_DIRECTORY "${INSTALL_DIR}/${sodium_INCLUDE_PATH}") + set_target_properties(sodium PROPERTIES IMPORTED_LOCATION "${INSTALL_DIR}/${sodium_LIBRARY_PATH}" INTERFACE_INCLUDE_DIRECTORIES "${INSTALL_DIR}/${sodium_INCLUDE_PATH}") + + add_dependencies(secrets_lib ext_libsodium) +endif() + target_link_libraries(secrets_lib Qt5::Core sodium base32_lib)