From b84a9443092d77597ab25a9925ec15e19e2bf567 Mon Sep 17 00:00:00 2001 From: Bhagirath Mehta Date: Thu, 15 Oct 2026 12:57:46 -0500 Subject: [PATCH] Fix exported IPC dependencies for external flatcc runtimes Keep the external runtime namespaced so exported IPC targets do not depend on a caller-defined flatccrt target or link-directory settings. Resolve configuration-specific runtime libraries when finding the package, including after relocating a combined install prefix. Exercise real build-tree and installed-package consumers on Windows, Linux, and macOS CI without downloading test dependencies. Preserve vendored flatcc exports and document installed-package consumption. Files changed: - CMakeLists.txt - cmake/config.cmake.in - ci/scripts/test_cmake_package.py - .github/workflows/build-and-test-ipc.yaml - examples/cmake-ipc/README.md Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7f6f6eb9-6891-4b8c-8e40-e252aec061d4 --- .github/workflows/build-and-test-ipc.yaml | 16 ++ CMakeLists.txt | 19 ++- ci/scripts/test_cmake_package.py | 188 ++++++++++++++++++++++ cmake/config.cmake.in | 37 ++++- examples/cmake-ipc/README.md | 18 +++ 5 files changed, 269 insertions(+), 9 deletions(-) create mode 100644 ci/scripts/test_cmake_package.py diff --git a/.github/workflows/build-and-test-ipc.yaml b/.github/workflows/build-and-test-ipc.yaml index 7f4f01afb..5b3ee30f3 100644 --- a/.github/workflows/build-and-test-ipc.yaml +++ b/.github/workflows/build-and-test-ipc.yaml @@ -26,6 +26,8 @@ on: - main paths: - 'CMakeLists.txt' + - 'cmake/**' + - 'ci/scripts/test_cmake_package.py' - '.github/workflows/build-and-test-ipc.yaml' - 'src/nanoarrow/**' @@ -33,6 +35,20 @@ permissions: contents: read jobs: + test-cmake-package: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-python@v7 + with: + python-version: "3.x" + - name: Test CMake package exports + run: python ci/scripts/test_cmake_package.py --build-dir build/package-tests + test-c-ipc: runs-on: ubuntu-latest diff --git a/CMakeLists.txt b/CMakeLists.txt index 848b933e8..f2e081dac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -216,10 +216,11 @@ if(NANOARROW_IPC) RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) + add_library(nanoarrow::flatccrt ALIAS flatccrt) elseif(NOT NANOARROW_FLATCC_ROOT_DIR) - add_library(flatccrt STATIC IMPORTED) - set_target_properties(flatccrt + add_library(nanoarrow::flatccrt STATIC IMPORTED) + set_target_properties(nanoarrow::flatccrt PROPERTIES IMPORTED_LOCATION "${NANOARROW_FLATCC_LIB_DIR}/${CMAKE_STATIC_LIBRARY_PREFIX}flatccrt${CMAKE_STATIC_LIBRARY_SUFFIX}" INTERFACE_INCLUDE_DIRECTORIES @@ -227,8 +228,8 @@ if(NANOARROW_IPC) elseif(NOT NANOARROW_FLATCC_INCLUDE_DIR) set(NANOARROW_FLATCC_INCLUDE_DIR "${NANOARROW_FLATCC_ROOT_DIR}/include") - add_library(flatccrt STATIC IMPORTED) - set_target_properties(flatccrt + add_library(nanoarrow::flatccrt STATIC IMPORTED) + set_target_properties(nanoarrow::flatccrt PROPERTIES IMPORTED_LOCATION "${NANOARROW_FLATCC_ROOT_DIR}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}flatccrt${CMAKE_STATIC_LIBRARY_SUFFIX}" IMPORTED_LOCATION_DEBUG @@ -239,6 +240,8 @@ if(NANOARROW_IPC) "${NANOARROW_FLATCC_INCLUDE_DIR}") endif() + get_target_property(NANOARROW_FLATCC_EXTERNAL nanoarrow::flatccrt IMPORTED) + if(NANOARROW_IPC_WITH_ZSTD) find_package(zstd REQUIRED) set(NANOARROW_IPC_EXTRA_FLAGS "-DNANOARROW_IPC_WITH_ZSTD") @@ -303,12 +306,12 @@ if(NANOARROW_IPC) add_library(nanoarrow_ipc_static STATIC ${NANOARROW_IPC_BUILD_SOURCES}) target_link_libraries(nanoarrow_ipc_static - PRIVATE flatccrt ${NANOARROW_IPC_EXTRA_LIBS} + PRIVATE nanoarrow::flatccrt ${NANOARROW_IPC_EXTRA_LIBS} PUBLIC nanoarrow_static nanoarrow_coverage_config) add_library(nanoarrow_ipc_shared SHARED ${NANOARROW_IPC_BUILD_SOURCES}) target_link_libraries(nanoarrow_ipc_shared - PRIVATE flatccrt ${NANOARROW_IPC_EXTRA_LIBS_SHARED} + PRIVATE nanoarrow::flatccrt ${NANOARROW_IPC_EXTRA_LIBS_SHARED} PUBLIC nanoarrow_shared nanoarrow_coverage_config) install(FILES src/nanoarrow/nanoarrow_ipc.h src/nanoarrow/nanoarrow_ipc.hpp @@ -343,7 +346,7 @@ if(NANOARROW_IPC AND (NANOARROW_BUILD_INTEGRATION_TESTS OR NANOARROW_BUILD_TESTS target_link_libraries(nanoarrow_ipc_integration PRIVATE nanoarrow_testing_static nanoarrow_ipc_static - flatccrt + nanoarrow::flatccrt nanoarrow_coverage_config ${NANOARROW_IPC_EXTRA_LIBS}) endif() @@ -760,7 +763,7 @@ if(NANOARROW_BUILD_TESTS) nanoarrow_coverage_config) if(NOT (name MATCHES "_hpp_")) - target_link_libraries(nanoarrow_ipc_${name}_test flatccrt) + target_link_libraries(nanoarrow_ipc_${name}_test nanoarrow::flatccrt) endif() if(Arrow_FOUND) diff --git a/ci/scripts/test_cmake_package.py b/ci/scripts/test_cmake_package.py new file mode 100644 index 000000000..7ea2d86ab --- /dev/null +++ b/ci/scripts/test_cmake_package.py @@ -0,0 +1,188 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Exercise build-tree and installed CMake packages without test dependencies.""" + +import argparse +import pathlib +import shutil +import subprocess + + +def run(*args): + subprocess.run([str(arg) for arg in args], check=True) + + +def configure(source, build, config, *options): + run( + "cmake", + "-S", + source, + "-B", + build, + f"-DCMAKE_BUILD_TYPE={config}", + *options, + ) + + +def build_and_install(build, config): + run("cmake", "--build", build, "--config", config, "--parallel", "2") + run("cmake", "--install", build, "--config", config) + + +def consume(source, build, prefix, config, caller_first): + configure( + source, + build, + config, + f"-DCMAKE_PREFIX_PATH={prefix.as_posix()}", + f"-DCALLER_FLATCC_FIRST={caller_first}", + ) + run("cmake", "--build", build, "--config", config, "--parallel", "2") + run("ctest", "--test-dir", build, "-C", config, "--output-on-failure") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--build-dir", required=True, type=pathlib.Path) + args = parser.parse_args() + root = args.build_dir.resolve() + root.mkdir(parents=True, exist_ok=True) + source = pathlib.Path(__file__).resolve().parents[2] + + # Build a real external runtime with the same layout as a package manager. + fixture = root / "flatcc" + fixture.mkdir() + (fixture / "CMakeLists.txt").write_text( + """cmake_minimum_required(VERSION 3.14) +project(flatcc_fixture LANGUAGES C) +if(NOT DEFINED CMAKE_DEBUG_POSTFIX) + set(CMAKE_DEBUG_POSTFIX _d) +endif() +add_library(flatccrt STATIC + "${FLATCC_SOURCE}/src/runtime/builder.c" + "${FLATCC_SOURCE}/src/runtime/emitter.c" + "${FLATCC_SOURCE}/src/runtime/verifier.c" + "${FLATCC_SOURCE}/src/runtime/refmap.c") +target_include_directories(flatccrt PRIVATE "${FLATCC_SOURCE}/include") +set_target_properties(flatccrt PROPERTIES POSITION_INDEPENDENT_CODE ON) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(destination debug/lib) +else() + set(destination lib) +endif() +install(TARGETS flatccrt ARCHIVE DESTINATION "${destination}") +install(DIRECTORY "${FLATCC_SOURCE}/include/" DESTINATION include) +""", + encoding="utf-8", + ) + + consumer = root / "consumer" + consumer.mkdir() + (consumer / "CMakeLists.txt").write_text( + """cmake_minimum_required(VERSION 3.14) +project(nanoarrow_package_consumer LANGUAGES C) +if(CALLER_FLATCC_FIRST) + add_library(flatccrt STATIC IMPORTED) +endif() +find_package(nanoarrow CONFIG REQUIRED) +if(NOT CALLER_FLATCC_FIRST) + add_library(flatccrt STATIC IMPORTED) +endif() +add_executable(consumer main.c) +target_link_libraries(consumer PRIVATE nanoarrow::nanoarrow_ipc_static) +enable_testing() +add_test(NAME nanoarrow-ipc-runtime COMMAND consumer) +""", + encoding="utf-8", + ) + (consumer / "main.c").write_text( + """#include +int main(void) { + struct ArrowIpcDecoder decoder; + int result = ArrowIpcDecoderInit(&decoder); + if (result != 0) return result; + ArrowIpcDecoderReset(&decoder); + return 0; +} +""", + encoding="utf-8", + ) + + for mode in ("external-root", "external-dirs", "vendored"): + prefix = root / f"{mode}-prefix" + configs = ("Release", "Debug") + if mode != "vendored": + for config in configs: + build = root / f"{mode}-flatcc-{config}" + configure( + fixture, + build, + config, + f"-DFLATCC_SOURCE={(source / 'thirdparty' / 'flatcc').as_posix()}", + f"-DCMAKE_INSTALL_PREFIX={prefix.as_posix()}", + *(["-DCMAKE_DEBUG_POSTFIX="] if mode == "external-dirs" else []), + ) + build_and_install(build, config) + + for config in configs: + build = root / f"{mode}-nanoarrow-{config}" + options = [ + "-DNANOARROW_IPC=ON", + "-DCMAKE_INSTALL_LIBDIR=lib", + "-DCMAKE_DEBUG_POSTFIX=_d", + f"-DCMAKE_INSTALL_PREFIX={prefix.as_posix()}", + ] + if mode == "external-root": + options.append(f"-DNANOARROW_FLATCC_ROOT_DIR={prefix.as_posix()}") + elif mode == "external-dirs": + include_dir = prefix / "include" + lib_dir = prefix / ("debug/lib" if config == "Debug" else "lib") + options.extend( + [ + f"-DNANOARROW_FLATCC_INCLUDE_DIR={include_dir.as_posix()}", + f"-DNANOARROW_FLATCC_LIB_DIR={lib_dir.as_posix()}", + ] + ) + configure(source, build, config, *options) + build_and_install(build, config) + for caller_first in ("ON", "OFF"): + consume( + consumer, + root / f"{mode}-build-consumer-{config}-{caller_first}", + build, + config, + caller_first, + ) + + relocated = root / f"{mode}-relocated" + shutil.move(prefix, relocated) + for config in configs: + for caller_first in ("ON", "OFF"): + consume( + consumer, + root / f"{mode}-installed-consumer-{config}-{caller_first}", + relocated, + config, + caller_first, + ) + + print("All CMake package consumers passed.") + + +if __name__ == "__main__": + main() diff --git a/cmake/config.cmake.in b/cmake/config.cmake.in index e1af6abb5..d014c3aeb 100644 --- a/cmake/config.cmake.in +++ b/cmake/config.cmake.in @@ -15,11 +15,46 @@ # specific language governing permissions and limitations # under the License. - @PACKAGE_INIT@ cmake_minimum_required(VERSION @CMAKE_MINIMUM_REQUIRED_VERSION@) +if("@NANOARROW_FLATCC_EXTERNAL@" AND NOT TARGET nanoarrow::flatccrt) + find_library(nanoarrow_FLATCCRT_LIBRARY + NAMES flatccrt + HINTS "${PACKAGE_PREFIX_DIR}/@CMAKE_INSTALL_LIBDIR@" + "@NANOARROW_FLATCC_LIB_DIR@" "@NANOARROW_FLATCC_ROOT_DIR@/lib") + find_library(nanoarrow_FLATCCRT_LIBRARY_DEBUG + NAMES flatccrt_d flatccrt + HINTS "${PACKAGE_PREFIX_DIR}/debug/@CMAKE_INSTALL_LIBDIR@" + "@NANOARROW_FLATCC_ROOT_DIR@/debug/lib" "@NANOARROW_FLATCC_LIB_DIR@" + ${CMAKE_PREFIX_PATH} + PATH_SUFFIXES debug/@CMAKE_INSTALL_LIBDIR@ + NO_DEFAULT_PATH) + if(NOT nanoarrow_FLATCCRT_LIBRARY AND NOT nanoarrow_FLATCCRT_LIBRARY_DEBUG) + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE + "nanoarrow IPC requires the external flatcc runtime library") + return() + endif() + + add_library(nanoarrow::flatccrt UNKNOWN IMPORTED) + if(nanoarrow_FLATCCRT_LIBRARY) + set_property(TARGET nanoarrow::flatccrt + APPEND + PROPERTY IMPORTED_CONFIGURATIONS RELEASE) + set_property(TARGET nanoarrow::flatccrt PROPERTY IMPORTED_LOCATION_RELEASE + "${nanoarrow_FLATCCRT_LIBRARY}") + endif() + if(nanoarrow_FLATCCRT_LIBRARY_DEBUG) + set_property(TARGET nanoarrow::flatccrt + APPEND + PROPERTY IMPORTED_CONFIGURATIONS DEBUG) + set_property(TARGET nanoarrow::flatccrt + PROPERTY IMPORTED_LOCATION_DEBUG "${nanoarrow_FLATCCRT_LIBRARY_DEBUG}") + endif() +endif() + include("${CMAKE_CURRENT_LIST_DIR}/nanoarrow-targets.cmake" REQUIRED) include("${CMAKE_CURRENT_LIST_DIR}/nanoarrow-config-version.cmake" REQUIRED) diff --git a/examples/cmake-ipc/README.md b/examples/cmake-ipc/README.md index bacd9bbe2..f314a6b2d 100644 --- a/examples/cmake-ipc/README.md +++ b/examples/cmake-ipc/README.md @@ -41,3 +41,21 @@ cat ../schema-valid.arrows | ./example_cmake_ipc_app cat ../invalid.arrows | ./example_cmake_ipc_app # Expected 0xFFFFFFFF at start of message but found 0xFFFFFF00 ``` + +## Consuming an installed package + +An installed nanoarrow package built with `NANOARROW_IPC=ON` can also be used +without `FetchContent`: + +```cmake +find_package(nanoarrow CONFIG REQUIRED) +target_link_libraries(my_app PRIVATE nanoarrow::nanoarrow_ipc_static) +``` + +When nanoarrow is built against an external flatcc runtime, package discovery +resolves that runtime as `nanoarrow::flatccrt`, including separate Debug and +Release libraries where available. Consumers do not need to create a +`flatccrt` target. If flatcc is installed in a separate prefix, include that +prefix in `CMAKE_PREFIX_PATH`. The `nanoarrow_FLATCCRT_LIBRARY` and +`nanoarrow_FLATCCRT_LIBRARY_DEBUG` cache variables can also select explicit +runtime library paths.