# CMake integration for the readcon-db C/C++ ABI.
#
# include/readcon-db.h is hand-written and shipped. cbindgen is not used.
# Rustc/cargo compile the library; CMake only orchestrates cargo rustc,
# installs headers/libs, and writes cmake-config + pkg-config files.
#
# Consumption:
# add_subdirectory / FetchContent_MakeAvailable -> readcon-db::{shared,static}
# find_package(readcon-db) after cmake --install
# pkg-config --libs readcon-db
cmake_minimum_required(VERSION 3.22)
if (${CMAKE_CURRENT_SOURCE_DIR} STREQUAL ${CMAKE_SOURCE_DIR})
set(READCON_DB_MAIN_PROJECT ON)
else()
set(READCON_DB_MAIN_PROJECT OFF)
endif()
if(${READCON_DB_MAIN_PROJECT} AND NOT "${CACHED_LAST_CMAKE_VERSION}" VERSION_EQUAL ${CMAKE_VERSION})
set(CACHED_LAST_CMAKE_VERSION ${CMAKE_VERSION} CACHE INTERNAL "Last version of cmake used to configure")
message(STATUS "Running CMake version ${CMAKE_VERSION}")
endif()
if (POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
if (POLICY CMP0077)
cmake_policy(SET CMP0077 NEW)
endif()
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml" CARGO_TOML_CONTENT)
set(READCON_DB_VERSION "")
foreach(line ${CARGO_TOML_CONTENT})
if (line MATCHES "^version = \"([0-9]+\\.[0-9]+\\.[0-9]+.*)\"")
set(READCON_DB_VERSION "${CMAKE_MATCH_1}")
break()
endif()
endforeach()
if (READCON_DB_VERSION STREQUAL "")
message(FATAL_ERROR "could not parse version from ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml")
endif()
string(REGEX REPLACE "([0-9]*)\\.([0-9]*)\\.([0-9]*).*" "\\1.\\2.\\3" READCON_DB_CMAKE_VERSION ${READCON_DB_VERSION})
project(readcon-db
VERSION ${READCON_DB_CMAKE_VERSION}
LANGUAGES C
)
set(PROJECT_VERSION ${READCON_DB_VERSION})
if(${READCON_DB_MAIN_PROJECT})
message(STATUS "Building readcon-db v${READCON_DB_VERSION}")
endif()
option(BUILD_SHARED_LIBS "Use a shared library by default instead of a static one" ON)
option(READCON_DB_INSTALL_BOTH_STATIC_SHARED "Install both shared and static libraries" ON)
set(READCON_DB_CARGO_FEATURES "" CACHE STRING "Comma-free cargo features to enable (space-separated)")
set(BIN_INSTALL_DIR "bin" CACHE PATH "Path relative to CMAKE_INSTALL_PREFIX where to install binaries/DLL")
set(LIB_INSTALL_DIR "lib" CACHE PATH "Path relative to CMAKE_INSTALL_PREFIX where to install libraries")
set(INCLUDE_INSTALL_DIR "include" CACHE PATH "Path relative to CMAKE_INSTALL_PREFIX where to install headers")
set(RUST_BUILD_TARGET "" CACHE STRING "Cross-compilation target for rust code. Leave empty to build for the host")
set(EXTRA_RUST_FLAGS "" CACHE STRING "Flags used to build rust code")
mark_as_advanced(RUST_BUILD_TARGET EXTRA_RUST_FLAGS)
set(CMAKE_MACOSX_RPATH ON)
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIB_INSTALL_DIR}")
if (${READCON_DB_MAIN_PROJECT})
if("${CMAKE_BUILD_TYPE}" STREQUAL "" AND "${CMAKE_CONFIGURATION_TYPES}" STREQUAL "")
message(STATUS "Setting build type to 'release' as none was specified.")
set(CMAKE_BUILD_TYPE "release"
CACHE STRING
"Choose the type of build, options are: debug or release"
FORCE)
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS release debug)
endif()
endif()
if("${CMAKE_BUILD_TYPE}" STREQUAL "")
set(CMAKE_BUILD_TYPE "release")
endif()
if(${READCON_DB_MAIN_PROJECT} AND NOT "${CACHED_LAST_CMAKE_BUILD_TYPE}" STREQUAL ${CMAKE_BUILD_TYPE})
set(CACHED_LAST_CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE INTERNAL "Last build type used in configuration")
message(STATUS "Building readcon-db in ${CMAKE_BUILD_TYPE} mode")
endif()
set(READCON_DB_C_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/include/readcon-db.h")
if (NOT EXISTS ${READCON_DB_C_HEADER})
message(FATAL_ERROR
"missing shipped C header ${READCON_DB_C_HEADER}. "
"The header is hand-maintained next to src/ffi.rs; cbindgen is not used.")
endif()
find_program(CARGO_EXE "cargo" DOC "path to cargo (Rust build system)")
if (NOT CARGO_EXE)
message(FATAL_ERROR
"could not find cargo, please make sure the Rust compiler is installed \
(see https://www.rust-lang.org/tools/install) or set CARGO_EXE")
endif()
execute_process(
COMMAND ${CARGO_EXE} "--version" "--verbose"
RESULT_VARIABLE CARGO_STATUS
OUTPUT_VARIABLE CARGO_VERSION_RAW
)
if(CARGO_STATUS AND NOT CARGO_STATUS EQUAL 0)
message(FATAL_ERROR "could not run cargo; install the Rust toolchain")
endif()
if (CARGO_VERSION_RAW MATCHES "cargo ([0-9]+\\.[0-9]+\\.[0-9]+).*")
set(CARGO_VERSION "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR "failed to determine cargo version, output was: ${CARGO_VERSION_RAW}")
endif()
if(NOT "${CACHED_LAST_CARGO_VERSION}" STREQUAL ${CARGO_VERSION})
set(CACHED_LAST_CARGO_VERSION ${CARGO_VERSION} CACHE INTERNAL "Last version of cargo used in configuration")
if(${READCON_DB_MAIN_PROJECT})
message(STATUS "Using cargo version ${CARGO_VERSION} at ${CARGO_EXE}")
endif()
set(CARGO_VERSION_CHANGED TRUE)
endif()
set(CARGO_BUILD_ARG "")
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock)
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--locked")
endif()
if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/vendor)
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--offline")
endif()
string(TOLOWER ${CMAKE_BUILD_TYPE} BUILD_TYPE)
if ("${BUILD_TYPE}" STREQUAL "debug")
set(CARGO_BUILD_TYPE "debug")
elseif("${BUILD_TYPE}" STREQUAL "release" OR "${BUILD_TYPE}" STREQUAL "relwithdebinfo" OR "${BUILD_TYPE}" STREQUAL "minsizerel")
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--release")
set(CARGO_BUILD_TYPE "release")
else()
message(FATAL_ERROR "unsupported build type: ${CMAKE_BUILD_TYPE}")
endif()
set(CARGO_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR}/cargo-target)
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--target-dir=${CARGO_TARGET_DIR}")
if (NOT "${READCON_DB_CARGO_FEATURES}" STREQUAL "")
string(REPLACE " " "," _feats "${READCON_DB_CARGO_FEATURES}")
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--features=${_feats}")
endif()
if ("${RUST_BUILD_TARGET}" STREQUAL "")
if (CARGO_VERSION_RAW MATCHES "host: ([a-zA-Z0-9_\\-]*)\n")
set(RUST_HOST_TARGET "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR "failed to determine host target, output was: ${CARGO_VERSION_RAW}")
endif()
set(CARGO_OUTPUT_DIR "${CARGO_TARGET_DIR}/${CARGO_BUILD_TYPE}")
set(RUST_BUILD_TARGET ${RUST_HOST_TARGET})
else()
if (${READCON_DB_MAIN_PROJECT})
message(STATUS "Cross-compiling to ${RUST_BUILD_TARGET}")
endif()
set(CARGO_BUILD_ARG "${CARGO_BUILD_ARG};--target=${RUST_BUILD_TARGET}")
set(CARGO_OUTPUT_DIR "${CARGO_TARGET_DIR}/${RUST_BUILD_TARGET}/${CARGO_BUILD_TYPE}")
endif()
# rustc native-static-libs for consumers of the static archive
if (CARGO_VERSION_CHANGED OR NOT DEFINED CARGO_DEFAULT_LIBRARIES)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/tempdir.cmake)
get_tempdir(TMPDIR)
execute_process(
COMMAND "${CARGO_EXE}" new --lib _cargo_required_libs
WORKING_DIRECTORY "${TMPDIR}"
RESULT_VARIABLE cargo_new_result
OUTPUT_VARIABLE cargo_new_stdout
ERROR_VARIABLE cargo_new_stderr
)
if (cargo_new_result)
message(FATAL_ERROR
"could not create empty project to find cargo's default static libs (error ${cargo_new_result})\n"
"stdout:\n${cargo_new_stdout}\nstderr:\n${cargo_new_stderr}")
endif()
file(APPEND "${TMPDIR}/_cargo_required_libs/Cargo.toml" "[lib]\ncrate-type=[\"staticlib\"]\n")
execute_process(
COMMAND ${CARGO_EXE} rustc --color never --target=${RUST_BUILD_TARGET} -- --print=native-static-libs
WORKING_DIRECTORY "${TMPDIR}/_cargo_required_libs"
RESULT_VARIABLE cargo_rustc_result
OUTPUT_VARIABLE cargo_rustc_stdout
ERROR_VARIABLE cargo_rustc_stderr
)
file(REMOVE_RECURSE "${TMPDIR}")
if(cargo_rustc_result)
message(FATAL_ERROR
"could not extract cargo's default static libs (error ${cargo_rustc_result})\n"
"stdout:\n${cargo_rustc_stdout}\nstderr:\n${cargo_rustc_stderr}")
endif()
if(cargo_rustc_stderr MATCHES "native-static-libs: ([^\r\n]+)\r?\n")
string(REPLACE " " ";" "libs_list" "${CMAKE_MATCH_1}")
set(stripped_lib_list "")
foreach(lib ${libs_list})
string(REGEX REPLACE "^-l" "" "stripped_lib" "${lib}")
string(REGEX REPLACE "\\.lib$" "" "stripped_lib" "${stripped_lib}")
list(APPEND stripped_lib_list "${stripped_lib}")
endforeach()
list(TRANSFORM stripped_lib_list REPLACE "^msvcrt$" "\$<\$<CONFIG:Debug>:msvcrtd>")
list(REMOVE_DUPLICATES stripped_lib_list)
set(CARGO_DEFAULT_LIBRARIES "${stripped_lib_list}" CACHE INTERNAL "list of implicitly linked libraries")
if (${READCON_DB_MAIN_PROJECT})
message(STATUS "Cargo default link libraries are: ${CARGO_DEFAULT_LIBRARIES}")
endif()
else()
message(FATAL_ERROR "could not find cargo's default static libs in: `${cargo_rustc_stderr}`")
endif()
endif()
file(GLOB_RECURSE ALL_RUST_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml
${CMAKE_CURRENT_SOURCE_DIR}/Cargo.lock
${CMAKE_CURRENT_SOURCE_DIR}/build.rs
${CMAKE_CURRENT_SOURCE_DIR}/src/*.rs
)
add_library(readcon-db::shared SHARED IMPORTED GLOBAL)
set(READCON_DB_SHARED_LOCATION "${CARGO_OUTPUT_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}readcon_db${CMAKE_SHARED_LIBRARY_SUFFIX}")
set(READCON_DB_IMPLIB_LOCATION "${READCON_DB_SHARED_LOCATION}.lib")
add_library(readcon-db::static STATIC IMPORTED GLOBAL)
set(READCON_DB_STATIC_LOCATION "${CARGO_OUTPUT_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}readcon_db${CMAKE_STATIC_LIBRARY_SUFFIX}")
get_filename_component(READCON_DB_SHARED_LIB_NAME ${READCON_DB_SHARED_LOCATION} NAME)
get_filename_component(READCON_DB_IMPLIB_NAME ${READCON_DB_IMPLIB_LOCATION} NAME)
get_filename_component(READCON_DB_STATIC_LIB_NAME ${READCON_DB_STATIC_LOCATION} NAME)
if (UNIX)
if (APPLE)
set(CARGO_RUSTC_ARGS "-Clink-arg=-Wl,-install_name,@rpath/${READCON_DB_SHARED_LIB_NAME}")
else()
set(CARGO_RUSTC_ARGS "-Clink-arg=-Wl,-soname,${READCON_DB_SHARED_LIB_NAME}")
endif()
else()
set(CARGO_RUSTC_ARGS "")
endif()
if (NOT "${EXTRA_RUST_FLAGS}" STREQUAL "")
set(CARGO_RUSTC_ARGS "${CARGO_RUSTC_ARGS};${EXTRA_RUST_FLAGS}")
endif()
set(CARGO_ENV "")
if (NOT "${CMAKE_OSX_DEPLOYMENT_TARGET}" STREQUAL "")
list(APPEND CARGO_ENV "MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}")
endif()
if (NOT "$ENV{RUSTC_WRAPPER}" STREQUAL "")
list(APPEND CARGO_ENV "RUSTC_WRAPPER=$ENV{RUSTC_WRAPPER}")
endif()
if (READCON_DB_INSTALL_BOTH_STATIC_SHARED)
set(CARGO_RUSTC_CRATE_TYPES "--crate-type=cdylib;--crate-type=staticlib")
set(CARGO_OUTPUTS ${READCON_DB_SHARED_LOCATION} ${READCON_DB_STATIC_LOCATION})
if (WIN32)
list(APPEND CARGO_OUTPUTS ${READCON_DB_IMPLIB_LOCATION})
endif()
else()
if (BUILD_SHARED_LIBS)
set(CARGO_RUSTC_CRATE_TYPES "--crate-type=cdylib")
set(CARGO_OUTPUTS ${READCON_DB_SHARED_LOCATION})
if (WIN32)
list(APPEND CARGO_OUTPUTS ${READCON_DB_IMPLIB_LOCATION})
endif()
else()
set(CARGO_RUSTC_CRATE_TYPES "--crate-type=staticlib")
set(CARGO_OUTPUTS ${READCON_DB_STATIC_LOCATION})
endif()
endif()
add_custom_command(
OUTPUT ${CARGO_OUTPUTS}
COMMAND ${CMAKE_COMMAND} -E env ${CARGO_ENV}
${CARGO_EXE} rustc --lib ${CARGO_BUILD_ARG} --manifest-path ${CMAKE_CURRENT_SOURCE_DIR}/Cargo.toml -- ${CARGO_RUSTC_CRATE_TYPES} ${CARGO_RUSTC_ARGS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${ALL_RUST_SOURCES}
COMMENT "Building readcon-db C ABI with cargo"
VERBATIM
)
add_custom_target(cargo-build-readcon-db ALL DEPENDS ${CARGO_OUTPUTS})
add_dependencies(readcon-db::shared cargo-build-readcon-db)
add_dependencies(readcon-db::static cargo-build-readcon-db)
set(READCON_DB_HEADERS
${READCON_DB_C_HEADER}
)
set(READCON_DB_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/)
set_target_properties(readcon-db::shared PROPERTIES
IMPORTED_LOCATION ${READCON_DB_SHARED_LOCATION}
INTERFACE_INCLUDE_DIRECTORIES ${READCON_DB_INCLUDE_DIR}
BUILD_VERSION "${READCON_DB_VERSION}"
)
if (WIN32)
set_target_properties(readcon-db::shared PROPERTIES
IMPORTED_IMPLIB ${READCON_DB_IMPLIB_LOCATION}
)
endif()
set_target_properties(readcon-db::static PROPERTIES
IMPORTED_LOCATION ${READCON_DB_STATIC_LOCATION}
INTERFACE_INCLUDE_DIRECTORIES ${READCON_DB_INCLUDE_DIR}
INTERFACE_LINK_LIBRARIES "${CARGO_DEFAULT_LIBRARIES}"
BUILD_VERSION "${READCON_DB_VERSION}"
)
if (BUILD_SHARED_LIBS)
add_library(readcon-db ALIAS readcon-db::shared)
add_library(readcon-db::readcon-db ALIAS readcon-db::shared)
else()
add_library(readcon-db ALIAS readcon-db::static)
add_library(readcon-db::readcon-db ALIAS readcon-db::static)
endif()
#------------------------------------------------------------------------------#
# Installation: headers, libs, cmake package, pkg-config
#------------------------------------------------------------------------------#
include(CMakePackageConfigHelpers)
# Config file lives in lib/cmake/readcon-db; prefix is two dirs up.
set(PACKAGE_RELATIVE_PATH "../..")
configure_package_config_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/readcon-db-config.in.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/readcon-db-config.cmake"
INSTALL_DESTINATION ${LIB_INSTALL_DIR}/cmake/readcon-db
)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/readcon-db-config-version.in.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/readcon-db-config-version.cmake"
@ONLY
)
set(PC_LIBS_PRIVATE "")
foreach(_lib ${CARGO_DEFAULT_LIBRARIES})
if (NOT _lib MATCHES "^\\$")
string(APPEND PC_LIBS_PRIVATE "-l${_lib} ")
endif()
endforeach()
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/cmake/readcon-db.pc.in"
"${CMAKE_CURRENT_BINARY_DIR}/readcon-db.pc"
@ONLY
)
install(FILES ${READCON_DB_HEADERS} DESTINATION ${INCLUDE_INSTALL_DIR})
if (READCON_DB_INSTALL_BOTH_STATIC_SHARED OR BUILD_SHARED_LIBS)
if (WIN32)
install(FILES ${READCON_DB_SHARED_LOCATION} DESTINATION ${BIN_INSTALL_DIR})
install(FILES ${READCON_DB_IMPLIB_LOCATION} DESTINATION ${LIB_INSTALL_DIR})
else()
install(FILES ${READCON_DB_SHARED_LOCATION} DESTINATION ${LIB_INSTALL_DIR})
endif()
endif()
if (READCON_DB_INSTALL_BOTH_STATIC_SHARED OR NOT BUILD_SHARED_LIBS)
install(FILES ${READCON_DB_STATIC_LOCATION} DESTINATION ${LIB_INSTALL_DIR})
endif()
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/readcon-db-config-version.cmake
${CMAKE_CURRENT_BINARY_DIR}/readcon-db-config.cmake
DESTINATION ${LIB_INSTALL_DIR}/cmake/readcon-db
)
install(FILES
${CMAKE_CURRENT_BINARY_DIR}/readcon-db.pc
DESTINATION ${LIB_INSTALL_DIR}/pkgconfig
)