From 210aff034678dd579d70e7625cf4513e5d031a62 Mon Sep 17 00:00:00 2001 From: Yavor Georgiev Date: Wed, 18 Oct 2023 18:56:38 +0200 Subject: [PATCH] Add support for musl c --- CMakeLists.txt | 35 ++++++++++++++++- Jenkinsfile | 54 ++++++++++++++++++++++++++ alpine.Dockerfile | 11 ++++++ src/realm/CMakeLists.txt | 4 ++ src/realm/util/backtrace.cpp | 14 ++++++- src/realm/util/basic_system_errors.cpp | 2 +- src/realm/util/config.h.in | 9 ++++- src/realm/util/file.cpp | 8 ++-- src/realm/util/thread.cpp | 22 ++++++++--- test/test_util_error.cpp | 5 +++ test/test_util_file.cpp | 3 ++ 11 files changed, 151 insertions(+), 16 deletions(-) create mode 100644 alpine.Dockerfile diff --git a/CMakeLists.txt b/CMakeLists.txt index 65dbac5b6e3..8f7533f675e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,7 @@ endif() include(GNUInstallDirs) include(CheckIncludeFiles) include(CheckSymbolExists) +include(CMakePushCheckState) include(Utilities) include(SpecialtyBuilds) include(GetVersion) @@ -237,11 +238,19 @@ if(MSVC) endif() if(UNIX) + cmake_push_check_state(RESET) # Enable access to large file APIs, but don't make them the default. add_compile_definitions("_LARGEFILE_SOURCE" "_LARGEFILE64_SOURCE") set(CMAKE_REQUIRED_DEFINITIONS "-D_LARGEFILE_SOURCE" "-D_LARGEFILE64_SOURCE") # Use readdir64 if available. check_symbol_exists(readdir64 "dirent.h" REALM_HAVE_READDIR64) + cmake_reset_check_state() + set(CMAKE_REQUIRED_DEFINITIONS "-D_POSIX_C_SOURCE=200809L") + check_symbol_exists(posix_fallocate "fcntl.h" REALM_HAVE_POSIX_FALLOCATE) + if(REALM_HAVE_POSIX_FALLOCATE) + add_compile_definitions("_POSIX_C_SOURCE=200809L") + endif() + cmake_pop_check_state() endif() # Options (passed to CMake) @@ -262,6 +271,28 @@ option(REALM_ENABLE_GEOSPATIAL "Enable geospatial types and queries." ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +# Threads library is pthread-compatible +# Check for these explicitly because older versions of musl define only one or the other +if(CMAKE_USE_PTHREADS_INIT) + check_symbol_exists(pthread_getname_np "pthread.h" REALM_HAVE_PTHREAD_GETNAME) + check_symbol_exists(pthread_setname_np "pthread.h" REALM_HAVE_PTHREAD_SETNAME) +endif() + +find_package(Backtrace) +if(Backtrace_FOUND) + add_library(Backtrace::Backtrace INTERFACE IMPORTED) + if(Backtrace_LIBRARIES AND NOT CMAKE_GENERATOR STREQUAL Xcode) + # Apple platforms always have backtrace. We disregard the `Backtrace_*` variables + # because their paths are hardcoded to one SDK within Xcode (e.g. macosx), + # whereas we build for several different SDKs and thus we can't use the include path from one in the other. + # Otherwise if CMake found that the backtrace facility is provided by an external library and not built-in + # we need to configure the interface target with the library include and link path. + target_include_directories(Backtrace::Backtrace INTERFACE ${Backtrace_INCLUDE_DIRS}) + target_link_libraries(Backtrace::Backtrace INTERFACE ${Backtrace_LIBRARIES}) + endif() + set(REALM_HAVE_BACKTRACE ON) +endif() + if(REALM_ENABLE_SYNC) option(REALM_FORCE_OPENSSL "Always use OpenSSL for SSL needs, regardless of target platform." OFF) if(CMAKE_SYSTEM_NAME MATCHES "^Windows|Linux|Android") @@ -282,7 +313,9 @@ if(REALM_NEEDS_OPENSSL OR REALM_FORCE_OPENSSL) include(${OPENSSL_CMAKE_INCLUDE_FILE}) endif() - set(OPENSSL_USE_STATIC_LIBS ON) + if(NOT DEFINED OPENSSL_USE_STATIC_LIBS) + set(OPENSSL_USE_STATIC_LIBS ON) + endif() find_package(OpenSSL REQUIRED) set(REALM_HAVE_OPENSSL ON) string(REGEX MATCH "^([0-9]+)\\.([0-9]+)" OPENSSL_VERSION_MAJOR_MINOR "${OPENSSL_VERSION}") diff --git a/Jenkinsfile b/Jenkinsfile index 9c4cf92a6d2..400b8970679 100755 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -15,6 +15,7 @@ branch = tokens[tokens.size()-1] ctest_cmd = "ctest -VV" warningFilters = [ excludeFile('/external/*'), // submodules and external libraries + excludeFile('/src/external/*'), // submodules and external libraries excludeFile('/libuv-src/*'), // libuv, where it was downloaded and built inside cmake excludeFile('/src/realm/parser/generated/*'), // the auto generated parser code we didn't write ] @@ -180,6 +181,7 @@ jobWrapper { parallelExecutors = [ buildLinuxRelease : doBuildLinux('Release'), + checkAlpine : doCheckAlpine(buildOptions + [enableSync: true]), checkLinuxDebug : doCheckInDocker(buildOptions + [useToolchain : true]), checkLinuxDebugEncrypt : doCheckInDocker(buildOptions + [useEncryption : true]), checkLinuxRelease_4 : doCheckInDocker(buildOptions + [maxBpNodeSize: 4, buildType : 'Release', useToolchain : true]), @@ -312,6 +314,58 @@ def doCheckInDocker(Map options = [:]) { } } +def doCheckAlpine(Map options = [:]) { + def cmakeOptions = [ + CMAKE_BUILD_TYPE: options.buildType, + REALM_MAX_BPNODE_SIZE: options.maxBpNodeSize, + REALM_ENABLE_ENCRYPTION: options.enableEncryption ? 'ON' : 'OFF', + REALM_ENABLE_SYNC: options.enableSync ? 'ON' : 'OFF', + REALM_USE_SYSTEM_OPENSSL: 'ON', + OPENSSL_USE_STATIC_LIBS: 'OFF', + ] + + def cmakeDefinitions = cmakeOptions.collect { k,v -> "-D$k=$v" }.join(' ') + + return { + rlmNode('docker') { + getArchive() + + def buildEnv = buildDockerEnv('alpine.Dockerfile') + + def environment = environment() + environment << 'UNITTEST_XML=unit-test-report.xml' + environment << "UNITTEST_SUITE_NAME=Alpine-${options.buildType}" + if (options.useEncryption) { + environment << 'UNITTEST_ENCRYPT_ALL=1' + } + + // We don't enable this by default, because using a toolchain with its own sysroot + // prevents CMake from finding system libraries like curl which we use in sync tests. + if (options.useToolchain) { + cmakeDefinitions += " -DCMAKE_TOOLCHAIN_FILE=\"${env.WORKSPACE}/tools/cmake/x86_64-linux-musl.toolchain.cmake\"" + } + + buildEnv.inside { + withEnv(environment) { + try { + dir('build-dir') { + sh "cmake ${cmakeDefinitions} -G Ninja .." + runAndCollectWarnings( + script: 'ninja', + name: "alpine-${options.buildType}-encrypt${options.enableEncryption}-BPNODESIZE_${options.maxBpNodeSize}", + filters: warningFilters, + ) + sh "${ctest_cmd}" + } + } finally { + junit testResults: 'build-dir/test/unit-test-report.xml' + } + } + } + } + } +} + def doCheckSanity(Map options = [:]) { def privileged = ''; diff --git a/alpine.Dockerfile b/alpine.Dockerfile new file mode 100644 index 00000000000..271f79f81ff --- /dev/null +++ b/alpine.Dockerfile @@ -0,0 +1,11 @@ +FROM alpine:3.11 + +RUN apk add --no-cache --update \ + build-base \ + cmake \ + curl-dev \ + git \ + libuv-dev \ + ninja \ + openssl-dev \ + zlib-dev diff --git a/src/realm/CMakeLists.txt b/src/realm/CMakeLists.txt index 03ae01356d1..3b338b37485 100644 --- a/src/realm/CMakeLists.txt +++ b/src/realm/CMakeLists.txt @@ -358,6 +358,10 @@ endif() target_link_libraries(Storage INTERFACE Threads::Threads) +if(TARGET Backtrace::Backtrace) + target_link_libraries(Storage PUBLIC Backtrace::Backtrace) +endif() + if(REALM_ENABLE_ENCRYPTION AND UNIX AND NOT APPLE AND REALM_HAVE_OPENSSL) target_link_libraries(Storage PUBLIC OpenSSL::Crypto) endif() diff --git a/src/realm/util/backtrace.cpp b/src/realm/util/backtrace.cpp index 058d1af6688..d71e70b5209 100644 --- a/src/realm/util/backtrace.cpp +++ b/src/realm/util/backtrace.cpp @@ -23,9 +23,19 @@ #include #include -#if REALM_PLATFORM_APPLE || (defined(__linux__) && !REALM_ANDROID) +#ifndef REALM_HAVE_BACKTRACE +// we detect the backtrace facility in CMake, but if building outside it we assume +// it's available on Apple or Linux/glibc +#define REALM_HAVE_BACKTRACE REALM_PLATFORM_APPLE || (REALM_LINUX && defined(__GNUC__)) +#endif + +#if REALM_HAVE_BACKTRACE +#if defined(REALM_BACKTRACE_HEADER) +#include REALM_BACKTRACE_HEADER +#else #include #endif +#endif using namespace realm::util; @@ -105,7 +115,7 @@ Backtrace& Backtrace::operator=(const Backtrace& other) noexcept Backtrace Backtrace::capture() noexcept { -#if REALM_PLATFORM_APPLE || (defined(__linux__) && !REALM_ANDROID) +#if REALM_HAVE_BACKTRACE static_cast(g_backtrace_unsupported_error); void* callstack[g_backtrace_depth]; int frames = ::backtrace(callstack, g_backtrace_depth); diff --git a/src/realm/util/basic_system_errors.cpp b/src/realm/util/basic_system_errors.cpp index f152fb8ff66..211308c8469 100644 --- a/src/realm/util/basic_system_errors.cpp +++ b/src/realm/util/basic_system_errors.cpp @@ -17,7 +17,7 @@ **************************************************************************/ // opt into the POSIX version of strerror_r -#define _POSIX_C_SOURCE 200112L +#define _POSIX_C_SOURCE 200809L #include #include diff --git a/src/realm/util/config.h.in b/src/realm/util/config.h.in index c9efb97cb97..36a7e099051 100644 --- a/src/realm/util/config.h.in +++ b/src/realm/util/config.h.in @@ -1,12 +1,17 @@ // Version information #cmakedefine REALM_VERSION "@VERSION@" -// Realm-specific configuration +// Feature detection #cmakedefine01 REALM_HAVE_READDIR64 +#cmakedefine01 REALM_HAVE_POSIX_FALLOCATE #cmakedefine01 REALM_HAVE_OPENSSL #cmakedefine01 REALM_HAVE_SECURE_TRANSPORT +#cmakedefine01 REALM_HAVE_PTHREAD_GETNAME +#cmakedefine01 REALM_HAVE_PTHREAD_SETNAME +#cmakedefine01 REALM_HAVE_BACKTRACE #cmakedefine01 REALM_INCLUDE_CERTS -#cmakedefine REALM_MAX_BPNODE_SIZE @REALM_MAX_BPNODE_SIZE@ +#define REALM_MAX_BPNODE_SIZE @REALM_MAX_BPNODE_SIZE@ +#define REALM_BACKTRACE_HEADER <@Backtrace_HEADER@> #cmakedefine01 REALM_ENABLE_ASSERTIONS #cmakedefine01 REALM_ENABLE_ALLOC_SET_ZERO #cmakedefine01 REALM_ENABLE_ENCRYPTION diff --git a/src/realm/util/file.cpp b/src/realm/util/file.cpp index b60834d452b..0fd6aa561ec 100644 --- a/src/realm/util/file.cpp +++ b/src/realm/util/file.cpp @@ -880,7 +880,7 @@ void File::prealloc(size_t size) #endif }; -#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version +#if REALM_HAVE_POSIX_FALLOCATE // Mostly Linux only if (!prealloc_if_supported(0, new_size)) { consume_space_interlocked(); @@ -954,7 +954,7 @@ void File::prealloc(size_t size) #error Please check if/how your OS supports file preallocation #endif -#endif // !(_POSIX_C_SOURCE >= 200112L) +#endif // REALM_HAVE_POSIX_FALLOCATE } @@ -962,7 +962,7 @@ bool File::prealloc_if_supported(SizeType offset, size_t size) { REALM_ASSERT_RELEASE(is_attached()); -#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version +#if REALM_HAVE_POSIX_FALLOCATE REALM_ASSERT_RELEASE(is_prealloc_supported()); @@ -1015,7 +1015,7 @@ bool File::prealloc_if_supported(SizeType offset, size_t size) bool File::is_prealloc_supported() { -#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version +#if REALM_HAVE_POSIX_FALLOCATE return true; #else return false; diff --git a/src/realm/util/thread.cpp b/src/realm/util/thread.cpp index b051f94f943..192dacf58f3 100644 --- a/src/realm/util/thread.cpp +++ b/src/realm/util/thread.cpp @@ -79,7 +79,15 @@ void Thread::join() void Thread::set_name(const std::string& name) { -#if defined _GNU_SOURCE && !REALM_ANDROID && !REALM_PLATFORM_APPLE && !defined(__EMSCRIPTEN__) +#if REALM_PLATFORM_APPLE + int r = pthread_setname_np(name.data()); + if (REALM_UNLIKELY(r != 0)) + throw std::system_error(r, std::system_category(), "pthread_setname_np() failed"); +#elif REALM_HAVE_PTHREAD_SETNAME || (!defined(REALM_HAVE_PTHREAD_SETNAME) && defined(_GNU_SOURCE)) +// Look for the HAVE_ macro defined by CMake. If building outside CMake and the macro wasn't explicitly defined, fall +// back to assuming this is available on Unix-y systems. +#if defined(__linux__) + // Thread names on Linux can only be 16 characters long, including the null terminator const size_t max = 16; size_t n = name.size(); if (n > max - 1) @@ -87,14 +95,13 @@ void Thread::set_name(const std::string& name) char name_2[max]; std::copy(name.data(), name.data() + n, name_2); name_2[n] = '\0'; +#else + const char* name_2 = name.c_str(); +#endif pthread_t id = pthread_self(); int r = pthread_setname_np(id, name_2); if (REALM_UNLIKELY(r != 0)) throw std::system_error(r, std::system_category(), "pthread_setname_np() failed"); -#elif REALM_PLATFORM_APPLE - int r = pthread_setname_np(name.data()); - if (REALM_UNLIKELY(r != 0)) - throw std::system_error(r, std::system_category(), "pthread_setname_np() failed"); #else static_cast(name); #endif @@ -103,7 +110,10 @@ void Thread::set_name(const std::string& name) bool Thread::get_name(std::string& name) noexcept { -#if (defined _GNU_SOURCE && !REALM_ANDROID && !defined(__EMSCRIPTEN__)) || REALM_PLATFORM_APPLE +// Look for the HAVE_ macro defined by CMake. If building outside CMake and the macro wasn't explicitly defined, fall +// back to assuming this is available on Unix-y systems. +#if REALM_HAVE_PTHREAD_GETNAME || \ + (!defined(REALM_HAVE_PTHREAD_GETNAME) && (defined(_GNU_SOURCE) || REALM_PLATFORM_APPLE)) const size_t max = 64; char name_2[max]; pthread_t id = pthread_self(); diff --git a/test/test_util_error.cpp b/test/test_util_error.cpp index 7d9e0ec73d3..9df75428340 100644 --- a/test/test_util_error.cpp +++ b/test/test_util_error.cpp @@ -64,7 +64,12 @@ TEST(BasicSystemErrors_Category) TEST(BasicSystemErrors_Messages) { +#if defined(__linux__) && !defined(__GLIBC__) + // Linux and not glibc implies Musl, which has its own message + const std::string error_message("No error information"); +#else const std::string error_message("Unknown error"); +#endif { std::error_code err = make_error_code(error::address_family_not_supported); diff --git a/test/test_util_file.cpp b/test/test_util_file.cpp index 55e0a4643b5..01d9f2ec777 100644 --- a/test/test_util_file.cpp +++ b/test/test_util_file.cpp @@ -310,6 +310,9 @@ TEST(Utils_File_SystemErrorMessage) std::string_view message = "my message"; #ifdef _WIN32 const char* expected = "my message: too many files open (%1)"; +#elif defined(__linux__) && !defined(__GLIBC__) + // Linux and not glibc implies Musl, which has its own message + const char* expected = "my message: No file descriptors available (%1)"; #else const char* expected = "my message: Too many open files (%1)"; #endif