diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d2cabf731..1c28427ebd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -435,6 +435,15 @@ jobs: # gradlew run: gradle jvmMainClasses jvmTest jsTest jsBrowserTest + build-cmake-package: + name: Test CMake package (static/shared) + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + - name: test + working-directory: tests + run: bash CMakePackageTest.sh + build-rust-linux: name: Build Rust Linux runs-on: ubuntu-24.04 diff --git a/CMake/flatbuffers-config.cmake b/CMake/flatbuffers-config.cmake index 0c32c2fe2b..ccb0b2b4e0 100644 --- a/CMake/flatbuffers-config.cmake +++ b/CMake/flatbuffers-config.cmake @@ -1,4 +1,72 @@ -include("${CMAKE_CURRENT_LIST_DIR}/FlatBuffersTargets.cmake" OPTIONAL) +set(FlatBuffers_known_comps static shared) +set(FlatBuffers_comp_static NO) +set(FlatBuffers_comp_shared NO) +foreach(FlatBuffers_comp IN LISTS ${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS) + if(FlatBuffers_comp IN_LIST FlatBuffers_known_comps) + set(FlatBuffers_comp_${FlatBuffers_comp} YES) + else() + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "FlatBuffers does not recognize component `${FlatBuffers_comp}`.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() + endif() +endforeach() + +if(FlatBuffers_comp_static AND FlatBuffers_comp_shared) + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "FlatBuffers `static` and `shared` components are mutually exclusive.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() +endif() + +set(FlatBuffers_static_targets "${CMAKE_CURRENT_LIST_DIR}/FlatBuffersStaticTargets.cmake") +set(FlatBuffers_shared_targets "${CMAKE_CURRENT_LIST_DIR}/FlatBuffersSharedTargets.cmake") + +macro(FlatBuffers_load_targets type) + if(NOT EXISTS "${FlatBuffers_${type}_targets}") + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "FlatBuffers `${type}` libraries were requested but not found.") + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() + endif() + include("${FlatBuffers_${type}_targets}") +endmacro() + +if(FlatBuffers_comp_static) + FlatBuffers_load_targets(static) +elseif(FlatBuffers_comp_shared) + FlatBuffers_load_targets(shared) +elseif(DEFINED FlatBuffers_SHARED_LIBS AND FlatBuffers_SHARED_LIBS) + FlatBuffers_load_targets(shared) +elseif(DEFINED FlatBuffers_SHARED_LIBS AND NOT FlatBuffers_SHARED_LIBS) + FlatBuffers_load_targets(static) +elseif(BUILD_SHARED_LIBS) + if(EXISTS "${FlatBuffers_shared_targets}") + FlatBuffers_load_targets(shared) + else() + FlatBuffers_load_targets(static) + endif() +else() + if(EXISTS "${FlatBuffers_static_targets}") + FlatBuffers_load_targets(static) + else() + FlatBuffers_load_targets(shared) + endif() +endif() + +# flatbuffers never materializes a separate "_shared"-suffixed physical +# library; flatbuffers::flatbuffers is the only real target. This alias +# exists purely so that consumers hardcoded to the legacy +# flatbuffers::flatbuffers_shared name keep working when the target we just +# loaded happens to be shared. ALIAS of an IMPORTED target needs CMake 3.11+; +# older consumers just don't get the compatibility alias. +if(NOT CMAKE_VERSION VERSION_LESS 3.11 AND NOT TARGET flatbuffers::flatbuffers_shared) + get_target_property(FlatBuffers_imported_type flatbuffers::flatbuffers TYPE) + if(FlatBuffers_imported_type STREQUAL "SHARED_LIBRARY") + add_library(flatbuffers::flatbuffers_shared ALIAS flatbuffers::flatbuffers) + endif() + unset(FlatBuffers_imported_type) +endif() + include("${CMAKE_CURRENT_LIST_DIR}/FlatcTargets.cmake" OPTIONAL) -include("${CMAKE_CURRENT_LIST_DIR}/FlatBuffersSharedTargets.cmake" OPTIONAL) -include("${CMAKE_CURRENT_LIST_DIR}/BuildFlatBuffers.cmake" OPTIONAL) \ No newline at end of file +include("${CMAKE_CURRENT_LIST_DIR}/BuildFlatBuffers.cmake" OPTIONAL) diff --git a/CMakeLists.txt b/CMakeLists.txt index eb5bed523b..1bf9527e09 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,6 @@ option(FLATBUFFERS_BUILD_FLATHASH "Enable the build of flathash" OFF) option(FLATBUFFERS_BUILD_BENCHMARKS "Enable the build of flatbenchmark." OFF) option(FLATBUFFERS_BUILD_GRPCTEST "Enable the build of grpctest" OFF) -option(FLATBUFFERS_BUILD_SHAREDLIB - "Enable the build of the flatbuffers shared library" - OFF) option(FLATBUFFERS_LIBCXX_WITH_CLANG "Force libc++ when using Clang" ON) # NOTE: Sanitizer check only works on Linux & OSX (gcc & llvm). option(FLATBUFFERS_CODE_SANITIZE @@ -437,8 +434,37 @@ else() endif() endif() +# Deprecated: FLATBUFFERS_BUILD_SHAREDLIB + +if(DEFINED FLATBUFFERS_BUILD_SHAREDLIB) + message(DEPRECATION + "FLATBUFFERS_BUILD_SHAREDLIB is deprecated and will be removed in a " + "future release. flatbuffers::flatbuffers is now the only library " + "target; its type follows BUILD_SHARED_LIBS (or FlatBuffers_SHARED_LIBS) " + "instead of a separately-typed flatbuffers::flatbuffers_shared target. " + "Use -DBUILD_SHARED_LIBS=ON in place of -DFLATBUFFERS_BUILD_SHAREDLIB=ON.") + if(FLATBUFFERS_BUILD_SHAREDLIB) + set(BUILD_SHARED_LIBS ON) + if(NOT FLATBUFFERS_BUILD_FLATLIB) + message(DEPRECATION + "FLATBUFFERS_BUILD_SHAREDLIB=ON with FLATBUFFERS_BUILD_FLATLIB=OFF " + "used to build only the shared library, under its own name. That " + "library is now built through FLATBUFFERS_BUILD_FLATLIB itself (as " + "shared, per BUILD_SHARED_LIBS above), so FLATBUFFERS_BUILD_FLATLIB " + "is being forced back ON to preserve the resulting library.") + set(FLATBUFFERS_BUILD_FLATLIB ON) + endif() + endif() +endif() + +# Honor FlatBuffers_SHARED_LIBS to match the install interface +if(DEFINED FlatBuffers_SHARED_LIBS) + set(BUILD_SHARED_LIBS "${FlatBuffers_SHARED_LIBS}") +endif() + if(FLATBUFFERS_BUILD_FLATLIB) - add_library(flatbuffers STATIC ${FlatBuffers_Library_SRCS}) + add_library(flatbuffers ${FlatBuffers_Library_SRCS}) + add_library(flatbuffers::flatbuffers ALIAS flatbuffers) # Attach header directory for when build via add_subdirectory(). target_include_directories(flatbuffers @@ -450,6 +476,21 @@ if(FLATBUFFERS_BUILD_FLATLIB) if(FLATBUFFERS_ENABLE_PCH) add_pch_to_target(flatbuffers include/flatbuffers/pch/pch.h) endif() + + # FlatBuffers use calendar-based versioning and do not provide any ABI + # stability guarantees. Therefore, always use the full version as SOVERSION + # in order to avoid breaking reverse dependencies on upgrades. These + # properties are no-ops when flatbuffers is built STATIC. + set_target_properties(flatbuffers PROPERTIES + SOVERSION "${PROJECT_VERSION}" + VERSION "${PROJECT_VERSION}") + + get_target_property(FlatBuffers_Library_TYPE flatbuffers TYPE) + if(FlatBuffers_Library_TYPE STREQUAL "SHARED_LIBRARY") + # Legacy target name, to be removed eventually. + add_library(flatbuffers_shared ALIAS flatbuffers) + add_library(flatbuffers::flatbuffers_shared ALIAS flatbuffers) + endif() endif() if(FLATBUFFERS_BUILD_FLATC) @@ -482,23 +523,7 @@ if(FLATBUFFERS_BUILD_FLATHASH) target_link_libraries(flathash PRIVATE $) endif() -if(FLATBUFFERS_BUILD_SHAREDLIB) - add_library(flatbuffers_shared SHARED ${FlatBuffers_Library_SRCS}) - target_link_libraries(flatbuffers_shared PRIVATE $) - # FlatBuffers use calendar-based versioning and do not provide any ABI - # stability guarantees. Therefore, always use the full version as SOVERSION - # in order to avoid breaking reverse dependencies on upgrades. - set(FlatBuffers_Library_SONAME_FULL "${PROJECT_VERSION}") - set_target_properties(flatbuffers_shared PROPERTIES - OUTPUT_NAME flatbuffers - SOVERSION "${FlatBuffers_Library_SONAME_FULL}" - VERSION "${FlatBuffers_Library_SONAME_FULL}") - if(FLATBUFFERS_ENABLE_PCH) - add_pch_to_target(flatbuffers_shared include/flatbuffers/pch/pch.h) - endif() -endif() - -function(compile_schema SRC_FBS OPT SUFFIX OUT_GEN_FILE) +function(compile_schema SRC_FBS OPT SUFFIX OUT_GEN_FILE) get_filename_component(SRC_FBS_DIR ${SRC_FBS} PATH) string(REGEX REPLACE "\\.fbs$" "${SUFFIX}.h" GEN_HEADER ${SRC_FBS}) add_custom_command( @@ -643,11 +668,23 @@ if(FLATBUFFERS_INSTALL) install( TARGETS flatbuffers EXPORT FlatBuffersTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) + # flatbuffers::flatbuffers is exported under a type-specific file name + # so that a packager who wants to offer both flavors can run two separate + # configure/build/install passes into the same prefix. The CMake package + # picks between them at find_package() time. + if(FlatBuffers_Library_TYPE STREQUAL "SHARED_LIBRARY") + set(FlatBuffers_Targets_FILE "FlatBuffersSharedTargets.cmake") + else() + set(FlatBuffers_Targets_FILE "FlatBuffersStaticTargets.cmake") + endif() + install(EXPORT FlatBuffersTargets - FILE FlatBuffersTargets.cmake + FILE "${FlatBuffers_Targets_FILE}" NAMESPACE flatbuffers:: DESTINATION ${FB_CMAKE_DIR} ) @@ -667,24 +704,7 @@ if(FLATBUFFERS_INSTALL) ) endif() - if(FLATBUFFERS_BUILD_SHAREDLIB) - install( - TARGETS flatbuffers_shared EXPORT FlatBuffersSharedTargets - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} - ) - - install( - EXPORT FlatBuffersSharedTargets - FILE FlatBuffersSharedTargets.cmake - NAMESPACE flatbuffers:: - DESTINATION ${FB_CMAKE_DIR} - ) - endif() - - if(FLATBUFFERS_BUILD_SHAREDLIB OR FLATBUFFERS_BUILD_FLATLIB) + if(FLATBUFFERS_BUILD_FLATLIB) configure_file(CMake/flatbuffers.pc.in flatbuffers.pc @ONLY) install( FILES "${CMAKE_CURRENT_BINARY_DIR}/flatbuffers.pc" diff --git a/tests/CMakeConsumerTest/CMakeLists.txt b/tests/CMakeConsumerTest/CMakeLists.txt new file mode 100644 index 0000000000..688fbb939b --- /dev/null +++ b/tests/CMakeConsumerTest/CMakeLists.txt @@ -0,0 +1,32 @@ +# Minimal downstream consumer used by CMakePackageTest.sh to exercise +# find_package(FlatBuffers) against differently-packaged installs (static +# only, shared only, both) and the component/variable/BUILD_SHARED_LIBS +# precedence chain in flatbuffers-config.cmake. +# +# This project is never add_subdirectory()'d into the main flatbuffers +# build; it is always configured standalone, against an install tree, with +# CMAKE_PREFIX_PATH pointing at the package under test. +cmake_minimum_required(VERSION 3.11) +project(flatbuffers_consumer_test CXX) + +# Optional COMPONENTS argument to pass through to find_package(FlatBuffers), +# e.g. -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=static +set(FLATBUFFERS_CONSUMER_TEST_COMPONENTS "" CACHE STRING + "COMPONENTS argument to pass to find_package(FlatBuffers), if any") + +if(FLATBUFFERS_CONSUMER_TEST_COMPONENTS) + find_package(FlatBuffers REQUIRED COMPONENTS ${FLATBUFFERS_CONSUMER_TEST_COMPONENTS}) +else() + find_package(FlatBuffers REQUIRED) +endif() + +add_executable(consumer_canonical consumer.cpp) +target_link_libraries(consumer_canonical PRIVATE flatbuffers::flatbuffers) + +if(TARGET flatbuffers::flatbuffers_shared) + add_executable(consumer_legacy_shared consumer.cpp) + target_link_libraries(consumer_legacy_shared PRIVATE flatbuffers::flatbuffers_shared) +endif() + +get_target_property(FLATBUFFERS_CONSUMER_TEST_TYPE flatbuffers::flatbuffers TYPE) +message(STATUS "flatbuffers::flatbuffers TYPE = ${FLATBUFFERS_CONSUMER_TEST_TYPE}") diff --git a/tests/CMakeConsumerTest/consumer.cpp b/tests/CMakeConsumerTest/consumer.cpp new file mode 100644 index 0000000000..7f585c41ff --- /dev/null +++ b/tests/CMakeConsumerTest/consumer.cpp @@ -0,0 +1,6 @@ +#include + +int main() { + flatbuffers::FlatBufferBuilder builder; + return 0; +} diff --git a/tests/CMakePackageTest.sh b/tests/CMakePackageTest.sh new file mode 100755 index 0000000000..3971be56c2 --- /dev/null +++ b/tests/CMakePackageTest.sh @@ -0,0 +1,128 @@ +#!/usr/bin/env bash +# +# Exercises the flatbuffers CMake package's static/shared packaging story: +# a single canonical flatbuffers::flatbuffers target whose type is resolved +# at find_package() time via COMPONENTS, FlatBuffers_SHARED_LIBS, +# BUILD_SHARED_LIBS, and (failing all of those) whichever variant is +# actually installed. See flatbuffers-config.cmake. +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +CONSUMER_DIR="${ROOT}/tests/CMakeConsumerTest" +WORK="$(mktemp -d)" +trap 'rm -rf "${WORK}"' EXIT + +NPROC="$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 2)" + +build_and_install() { + local name="$1" install_prefix="$2" + shift 2 + local build_dir="${WORK}/${name}-build" + cmake -S "${ROOT}" -B "${build_dir}" \ + -DCMAKE_INSTALL_PREFIX="${install_prefix}" \ + -DFLATBUFFERS_BUILD_TESTS=OFF \ + -DFLATBUFFERS_BUILD_FLATC=OFF \ + -DFLATBUFFERS_BUILD_FLATHASH=OFF \ + "$@" \ + > "${build_dir}.configure.log" 2>&1 + cmake --build "${build_dir}" --target flatbuffers -j"${NPROC}" \ + > "${build_dir}.build.log" 2>&1 + cmake --install "${build_dir}" > "${build_dir}.install.log" 2>&1 +} + +# configure_consumer PREFIX [extra cmake args...] +# Leaves the consumer build directory in $CONSUMER_BUILD_DIR on success. +configure_consumer() { + local prefix="$1" + shift + CONSUMER_BUILD_DIR="${WORK}/consumer-$$-${RANDOM}" + cmake -S "${CONSUMER_DIR}" -B "${CONSUMER_BUILD_DIR}" \ + -DCMAKE_PREFIX_PATH="${prefix}" \ + "$@" \ + > "${CONSUMER_BUILD_DIR}.log" 2>&1 +} + +fail() { + echo "FAIL: $1" >&2 + echo "--- log: ${2:-} ---" >&2 + [ -n "${2:-}" ] && cat "$2" >&2 + exit 1 +} + +# expect_type PREFIX WANT_TYPE [extra cmake args...] +# Configures and builds the consumer; asserts flatbuffers::flatbuffers +# resolved to WANT_TYPE (STATIC_LIBRARY or SHARED_LIBRARY) and that the +# resulting binaries run. +expect_type() { + local prefix="$1" want_type="$2" + shift 2 + echo "-- expect ${want_type}: prefix=${prefix} $* --" + if ! configure_consumer "${prefix}" "$@"; then + fail "expected successful configure" "${CONSUMER_BUILD_DIR}.log" + fi + if ! grep -q "TYPE = ${want_type}" "${CONSUMER_BUILD_DIR}.log"; then + fail "expected TYPE = ${want_type}" "${CONSUMER_BUILD_DIR}.log" + fi + if ! cmake --build "${CONSUMER_BUILD_DIR}" >> "${CONSUMER_BUILD_DIR}.log" 2>&1; then + fail "expected successful build" "${CONSUMER_BUILD_DIR}.log" + fi + "${CONSUMER_BUILD_DIR}/consumer_canonical" \ + || fail "consumer_canonical did not run" "${CONSUMER_BUILD_DIR}.log" + if [ -x "${CONSUMER_BUILD_DIR}/consumer_legacy_shared" ]; then + "${CONSUMER_BUILD_DIR}/consumer_legacy_shared" \ + || fail "consumer_legacy_shared did not run" "${CONSUMER_BUILD_DIR}.log" + fi +} + +# expect_not_found PREFIX [extra cmake args...] +# Asserts find_package(FlatBuffers) fails (not just any configure error). +expect_not_found() { + local prefix="$1" + shift + echo "-- expect NOT_FOUND: prefix=${prefix} $* --" + if configure_consumer "${prefix}" "$@"; then + fail "expected configure to fail, but it succeeded" "${CONSUMER_BUILD_DIR}.log" + fi + if ! grep -q "considered to be NOT FOUND" "${CONSUMER_BUILD_DIR}.log"; then + fail "expected a FlatBuffers NOT_FOUND message" "${CONSUMER_BUILD_DIR}.log" + fi +} + +echo "=== Building static-only package ===" +STATIC_INSTALL="${WORK}/static-install" +build_and_install static "${STATIC_INSTALL}" + +echo "=== Building shared-only package (-DBUILD_SHARED_LIBS=ON) ===" +SHARED_INSTALL="${WORK}/shared-install" +build_and_install shared "${SHARED_INSTALL}" -DBUILD_SHARED_LIBS=ON + +echo "=== Building shared-only package via deprecated Fedora-style flags ===" +# Matches Fedora's flatbuffers.spec exactly: -DFLATBUFFERS_BUILD_SHAREDLIB=ON +# -DFLATBUFFERS_BUILD_FLATLIB=OFF. The deprecation shim must still produce a +# working flatbuffers::flatbuffers (shared) with no spec changes required. +LEGACY_SHARED_INSTALL="${WORK}/legacy-shared-install" +build_and_install legacy-shared "${LEGACY_SHARED_INSTALL}" \ + -DFLATBUFFERS_BUILD_SHAREDLIB=ON -DFLATBUFFERS_BUILD_FLATLIB=OFF + +# --- Precedence chain: static-only install --- +expect_type "${STATIC_INSTALL}" STATIC_LIBRARY +expect_type "${STATIC_INSTALL}" STATIC_LIBRARY -DFlatBuffers_SHARED_LIBS=OFF +expect_type "${STATIC_INSTALL}" STATIC_LIBRARY -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=static +expect_type "${STATIC_INSTALL}" STATIC_LIBRARY -DBUILD_SHARED_LIBS=ON # falls back: no shared installed +expect_not_found "${STATIC_INSTALL}" -DFlatBuffers_SHARED_LIBS=ON +expect_not_found "${STATIC_INSTALL}" -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=shared +expect_not_found "${STATIC_INSTALL}" -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS="static;shared" +expect_not_found "${STATIC_INSTALL}" -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=bogus + +# --- Precedence chain: shared-only install --- +expect_type "${SHARED_INSTALL}" SHARED_LIBRARY +expect_type "${SHARED_INSTALL}" SHARED_LIBRARY -DFlatBuffers_SHARED_LIBS=ON +expect_type "${SHARED_INSTALL}" SHARED_LIBRARY -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=shared +expect_type "${SHARED_INSTALL}" SHARED_LIBRARY -DBUILD_SHARED_LIBS=OFF # falls back: no static installed +expect_not_found "${SHARED_INSTALL}" -DFlatBuffers_SHARED_LIBS=OFF +expect_not_found "${SHARED_INSTALL}" -DFLATBUFFERS_CONSUMER_TEST_COMPONENTS=static + +# --- Deprecation shim produces a working shared install --- +expect_type "${LEGACY_SHARED_INSTALL}" SHARED_LIBRARY + +echo "=== All CMake packaging scenarios passed ==="