diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f14738a..76281850 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,6 +35,13 @@ endif() # ############################################################################## option(AL_PYTHON_BINDINGS "Build Python bindings" OFF) +# When ON, the al C++ library is not built here; instead it is located with +# find_package(al-core CONFIG). Enables a two-stage build where libimas-core +# (the C/C++ library) and imas-core (the Python wrapper) are packaged +# separately, e.g. as two conda packages. +option(AL_USE_INSTALLED_CORE + "Link Python bindings against a pre-installed al-core (find_package)" OFF) + # Configuration options for shared libraries # ############################################################################## @@ -127,8 +134,31 @@ if(WIN32) find_package(dlfcn-win32 CONFIG REQUIRED) endif() -# build AL_CORE library only if backend is enabled -if(AL_BACKEND_HDF5 OR AL_BACKEND_MDSPLUS OR AL_BACKEND_UDA OR AL_BACKEND_UDAFAT OR AL_PYTHON_BINDINGS) +# Two-stage build entry: locate a pre-installed al-core instead of building it. +# Exposes target `al-core::al` and an unqualified alias `al` so the python/ +# subdirectory and any consumers can link to `al` transparently. +if(AL_USE_INSTALLED_CORE) + find_package(al-core CONFIG) + if(NOT al-core_FOUND) + message(FATAL_ERROR + "AL_USE_INSTALLED_CORE=ON requires an installed al-core that ships " + "al-coreConfig.cmake (introduced in IMAS-Core 5.7.1 or later). " + "find_package(al-core CONFIG) could not locate it.\n" + "Point CMAKE_PREFIX_PATH (or al-core_DIR) at an install prefix that " + "contains lib/cmake/al-core/al-coreConfig.cmake — typically the " + "stage-1 install tree of this branch. Releases built before this " + "change (e.g. IMAS-Core/5.6.0) only ship al-core.pc and will NOT " + "satisfy AL_USE_INSTALLED_CORE; either install stage 1 from source " + "or wait for a release that includes the CMake package config.") + endif() + if(NOT TARGET al) + add_library(al ALIAS al-core::al) + endif() +endif() + +# build AL_CORE library only if backend is enabled and we are not reusing an +# already-installed al-core +if((AL_BACKEND_HDF5 OR AL_BACKEND_MDSPLUS OR AL_BACKEND_UDA OR AL_BACKEND_UDAFAT OR AL_PYTHON_BINDINGS) AND NOT AL_USE_INSTALLED_CORE) # Core dependencies set(Boost_USE_MULTITHREADED FALSE) @@ -199,14 +229,43 @@ add_dependencies( imas_print_version al ) # ############################################################################## # Install al library +include(GNUInstallDirs) install( TARGETS al + EXPORT al-coreTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} COMPONENT core ) +# Export CMake package config so downstream projects (notably the Python +# wrapper built with AL_USE_INSTALLED_CORE=ON) can `find_package(al-core CONFIG)`. +include(CMakePackageConfigHelpers) +set(_al_core_cmake_dir ${CMAKE_INSTALL_LIBDIR}/cmake/al-core) +install( + EXPORT al-coreTargets + FILE al-coreTargets.cmake + NAMESPACE al-core:: + DESTINATION ${_al_core_cmake_dir} +) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/cmake/al-coreConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/al-coreConfig.cmake + INSTALL_DESTINATION ${_al_core_cmake_dir} +) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/al-coreConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion +) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/al-coreConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/al-coreConfigVersion.cmake + DESTINATION ${_al_core_cmake_dir} +) + # TODO: put public heades in a separate directory? install( FILES ${PUBLIC_HEADER_FILES} @@ -240,14 +299,15 @@ install(DIRECTORY common TYPE DATA) # Install Dummy install(TARGETS imas_print_version DESTINATION bin) -# Scikit-build-core entry point for python bindings +endif() # AL core library: (AL_BACKEND_* OR AL_PYTHON_BINDINGS) AND NOT AL_USE_INSTALLED_CORE + +# Scikit-build-core entry point for python bindings — works whether `al` was +# just built here or imported via find_package(al-core). # ############################################################################## if(AL_PYTHON_BINDINGS) include(skbuild.cmake) endif() -endif() # AL core library (AL_BACKEND_HDF5 OR AL_BACKEND_MDSPLUS OR AL_BACKEND_UDA OR AL_BACKEND_UDAFAT OR AL_PYTHON_BINDINGS) - # MDSplus models # ############################################################################## diff --git a/cmake/al-coreConfig.cmake.in b/cmake/al-coreConfig.cmake.in new file mode 100644 index 00000000..4d8b30ed --- /dev/null +++ b/cmake/al-coreConfig.cmake.in @@ -0,0 +1,8 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Boost COMPONENTS filesystem) + +include("${CMAKE_CURRENT_LIST_DIR}/al-coreTargets.cmake") + +check_required_components(al-core) diff --git a/pyproject.toml b/pyproject.toml index e0ce230c..68165362 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,7 @@ AL_BACKEND_UDA = { env = "AL_BACKEND_UDA", default = "ON" } AL_BACKEND_MDSPLUS = { env = "AL_BACKEND_MDSPLUS", default = "OFF" } AL_BUILD_MDSPLUS_MODELS = { env = "AL_BUILD_MDSPLUS_MODELS", default = "OFF" } AL_PYTHON_BINDINGS = "ON" +AL_USE_INSTALLED_CORE = { env = "AL_USE_INSTALLED_CORE", default = "OFF" } DOWNLOAD_DEPENDENCIES = { env = "DOWNLOAD_DEPENDENCIES", default = "ON" } DD_GIT_REPOSITORY = { env = "DD_GIT_REPOSITORY", default = "EMPTY" } DD_VERSION = { env = "DD_VERSION", default = "EMPTY" } diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 528e33f6..42341ece 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -8,6 +8,14 @@ python_add_library(al_defs MODULE ${al_defs_source} WITH_SOABI) target_link_libraries(_al_lowlevel PRIVATE Python::NumPy al) target_link_libraries(al_defs PRIVATE Python::NumPy al) +if(AL_USE_INSTALLED_CORE) + # libal is provided externally (e.g. the libimas-core conda package). + # Install only the Python extensions — the dynamic linker resolves libal + # at load time via the host's normal library search path / rpath. + install(TARGETS _al_lowlevel al_defs DESTINATION imas_core) + return() +endif() + # Handling RPATH in macOS: if(APPLE) set_target_properties(al PROPERTIES INSTALL_RPATH "@loader_path") diff --git a/skbuild.cmake b/skbuild.cmake index facd0058..b19e0964 100644 --- a/skbuild.cmake +++ b/skbuild.cmake @@ -34,19 +34,36 @@ elseif(NOT ${AL_PYTHON_BINDINGS} MATCHES "[Oo][Nn]") message(FATAL_ERROR "AL_PYTHON_BINDINGS=${AL_PYTHON_BINDINGS} not e|editable|no-build-isolation|[Oo][Nn]") endif() -add_custom_command( - TARGET al POST_BUILD - COMMAND - ${CMAKE_COMMAND} -E env +if(AL_USE_INSTALLED_CORE) + # `al` is an ALIAS to an imported target — it is not built in this tree, + # so it cannot carry a POST_BUILD hook. Drive pip wheel from the custom + # target itself, and put it in ALL so `cmake --build` triggers it. + add_custom_target(al-python-bindings ALL + COMMAND + ${CMAKE_COMMAND} -E env CMAKE_ARGS=${CMAKE_ARGS} - SKBUILD_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/{wheel_tag} + SKBUILD_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/{wheel_tag} ${Python_EXECUTABLE} - -m pip wheel + -m pip wheel ${CMAKE_CURRENT_SOURCE_DIR} - ${PIP_OPTIONS} - --wheel-dir ${CMAKE_CURRENT_BINARY_DIR}/dist/ -) -add_custom_target(al-python-bindings DEPENDS al) + ${PIP_OPTIONS} + --wheel-dir ${CMAKE_CURRENT_BINARY_DIR}/dist/ + ) +else() + add_custom_command( + TARGET al POST_BUILD + COMMAND + ${CMAKE_COMMAND} -E env + CMAKE_ARGS=${CMAKE_ARGS} + SKBUILD_BUILD_DIR=${CMAKE_CURRENT_BINARY_DIR}/{wheel_tag} + ${Python_EXECUTABLE} + -m pip wheel + ${CMAKE_CURRENT_SOURCE_DIR} + ${PIP_OPTIONS} + --wheel-dir ${CMAKE_CURRENT_BINARY_DIR}/dist/ + ) + add_custom_target(al-python-bindings DEPENDS al) +endif() set_target_properties( al-python-bindings PROPERTIES DIST_FOLDER ${CMAKE_CURRENT_BINARY_DIR}/dist/) install(CODE "execute_process(COMMAND ${Python_EXECUTABLE}