From 1e201dc8d8e5101c66d3091dd83b9fe90ac8f20b Mon Sep 17 00:00:00 2001 From: Aaron Jomy Date: Fri, 4 Sep 2026 09:57:49 +0200 Subject: [PATCH] [build] Consume an external CppInterOp via find_package --- CMakeLists.txt | 112 +++--------- cmake/AddCppInterOp.cmake | 297 +++++++++++++++++++++++--------- src/interop/interop_wrapper.cxx | 41 +++-- 3 files changed, 264 insertions(+), 186 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 465d158..20d2dc6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,6 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -include(ExternalProject) include(GNUInstallDirs) # This option won't make a lot of sense since we only ship the shared library in site-packages @@ -32,63 +31,6 @@ endif() set(CPPJIT_LLVM_VERSION_MIN 20) set(CPPJIT_LLVM_VERSION_MAX 22) -set(_llvm_hints "") - -if(DEFINED ENV{CONDA_PREFIX}) - list(APPEND _llvm_hints "$ENV{CONDA_PREFIX}/lib/cmake/llvm") -endif() - -if(DEFINED LLVM_DIR) - # An explicit LLVM_DIR is authoritative: fail instead of falling back to a - # different LLVM than the one requested. A failed find_package resets - # LLVM_DIR to -NOTFOUND, so keep the requested value for the message. - set(_llvm_dir_arg "${LLVM_DIR}") - find_package(LLVM CONFIG PATHS "${LLVM_DIR}" NO_DEFAULT_PATH) - if(NOT LLVM_FOUND) - message(FATAL_ERROR - "No LLVMConfig.cmake under LLVM_DIR (${_llvm_dir_arg}); expected " - "/lib/cmake/llvm") - endif() -else() - find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) - if(NOT LLVM_FOUND) - message(FATAL_ERROR - "No LLVM CMake package found. Install LLVM " - "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX} development packages " - "(apt: llvm-${CPPJIT_LLVM_VERSION_MAX}-dev libclang-${CPPJIT_LLVM_VERSION_MAX}-dev; " - "conda: llvmdev clangdev), or point cppjit at your own LLVM build with " - "-DLLVM_DIR=/lib/cmake/llvm " - "(pip: --config-settings=cmake.define.LLVM_DIR=...)") - endif() -endif() - -message(STATUS "Found LLVM ${LLVM_VERSION} at ${LLVM_DIR}") -if(LLVM_VERSION_MAJOR LESS CPPJIT_LLVM_VERSION_MIN OR - LLVM_VERSION_MAJOR GREATER CPPJIT_LLVM_VERSION_MAX) - message(FATAL_ERROR - "LLVM ${LLVM_VERSION} is unsupported: the currently supported " - "CppInterOp version (${CPPINTEROP_GIT_TAG}) only supports LLVM " - "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX}") -endif() - -if(DEFINED Clang_DIR) - # An explicit Clang_DIR is authoritative, like LLVM_DIR above. - set(_clang_dir_arg "${Clang_DIR}") - find_package(Clang CONFIG PATHS "${Clang_DIR}" NO_DEFAULT_PATH) - if(NOT Clang_FOUND) - message(FATAL_ERROR - "No ClangConfig.cmake under Clang_DIR (${_clang_dir_arg}); expected " - "/lib/cmake/clang") - endif() -else() - # Clang's package sits beside LLVM's in every supported layout; search - # only there so an unrelated system clang cannot satisfy the lookup. - find_package(Clang CONFIG QUIET HINTS "${LLVM_DIR}/../clang" NO_DEFAULT_PATH) -endif() -if(Clang_FOUND) - message(STATUS "Found Clang at ${Clang_DIR}") -endif() - # CppInterOp is installed at the location cppjit ships at runtime: ask for the # site-packages path; fall back to CMAKE_INSTALL_PREFIX for standalone builds. execute_process( @@ -106,7 +48,9 @@ endif() # owns every installed file. set(CPPINTEROP_STAGE_DIR "${CMAKE_BINARY_DIR}/cppinterop-stage") -# Include cmake for CppInterOp config and build using ExternalProject. +# Acquire CppInterOp: an external prebuilt one (CppInterOp_DIR / +# CMAKE_PREFIX_PATH), else the pinned ExternalProject. Exports the +# CPPJIT_INTEROP_* coordinates consumed below. include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AddCppInterOp.cmake) cppjit_add_cppinterop() @@ -118,17 +62,19 @@ set(INTEROP_SOURCES ) add_library(cppjit SHARED ${CPYRT_SOURCES} ${INTEROP_SOURCES}) -add_dependencies(cppjit CppInterOp) +if(TARGET CppInterOp) + add_dependencies(cppjit CppInterOp) +endif() -# The wrapper anchors these relative spellings at its own load location, -# falling back to the install prefix (see cppinterop_paths()); the clang -# major names the versioned compiler probed for the runtime resource dir. +# The wrapper anchors these coordinates at its own load location, falling +# back to the install prefix (see cppinterop_paths()); the clang major +# names the versioned compiler probed for the runtime resource dir. target_compile_definitions(cppjit PRIVATE CPPINTEROP_INSTALL_PREFIX="${CPPINTEROP_INSTALL_PREFIX}/cppjit" - CPPINTEROP_LIBRARY="interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" - CPPINTEROP_INCLUDE_DIR="interop/include" - CPPJIT_CLANG_MAJOR="${LLVM_VERSION_MAJOR}" - CPPJIT_CLANG_INCLUDE_DIR="interop/lib/clang/${LLVM_VERSION_MAJOR}" + CPPINTEROP_LIBRARY="${CPPJIT_INTEROP_LIBRARY}" + CPPINTEROP_INCLUDE_DIR="${CPPJIT_INTEROP_RUNTIME_INCLUDES}" + CPPJIT_CLANG_MAJOR="${CPPJIT_INTEROP_CLANG_MAJOR}" + CPPJIT_CLANG_INCLUDE_DIR="${CPPJIT_INTEROP_CLANG_DIR}" ) target_include_directories(cppjit PRIVATE @@ -137,7 +83,7 @@ target_include_directories(cppjit PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}/src/cpyrt ${CMAKE_CURRENT_SOURCE_DIR}/src/interop - ${CPPINTEROP_STAGE_DIR}/include + ${CPPJIT_INTEROP_COMPILE_INCLUDES} ${Python_INCLUDE_DIRS} ) @@ -167,24 +113,20 @@ install(TARGETS cppjit LIBRARY DESTINATION cppjit ) -install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/lib/" - DESTINATION cppjit/interop/lib -) -install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/include/" - DESTINATION cppjit/interop/include -) - -# ship the builtin headers of the build clang, laid out as a headers-only -# resource dir: only include/ ships -set(_clang_resource_dir "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") -if(NOT EXISTS "${_clang_resource_dir}/include") - message(FATAL_ERROR - "No builtin headers at ${_clang_resource_dir}/include; the LLVM at " - "${LLVM_DIR} carries no clang resource directory") +# An external CppInterOp is consumed in place and nothing of it ships; +# the bundled one installs with the builtin headers of the build clang, +# laid out as a headers-only resource dir (only include/ ships). +if(TARGET CppInterOp) + install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/lib/" + DESTINATION cppjit/interop/lib + ) + install(DIRECTORY "${CPPINTEROP_STAGE_DIR}/include/" + DESTINATION cppjit/interop/include + ) + install(DIRECTORY "${CPPJIT_INTEROP_CLANG_RESOURCE_DIR}/include/" + DESTINATION "cppjit/interop/lib/clang/${CPPJIT_INTEROP_CLANG_MAJOR}/include" + ) endif() -install(DIRECTORY "${_clang_resource_dir}/include/" - DESTINATION "cppjit/interop/lib/clang/${LLVM_VERSION_MAJOR}/include" -) # the public cpyrt API headers keep their installed cpyrt/ prefix install(FILES diff --git a/cmake/AddCppInterOp.cmake b/cmake/AddCppInterOp.cmake index be0fef2..57e2ea9 100644 --- a/cmake/AddCppInterOp.cmake +++ b/cmake/AddCppInterOp.cmake @@ -1,10 +1,26 @@ -# Configures the CppInterOp ExternalProject, built either from the pinned git -# tag (default) or from a local checkout (CPPINTEROP_SOURCE_DIR). Default -# backend is clang-repl; CPPJIT_USE_CLING builds CppInterOp against a provided -# cling build +# Acquires CppInterOp. A distribution or developer supplies a prebuilt one +# -- an install prefix or a build directory -- through CppInterOp_DIR or +# CMAKE_PREFIX_PATH, and it is consumed in place with nothing bundled. +# Otherwise the pinned tag (or CPPINTEROP_SOURCE_DIR) builds as an +# ExternalProject and stages for bundling; default backend is clang-repl, +# CPPJIT_USE_CLING builds against a provided cling. Coordinates exported to +# the parent scope, relative ones anchored at the runtime layout and +# absolute ones standing on their own (see cppinterop_paths()): +# CPPJIT_INTEROP_LIBRARY runtime library +# CPPJIT_INTEROP_RUNTIME_INCLUDES runtime include dirs, ':'-joined +# CPPJIT_INTEROP_COMPILE_INCLUDES include dirs for building the wrapper +# CPPJIT_INTEROP_CLANG_MAJOR clang major of the interpreter +# CPPJIT_INTEROP_CLANG_DIR resource-dir coordinate; empty = probe +# CPPJIT_INTEROP_CLANG_RESOURCE_DIR builtin headers to bundle (install only) include_guard(GLOBAL) include(ExternalProject) +# Hint LLVM discovery at an active conda toolchain. +set(_llvm_hints "") +if(DEFINED ENV{CONDA_PREFIX}) + list(APPEND _llvm_hints "$ENV{CONDA_PREFIX}/lib/cmake/llvm") +endif() + # Developer toggle: Cling from either ROOT or standalone supplies LLVM_DIR/Clang_DIR option(CPPJIT_USE_CLING "Build CppInterOp against a prebuilt Cling C++ Interpreter (ROOT)" OFF) mark_as_advanced(CPPJIT_USE_CLING) @@ -18,95 +34,204 @@ else() endif() function(cppjit_add_cppinterop) - set(_args - -DLLVM_DIR=${LLVM_DIR} - -DCPPINTEROP_ENABLE_TESTING=${CPPJIT_ENABLE_CPPINTEROP_TESTS} - -DBUILD_SHARED_LIBS=ON - # The wheel ships a single unversioned library file. - -DCPPINTEROP_SHARED_LIBRARY_VERSIONING=OFF - -DCMAKE_INSTALL_PREFIX=${CPPINTEROP_STAGE_DIR} - -DCMAKE_INSTALL_LIBDIR=lib - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_CXX_STANDARD=17 - ) - - if(CPPJIT_USE_CLING) - set(_cling_dir "${Cling_DIR}") - if(NOT _cling_dir) - # LLVM_DIR is /lib/cmake/llvm; cling config sits at the - # sibling /lib/cmake/cling (setup-llvm flavor:cling layout). - get_filename_component(_prefix "${LLVM_DIR}" DIRECTORY) # /lib/cmake - get_filename_component(_prefix "${_prefix}" DIRECTORY) # /lib - get_filename_component(_prefix "${_prefix}" DIRECTORY) # - set(_cling_dir "${_prefix}/lib/cmake/cling") + # An explicit CppInterOp_DIR is authoritative, like LLVM_DIR below. + if(DEFINED CppInterOp_DIR) + find_package(CppInterOp CONFIG REQUIRED PATHS "${CppInterOp_DIR}" NO_DEFAULT_PATH) + else() + find_package(CppInterOp CONFIG QUIET) + endif() + + set(_clang_resource_dir "") + if(CppInterOp_FOUND) + if(CPPINTEROP_SOURCE_DIR) + message(FATAL_ERROR + "An external CppInterOp and CPPINTEROP_SOURCE_DIR are mutually exclusive") endif() - message(STATUS "CppInterOp backend: Cling (found at ${_cling_dir})") - list(APPEND _args - -DCPPINTEROP_USE_CLING=ON - -DCPPINTEROP_USE_REPL=OFF - -DCling_DIR=${_cling_dir} - ) + # The developer owns compatibility: warn, do not fail. + if(CPPINTEROP_LLVM_VERSION_MAJOR LESS CPPJIT_LLVM_VERSION_MIN OR + CPPINTEROP_LLVM_VERSION_MAJOR GREATER CPPJIT_LLVM_VERSION_MAX) + message(WARNING + "External CppInterOp embeds LLVM ${CPPINTEROP_LLVM_VERSION}, outside " + "the tested ${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX}") + endif() + # Builtin headers from a matching host LLVM when one is findable; an + # empty coordinate leaves them to the wrapper's runtime probe. + find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) + set(_clang_dir "") + if(LLVM_FOUND AND LLVM_VERSION_MAJOR EQUAL CPPINTEROP_LLVM_VERSION_MAJOR + AND EXISTS "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}/include") + set(_clang_dir "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") + endif() + + set(_library "${CPPINTEROP_LIBRARIES}") + set(_compile_includes "${CPPINTEROP_INCLUDE_DIRS}") + list(JOIN CPPINTEROP_INCLUDE_DIRS ":" _runtime_includes) + set(_clang_major "${CPPINTEROP_LLVM_VERSION_MAJOR}") + message(STATUS + "CppInterOp: external ${CPPINTEROP_VERSION} (LLVM ${CPPINTEROP_LLVM_VERSION}) " + "at ${CPPINTEROP_LIBRARIES}") else() - list(APPEND _args - -DCPPINTEROP_USE_REPL=ON - -DCPPINTEROP_USE_CLING=OFF + if(DEFINED LLVM_DIR) + # An explicit LLVM_DIR is authoritative: fail instead of falling back to a + # different LLVM than the one requested. A failed find_package resets + # LLVM_DIR to -NOTFOUND, so keep the requested value for the message. + set(_llvm_dir_arg "${LLVM_DIR}") + find_package(LLVM CONFIG PATHS "${LLVM_DIR}" NO_DEFAULT_PATH) + if(NOT LLVM_FOUND) + message(FATAL_ERROR + "No LLVMConfig.cmake under LLVM_DIR (${_llvm_dir_arg}); expected " + "/lib/cmake/llvm") + endif() + else() + find_package(LLVM CONFIG QUIET HINTS ${_llvm_hints}) + if(NOT LLVM_FOUND) + message(FATAL_ERROR + "No LLVM CMake package found. Install LLVM " + "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX} development packages " + "(apt: llvm-${CPPJIT_LLVM_VERSION_MAX}-dev libclang-${CPPJIT_LLVM_VERSION_MAX}-dev; " + "conda: llvmdev clangdev), or point cppjit at your own LLVM build with " + "-DLLVM_DIR=/lib/cmake/llvm " + "(pip: --config-settings=cmake.define.LLVM_DIR=...)") + endif() + endif() + + message(STATUS "Found LLVM ${LLVM_VERSION} at ${LLVM_DIR}") + if(LLVM_VERSION_MAJOR LESS CPPJIT_LLVM_VERSION_MIN OR + LLVM_VERSION_MAJOR GREATER CPPJIT_LLVM_VERSION_MAX) + message(FATAL_ERROR + "LLVM ${LLVM_VERSION} is unsupported: the currently supported " + "CppInterOp version (${CPPINTEROP_GIT_TAG}) only supports LLVM " + "${CPPJIT_LLVM_VERSION_MIN}-${CPPJIT_LLVM_VERSION_MAX}") + endif() + + if(DEFINED Clang_DIR) + # An explicit Clang_DIR is authoritative, like LLVM_DIR above. + set(_clang_dir_arg "${Clang_DIR}") + find_package(Clang CONFIG PATHS "${Clang_DIR}" NO_DEFAULT_PATH) + if(NOT Clang_FOUND) + message(FATAL_ERROR + "No ClangConfig.cmake under Clang_DIR (${_clang_dir_arg}); expected " + "/lib/cmake/clang") + endif() + else() + # Clang's package sits beside LLVM's in every supported layout; search + # only there so an unrelated system clang cannot satisfy the lookup. + find_package(Clang CONFIG QUIET HINTS "${LLVM_DIR}/../clang" NO_DEFAULT_PATH) + endif() + if(Clang_FOUND) + message(STATUS "Found Clang at ${Clang_DIR}") + endif() + + set(_args + -DLLVM_DIR=${LLVM_DIR} + -DCPPINTEROP_ENABLE_TESTING=${CPPJIT_ENABLE_CPPINTEROP_TESTS} + -DBUILD_SHARED_LIBS=ON + # The wheel ships a single unversioned library file. + -DCPPINTEROP_SHARED_LIBRARY_VERSIONING=OFF + -DCMAKE_INSTALL_PREFIX=${CPPINTEROP_STAGE_DIR} + -DCMAKE_INSTALL_LIBDIR=lib + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} + -DCMAKE_CXX_STANDARD=17 ) - endif() - if(Clang_DIR) - list(APPEND _args -DClang_DIR=${Clang_DIR}) - endif() - if(CMAKE_C_COMPILER) - list(APPEND _args -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}) - endif() - if(CMAKE_CXX_COMPILER) - list(APPEND _args -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) - endif() + if(CPPJIT_USE_CLING) + set(_cling_dir "${Cling_DIR}") + if(NOT _cling_dir) + # LLVM_DIR is /lib/cmake/llvm; cling config sits at the + # sibling /lib/cmake/cling (setup-llvm flavor:cling layout). + get_filename_component(_prefix "${LLVM_DIR}" DIRECTORY) # /lib/cmake + get_filename_component(_prefix "${_prefix}" DIRECTORY) # /lib + get_filename_component(_prefix "${_prefix}" DIRECTORY) # + set(_cling_dir "${_prefix}/lib/cmake/cling") + endif() + message(STATUS "CppInterOp backend: Cling (found at ${_cling_dir})") + list(APPEND _args + -DCPPINTEROP_USE_CLING=ON + -DCPPINTEROP_USE_REPL=OFF + -DCling_DIR=${_cling_dir} + ) + else() + list(APPEND _args + -DCPPINTEROP_USE_REPL=ON + -DCPPINTEROP_USE_CLING=OFF + ) + endif() + + if(Clang_DIR) + list(APPEND _args -DClang_DIR=${Clang_DIR}) + endif() + if(CMAKE_C_COMPILER) + list(APPEND _args -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}) + endif() + if(CMAKE_CXX_COMPILER) + list(APPEND _args -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}) + endif() - set(_source_args - GIT_REPOSITORY ${CPPINTEROP_GIT_REPOSITORY} - GIT_TAG ${CPPINTEROP_GIT_TAG} - ) - set(_log_args - LOG_DOWNLOAD ON - LOG_CONFIGURE ON - LOG_BUILD ON - LOG_INSTALL ON - LOG_OUTPUT_ON_FAILURE ON - ) - if(CPPINTEROP_SOURCE_DIR) - message(STATUS "CppInterOp: building from local source at ${CPPINTEROP_SOURCE_DIR} " - "over the currently supported version ${CPPINTEROP_GIT_TAG}") - # BUILD_ALWAYS recompiles uncommitted edits and refreshes the installed - # libclangCppInterOp on every build; the sub-build keeps this incremental. set(_source_args - SOURCE_DIR "${CPPINTEROP_SOURCE_DIR}" - BUILD_ALWAYS ON + GIT_REPOSITORY ${CPPINTEROP_GIT_REPOSITORY} + GIT_TAG ${CPPINTEROP_GIT_TAG} + ) + set(_log_args + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + LOG_INSTALL ON + LOG_OUTPUT_ON_FAILURE ON ) - # Stream sub-build output in the dev loop instead of hiding it in log files. - set(_log_args "") + if(CPPINTEROP_SOURCE_DIR) + message(STATUS "CppInterOp: building from local source at ${CPPINTEROP_SOURCE_DIR} " + "over the currently supported version ${CPPINTEROP_GIT_TAG}") + # BUILD_ALWAYS recompiles uncommitted edits and refreshes the installed + # libclangCppInterOp on every build; the sub-build keeps this incremental. + set(_source_args + SOURCE_DIR "${CPPINTEROP_SOURCE_DIR}" + BUILD_ALWAYS ON + ) + # Stream sub-build output in the dev loop instead of hiding it in log files. + set(_log_args "") + endif() + + # Install only the library and headers, not CppInterOp's full install tree. + ExternalProject_Add(CppInterOp + ${_source_args} + PREFIX "${CMAKE_BINARY_DIR}/CppInterOp" + CMAKE_ARGS ${_args} + # -stripped keeps .dynsym, so the dlsym-based dispatch still resolves. + INSTALL_COMMAND ${CMAKE_COMMAND} --build + --target install-clangCppInterOp-stripped install-cppinterop-headers + BUILD_BYPRODUCTS + "${CPPINTEROP_STAGE_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" + ${_log_args} + ) + + # Verify that the user-provided CppInterOp source override is legitimate. + ExternalProject_Add_Step(CppInterOp verify_project + COMMAND ${CMAKE_COMMAND} + -DCPPINTEROP_BINARY_DIR= + -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/VerifyCppInterOp.cmake" + DEPENDEES configure + DEPENDERS build + COMMENT "Verifying the configured source tree is CppInterOp" + ) + + set(_clang_resource_dir "${LLVM_LIBRARY_DIR}/clang/${LLVM_VERSION_MAJOR}") + if(NOT EXISTS "${_clang_resource_dir}/include") + message(FATAL_ERROR + "No builtin headers at ${_clang_resource_dir}/include; the LLVM at " + "${LLVM_DIR} carries no clang resource directory") + endif() + + set(_library "interop/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}") + set(_compile_includes "${CPPINTEROP_STAGE_DIR}/include") + set(_runtime_includes "interop/include") + set(_clang_major "${LLVM_VERSION_MAJOR}") + set(_clang_dir "interop/lib/clang/${LLVM_VERSION_MAJOR}") endif() - # Install only the library and headers, not CppInterOp's full install tree. - ExternalProject_Add(CppInterOp - ${_source_args} - PREFIX "${CMAKE_BINARY_DIR}/CppInterOp" - CMAKE_ARGS ${_args} - # -stripped keeps .dynsym, so the dlsym-based dispatch still resolves. - INSTALL_COMMAND ${CMAKE_COMMAND} --build - --target install-clangCppInterOp-stripped install-cppinterop-headers - BUILD_BYPRODUCTS - "${CPPINTEROP_STAGE_DIR}/lib/libclangCppInterOp${CMAKE_SHARED_LIBRARY_SUFFIX}" - ${_log_args} - ) - - # Verify that the user-provided CppInterOp source override is legitimate. - ExternalProject_Add_Step(CppInterOp verify_project - COMMAND ${CMAKE_COMMAND} - -DCPPINTEROP_BINARY_DIR= - -P "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/VerifyCppInterOp.cmake" - DEPENDEES configure - DEPENDERS build - COMMENT "Verifying the configured source tree is CppInterOp" - ) + set(CPPJIT_INTEROP_LIBRARY "${_library}" PARENT_SCOPE) + set(CPPJIT_INTEROP_RUNTIME_INCLUDES "${_runtime_includes}" PARENT_SCOPE) + set(CPPJIT_INTEROP_COMPILE_INCLUDES "${_compile_includes}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_MAJOR "${_clang_major}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_DIR "${_clang_dir}" PARENT_SCOPE) + set(CPPJIT_INTEROP_CLANG_RESOURCE_DIR "${_clang_resource_dir}" PARENT_SCOPE) endfunction() diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index 3b6d8d4..d29dada 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -81,12 +81,14 @@ static inline bool is_integral(std::string& s) { struct InterOpPaths { std::string Library; - std::string IncludeDir; - std::string ClangIncludeDir; // empty when the bundled headers are absent + std::vector IncludeDirs; + std::string ClangIncludeDir; // empty when no usable resource dir is known }; -// One relative layout, two anchors: prefer CppInterOp next to our own load -// location so wheels relocate; fall back to the build-time install prefix. +// One set of coordinates, two anchors: prefer CppInterOp next to our own +// load location so wheels relocate; fall back to the build-time install +// prefix. An absolute coordinate (an external CppInterOp) replaces the +// anchor in the join ([fs.path.append]). static InterOpPaths cppinterop_paths() { std::filesystem::path anchor = CPPINTEROP_INSTALL_PREFIX; #ifndef _WIN32 @@ -99,16 +101,24 @@ static InterOpPaths cppinterop_paths() { anchor = here; } #endif - InterOpPaths Paths{(anchor / CPPINTEROP_LIBRARY).string(), - (anchor / CPPINTEROP_INCLUDE_DIR).string(), - {}}; - // The builtin headers of the build clang ship with every installed - // package (see the CMake install rule); a raw build tree has none and - // falls back to resource-dir detection. - const std::filesystem::path bundled = anchor / CPPJIT_CLANG_INCLUDE_DIR; - std::error_code ec; - if (std::filesystem::exists(bundled / "include", ec)) - Paths.ClangIncludeDir = bundled.string(); + InterOpPaths Paths; + Paths.Library = (anchor / CPPINTEROP_LIBRARY).string(); + // The include coordinate may carry several ':'-separated directories (an + // external CppInterOp build tree splits source and generated headers). + std::istringstream includeSpec{CPPINTEROP_INCLUDE_DIR}; + for (std::string dir; std::getline(includeSpec, dir, ':');) + if (!dir.empty()) + Paths.IncludeDirs.push_back((anchor / dir).string()); + // A bundled install ships the build clang's builtin headers (see the + // CMake install rule); an empty coordinate or a missing directory falls + // back to resource-dir detection. + const std::string clangSpec = CPPJIT_CLANG_INCLUDE_DIR; + if (!clangSpec.empty()) { + const std::filesystem::path bundled = anchor / clangSpec; + std::error_code ec; + if (std::filesystem::exists(bundled / "include", ec)) + Paths.ClangIncludeDir = bundled.string(); + } return Paths; } @@ -169,7 +179,8 @@ static void configureInterpreter(const InterOpPaths& Paths) { Cpp::Process(s.str().c_str()); } - Cpp::AddIncludePath(Paths.IncludeDir.c_str()); + for (const std::string& dir : Paths.IncludeDirs) + Cpp::AddIncludePath(dir.c_str()); Cpp::LoadLibrary("libstdc++", /* lookup= */ true); }