From 6af8f5b8a602e349f95efbe5b91d231b426108b0 Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 22 Jul 2026 12:40:56 -0400 Subject: [PATCH 1/2] Fix Halide/HalideHelpers CMake package dependency inversion Halide previously depended on HalideHelpers, backwards from the intended cross-compiling story where a target-side build should load only the lightweight, arch-independent helpers and lazily pull in the compiler binaries only when actually needed. Swap the roles: Halide is now the always-available helpers package (find_package(Halide) never touches a compiled binary while cross-compiling unless the new JIT or Python component is explicitly requested), and HalideCompiler is the new name for the platform-specific compiled package it can lazily load. The static/shared preference recorded by Halide's static/shared components is a plain, directory-scoped variable, so independent find_package(Halide ...) calls in unrelated directories of the same project don't stomp on each other. HalideCompilerConfig.cmake detects a genuine conflict -- an explicit component that contradicts an already-recorded preference, or an ancestor directory that already loaded the other flavor -- and fails cleanly instead of silently keeping whichever flavor loaded first. An explicit static/shared request is also forwarded as a real component into HalideConfig.cmake's own find_dependency(HalideCompiler) call, since find_dependency() otherwise skips its underlying find_package() call for arguments identical to an earlier call in the same directory (or a descendant), and the preference variable alone isn't part of those arguments -- without forwarding, that CMake-level optimization would silently defeat the conflict detection above. Also fixes two pre-existing, unrelated packaging bugs: a casing mismatch in the FlatBuffers pkgdep heuristic, and a missing CMAKE_MODULE_PATH entry for the bundled Halide_LLVM/V8 Find modules. Adds test/integration coverage to testing-ecosystem.yml: shared + static + serialization builds, a real aarch64 cross-compile, a forced-JIT-without-compiler negative case, and independent per-directory static/shared selection including a conflict-detection negative case. Fixes #7344 Fixes #7037 Co-Authored-By: Claude Sonnet 5 --- .github/workflows/testing-ecosystem.yml | 51 ++++- .gitignore | 2 +- CMakeLists.txt | 4 +- .../cmake-external_project/CMakeLists.txt | 3 +- .../generator/CMakeLists.txt | 2 +- .../cmake-super_build/app/CMakeLists.txt | 3 +- .../generator/CMakeLists.txt | 2 +- .../HelloBaremetal/cmake-twice/CMakeLists.txt | 3 +- apps/hannk/CMakeLists.txt | 4 +- apps/hannk/configure_cmake.sh | 6 +- cmake/HalideGeneratorHelpers.cmake | 11 +- cmake/HalidePackageConfigHelpers.cmake | 17 +- doc/HalideCMakePackage.md | 38 ++++ packaging/CMakeLists.txt | 74 +++---- packaging/common/HalideCompilerConfig.cmake | 208 ++++++++++++++++++ packaging/common/HalideConfig.cmake | 134 +++++------ packaging/common/HalideHelpersConfig.cmake | 11 - packaging/pip/CMakeLists.txt | 10 +- src/CMakeLists.txt | 2 +- test/integration/CMakeLists.txt | 120 +++++++++- test/integration/multi_dir/CMakeLists.txt | 28 +++ .../conflicting_consumer/CMakeLists.txt | 4 + test/integration/multi_dir/main.cpp | 24 ++ .../multi_dir/shared_consumer/CMakeLists.txt | 15 ++ .../multi_dir/static_consumer/CMakeLists.txt | 15 ++ test/integration/xc/CMakeLists.txt | 10 +- 26 files changed, 646 insertions(+), 155 deletions(-) create mode 100644 packaging/common/HalideCompilerConfig.cmake delete mode 100644 packaging/common/HalideHelpersConfig.cmake create mode 100644 test/integration/multi_dir/CMakeLists.txt create mode 100644 test/integration/multi_dir/conflicting_consumer/CMakeLists.txt create mode 100644 test/integration/multi_dir/main.cpp create mode 100644 test/integration/multi_dir/shared_consumer/CMakeLists.txt create mode 100644 test/integration/multi_dir/static_consumer/CMakeLists.txt diff --git a/.github/workflows/testing-ecosystem.yml b/.github/workflows/testing-ecosystem.yml index 065237e09b85..94cb69ff756f 100644 --- a/.github/workflows/testing-ecosystem.yml +++ b/.github/workflows/testing-ecosystem.yml @@ -6,8 +6,9 @@ on: - '**.h' - '**.c' - '**.cpp' - - 'CMakeLists.txt' + - '**/CMakeLists.txt' - '**.cmake' + - 'test/integration/**' - '.github/workflows/testing-ecosystem.yml' concurrency: @@ -40,6 +41,8 @@ jobs: "clang-${LLVM_VERSION}" \ curl \ flatbuffers-compiler \ + g++-aarch64-linux-gnu \ + gcc-aarch64-linux-gnu \ "libclang-${LLVM_VERSION}-dev" \ libedit-dev \ libflatbuffers-dev \ @@ -48,7 +51,47 @@ jobs: "liblld-${LLVM_VERSION}-dev" \ "lld-${LLVM_VERSION}" \ "llvm-${LLVM_VERSION}-dev" \ - pybind11-dev + ninja-build \ + pybind11-dev \ + qemu-user - - name: Check CMake configures - run: cmake --preset ubuntu-release -DHalide_LLVM_ROOT="/usr/lib/llvm-${LLVM_VERSION}" + - name: Build and install Halide (shared) + run: | + cmake --preset ubuntu-release -G Ninja -B build-shared \ + -DHalide_LLVM_ROOT="/usr/lib/llvm-${LLVM_VERSION}" \ + -DWITH_TESTS=NO -DWITH_TUTORIALS=NO -DWITH_UTILS=NO + cmake --build build-shared -j "$(nproc)" + cmake --install build-shared --prefix "${{ github.workspace }}/pkg-install" + + # A second, statically-linked build with serialization enabled (the + # default) is what actually exercises the FlatBuffers- and + # LLVM-dependency resolution paths in the installed HalideCompiler + # package: those dependencies only show up as PUBLIC/exported link + # requirements for a static Halide::Halide, not a shared one. + - name: Build and install Halide (static) + run: | + cmake --preset ubuntu-release -G Ninja -B build-static \ + -DHalide_LLVM_ROOT="/usr/lib/llvm-${LLVM_VERSION}" \ + -DWITH_TESTS=NO -DWITH_TUTORIALS=NO -DWITH_UTILS=NO \ + -DBUILD_SHARED_LIBS=NO + cmake --build build-static -j "$(nproc)" + cmake --install build-static --prefix "${{ github.workspace }}/pkg-install" + + # A prefix containing only the Halide (helpers) package, with no + # HalideCompiler at all, used to verify that forcing the JIT component + # while cross-compiling fails cleanly when no matching compiler package + # is available -- see cross_compile_aarch64_force_jit_fails_cleanly in + # test/integration/CMakeLists.txt. + - name: Prepare a Halide-helpers-only prefix + run: | + cp -R "${{ github.workspace }}/pkg-install" "${{ github.workspace }}/pkg-install-helpers-only" + rm -rf "${{ github.workspace }}/pkg-install-helpers-only/lib/cmake/HalideCompiler" + + - name: Test the installed CMake package (test/integration) + env: + CMAKE_PREFIX_PATH: ${{ github.workspace }}/pkg-install + run: | + cmake -G Ninja -S test/integration -B build-integration \ + -DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \ + "-Dxc_HELPERS_ONLY_PREFIX=${{ github.workspace }}/pkg-install-helpers-only" + ctest --test-dir build-integration --output-on-failure -j "$(nproc)" diff --git a/.gitignore b/.gitignore index 368a457c85c2..83c758bb3fcb 100644 --- a/.gitignore +++ b/.gitignore @@ -272,4 +272,4 @@ TAGS # TODO: these should become .cmake.in !packaging/common/HalideConfig.cmake -!packaging/common/HalideHelpersConfig.cmake +!packaging/common/HalideCompilerConfig.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 69796c26b13b..24b03424b677 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,8 +19,8 @@ file(CONFIGURE ) file(CONFIGURE - OUTPUT "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/HalideHelpersConfig.cmake" - CONTENT "set(HalideHelpers_FOUND 1)\n" + OUTPUT "${CMAKE_FIND_PACKAGE_REDIRECTS_DIR}/HalideCompilerConfig.cmake" + CONTENT "set(HalideCompiler_FOUND 1)\n" ) ## diff --git a/apps/HelloBaremetal/cmake-external_project/CMakeLists.txt b/apps/HelloBaremetal/cmake-external_project/CMakeLists.txt index bf2aea0bc342..619ed700d4ed 100644 --- a/apps/HelloBaremetal/cmake-external_project/CMakeLists.txt +++ b/apps/HelloBaremetal/cmake-external_project/CMakeLists.txt @@ -30,8 +30,7 @@ ExternalProject_Add( # Build application with cross compiler, # where the generator executable built in Step 1 is imported and called -# Import Halide. Instead of the package Halide, HalideHelpers is enough as we don't use JIT mode. -find_package(HalideHelpers REQUIRED) +find_package(Halide REQUIRED) set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/apps/HelloBaremetal/cmake-external_project/generator/CMakeLists.txt b/apps/HelloBaremetal/cmake-external_project/generator/CMakeLists.txt index a3dee0de39bb..cce2d8b6d287 100644 --- a/apps/HelloBaremetal/cmake-external_project/generator/CMakeLists.txt +++ b/apps/HelloBaremetal/cmake-external_project/generator/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_CXX_EXTENSIONS NO) set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..) -find_package(Halide REQUIRED) # We need Halide::Generator +find_package(Halide REQUIRED) # Build generator add_halide_generator(${GEN_EXE} SOURCES ${SRC_DIR}/add_generator.cpp) diff --git a/apps/HelloBaremetal/cmake-super_build/app/CMakeLists.txt b/apps/HelloBaremetal/cmake-super_build/app/CMakeLists.txt index 7ccb834dc777..e1c85871e13f 100644 --- a/apps/HelloBaremetal/cmake-super_build/app/CMakeLists.txt +++ b/apps/HelloBaremetal/cmake-super_build/app/CMakeLists.txt @@ -12,8 +12,7 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_CXX_EXTENSIONS NO) -# Import Halide. Instead of the package Halide, HalideHelpers is enough as we don't use JIT mode. -find_package(HalideHelpers REQUIRED) +find_package(Halide REQUIRED) set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..) diff --git a/apps/HelloBaremetal/cmake-super_build/generator/CMakeLists.txt b/apps/HelloBaremetal/cmake-super_build/generator/CMakeLists.txt index 4888c9539ac6..156ec29fc9b7 100644 --- a/apps/HelloBaremetal/cmake-super_build/generator/CMakeLists.txt +++ b/apps/HelloBaremetal/cmake-super_build/generator/CMakeLists.txt @@ -13,7 +13,7 @@ set(CMAKE_CXX_EXTENSIONS NO) set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../..) -find_package(Halide REQUIRED) # We need Halide::Generator +find_package(Halide REQUIRED) # Build generator add_halide_generator( diff --git a/apps/HelloBaremetal/cmake-twice/CMakeLists.txt b/apps/HelloBaremetal/cmake-twice/CMakeLists.txt index d28fe62b557a..78f7de00eb9b 100644 --- a/apps/HelloBaremetal/cmake-twice/CMakeLists.txt +++ b/apps/HelloBaremetal/cmake-twice/CMakeLists.txt @@ -12,8 +12,7 @@ if (NOT DEFINED GEN_PACKAGE) set(GEN_PACKAGE "HelloBaremetal-add_generator") endif () -# Find Halide. Instead of the package Halide, HalideHelpers is enough as we don't use JIT mode. -find_package(HalideHelpers REQUIRED) +find_package(Halide REQUIRED) set(SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/..) diff --git a/apps/hannk/CMakeLists.txt b/apps/hannk/CMakeLists.txt index f2a4d866af28..ad05191f8f2e 100644 --- a/apps/hannk/CMakeLists.txt +++ b/apps/hannk/CMakeLists.txt @@ -42,8 +42,8 @@ add_compile_definitions(HANNK_BUILD_TFLITE=$) # nol # ---------------------------- -# Find HalideHelpers -- this is just the Runtime headers and CMake functions, but no libraries -find_package(HalideHelpers REQUIRED) +find_package(Halide REQUIRED) + if (HANNK_BUILD_TFLITE) find_package(tensorflow-lite CONFIG REQUIRED) endif () diff --git a/apps/hannk/configure_cmake.sh b/apps/hannk/configure_cmake.sh index a1d8c6d0d6d3..afee340a7d27 100755 --- a/apps/hannk/configure_cmake.sh +++ b/apps/hannk/configure_cmake.sh @@ -23,8 +23,7 @@ HANNK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" SOURCE_DIR="${HANNK_DIR}" CMAKE_DEFS=( -DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" - -DHalide_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/Halide" - -DHalideHelpers_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/HalideHelpers" + -DHalide_ROOT="${HALIDE_INSTALL_PATH}" -DHalide_TARGET="${HL_TARGET}" ) @@ -38,8 +37,7 @@ if [[ ! ${HL_TARGET} =~ ^host*|${HL_HOST_TARGET}* ]]; then SOURCE_DIR="${HANNK_DIR}/cmake/superbuild" CMAKE_DEFS=( "${CMAKE_DEFS[@]}" - -DHANNK_HOST_Halide_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/Halide" - -DHANNK_HOST_HalideHelpers_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/HalideHelpers" + -DHANNK_HOST_Halide_ROOT="${HALIDE_INSTALL_PATH}" ) # Special settings for cross-compiling targets with known quirks diff --git a/cmake/HalideGeneratorHelpers.cmake b/cmake/HalideGeneratorHelpers.cmake index a295505dbbc0..dc7526c61143 100644 --- a/cmake/HalideGeneratorHelpers.cmake +++ b/cmake/HalideGeneratorHelpers.cmake @@ -119,7 +119,7 @@ function(add_halide_generator TARGET) add_executable(${gen} ALIAS ${TARGET}) if (NOT TARGET Halide::Generator) - find_package(Halide REQUIRED) + find_package(HalideCompiler REQUIRED) endif () target_link_libraries(${TARGET} PRIVATE Halide::Generator ${ARG_LINK_LIBRARIES}) @@ -186,6 +186,10 @@ function(add_halide_generator TARGET) VISIBILITY_INLINES_HIDDEN ON POSITION_INDEPENDENT_CODE ON ) + + if (NOT TARGET Halide::PyStubs) + find_package(HalideCompiler REQUIRED COMPONENTS Python) + endif () target_link_libraries( ${TARGET}_pystub PRIVATE Halide::PyStubs Halide::Halide ${ARG_LINK_LIBRARIES} @@ -790,6 +794,11 @@ function(add_halide_library TARGET) if (ARG_AUTOSCHEDULER) if ("${ARG_AUTOSCHEDULER}" MATCHES "::") + # Autoscheduler plugins are compiled targets that live in HalideCompiler; + # lazily load it if it hasn't already been (e.g. while cross-compiling). + if (NOT TARGET "${ARG_AUTOSCHEDULER}") + find_package(HalideCompiler REQUIRED) + endif () if (NOT TARGET "${ARG_AUTOSCHEDULER}") message(FATAL_ERROR "Autoscheduler ${ARG_AUTOSCHEDULER} does not exist.") endif () diff --git a/cmake/HalidePackageConfigHelpers.cmake b/cmake/HalidePackageConfigHelpers.cmake index 3ee9c3a95850..468082f0617f 100644 --- a/cmake/HalidePackageConfigHelpers.cmake +++ b/cmake/HalidePackageConfigHelpers.cmake @@ -35,7 +35,16 @@ # Helper for registering package dependencies function(_Halide_pkgdep PKG) - cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "PACKAGE_VARS") + # NAMESPACE lets the target-namespace substring match (below, in + # _Halide_install_pkgdeps) differ from the name used to find_package() + # this dependency -- some packages export targets under a namespace with + # different casing than their own package name (e.g. FlatBuffers exports + # flatbuffers::flatbuffers). + cmake_parse_arguments(PARSE_ARGV 1 ARG "" "NAMESPACE" "PACKAGE_VARS") + + if (NOT ARG_NAMESPACE) + set(ARG_NAMESPACE "${PKG}") + endif () set(code "") foreach (var IN LISTS ARG_PACKAGE_VARS) @@ -53,8 +62,11 @@ function(_Halide_pkgdep PKG) ) endif () + set(key "pkgdeps_namespace[${PKG}]") + set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" APPEND PROPERTY pkgdeps "${PKG}") # nolint set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "pkgdeps[${PKG}]" "${code}") # nolint + set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "${key}" "${ARG_NAMESPACE}") # nolint endfunction() ## @@ -87,8 +99,9 @@ function(_Halide_install_pkgdeps) get_property(pkgdeps DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY pkgdeps) foreach (dep IN LISTS pkgdeps) get_property(pkgcode DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "pkgdeps[${dep}]") + get_property(pkgns DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "pkgdeps_namespace[${dep}]") _Halide_install_code( - "if (target_cmake MATCHES \"${dep}::\")" + "if (target_cmake MATCHES \"${pkgns}::\")" " file(APPEND \"${depFile}.in\"" " [===[${pkgcode}]===] \"\\n\")" "endif ()" diff --git a/doc/HalideCMakePackage.md b/doc/HalideCMakePackage.md index a178e51a422b..2b5621271736 100644 --- a/doc/HalideCMakePackage.md +++ b/doc/HalideCMakePackage.md @@ -253,6 +253,39 @@ try the static libs first then fall back to the shared libs. To ensure that the Python bindings are available, include the `Python` component. +Finally, when [cross compiling](#cross-compiling), +`find_package(Halide REQUIRED)` never pulls in the compiled compiler/JIT library +(`Halide::Halide`, `Halide::Generator`, etc.) unless you explicitly ask for it. +Add the `JIT` component (or `Python`) to force it to be loaded even while +cross-compiling: + +```cmake +find_package(Halide REQUIRED COMPONENTS JIT) +``` + +This looks for a `HalideCompiler` package matching your current (target) +platform and fails with a normal `find_package` error if none is found. +`HalideCompiler` is the name of the underlying platform-specific package that +actually contains the compiled libraries; you generally don't need to +`find_package(HalideCompiler)` directly, but its name is useful for +`HalideCompiler_ROOT`/`-DHalideCompiler_DIR=...` when pointing CMake at a +specific installed build. + +Note that `static`/`shared`, unlike `JIT`/`Python`, never force this load by +themselves -- requesting one merely records your preference for whichever +package eventually loads the compiled compiler (whether that's this same +`find_package(Halide ...)` call, because you're not cross-compiling or also +requested `JIT`/`Python`, or a later, unrelated one, such as the internal lookup +`add_halide_generator` performs when it needs to build a generator). This +preference is scoped to the current directory (and any subdirectories added +after it), so independent parts of a project -- so long as neither is a +subdirectory of the other -- can request different linkage without conflicting +with each other. If one directory's `find_package` call ends up loading the +compiled compiler before a subdirectory requests the other flavor, that's a real +conflict (CMake can only load one flavor of `Halide::Halide` per directory +scope) and fails cleanly with a descriptive error rather than silently keeping +whichever flavor loaded first. + ## Variables Variables that control package loading: @@ -579,6 +612,11 @@ If you are writing new programs that use Halide, you might wish to use `add_halide_generator`. When using this helper, you are expected to build your project twice: once for your build host and again for your intended target. +On the target-side build, a plain `find_package(Halide REQUIRED)` is all you +need (no separate package name): it never pulls in the compiled compiler, and +`add_halide_generator` will lazily load it under the hood only if it can't find +a prebuilt host generators package to import instead. + When building the host build, you can use the `` (see the documentation above) target to build _just_ the generators. Then, in the target build, set `_ROOT` to the host build directory. diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt index 1d0481584396..5ac7db2ff2f0 100644 --- a/packaging/CMakeLists.txt +++ b/packaging/CMakeLists.txt @@ -9,8 +9,8 @@ set(Halide_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/Halide" CACHE STRING "Path to Halide CMake files" ) -set(Halide_INSTALL_HELPERSDIR "${CMAKE_INSTALL_LIBDIR}/cmake/HalideHelpers" - CACHE STRING "Path to Halide platform-independent CMake files" +set(Halide_INSTALL_COMPILERDIR "${CMAKE_INSTALL_LIBDIR}/cmake/HalideCompiler" + CACHE STRING "Path to Halide's platform-specific compiled-library CMake files" ) set(Halide_INSTALL_PLUGINDIR "${CMAKE_INSTALL_LIBDIR}" CACHE STRING "Path to Halide plugins folder") @@ -44,7 +44,7 @@ endfunction() install( TARGETS Halide Halide_Generator Halide_GenGen - EXPORT Halide_Targets + EXPORT HalideCompiler_Targets RUNTIME COMPONENT Halide_Runtime LIBRARY COMPONENT Halide_Runtime NAMELINK_COMPONENT Halide_Development ARCHIVE COMPONENT Halide_Development @@ -54,9 +54,12 @@ install( if (WITH_AUTOSCHEDULERS) set(autoschedulers Halide_Adams2019 Halide_Li2018 Halide_Mullapudi2016 Halide_Anderson2021) + # Autoscheduler plugins are compiled, platform-specific shared libraries, + # so they belong to HalideCompiler even though they're loaded via dlopen + # rather than linked directly. install( TARGETS ${autoschedulers} - EXPORT Halide_Interfaces + EXPORT HalideCompiler_Targets LIBRARY DESTINATION ${Halide_INSTALL_PLUGINDIR} COMPONENT Halide_Runtime @@ -76,11 +79,7 @@ endif () # Runtime headers ## -install( - TARGETS Halide_Runtime - EXPORT Halide_Interfaces - FILE_SET HEADERS COMPONENT Halide_Development -) +install(TARGETS Halide_Runtime EXPORT Halide_Targets FILE_SET HEADERS COMPONENT Halide_Development) ## # Halide tools @@ -99,7 +98,7 @@ install( install( TARGETS Halide_Tools Halide_ImageIO Halide_RunGenMain Halide_ThreadPool - EXPORT Halide_Interfaces + EXPORT Halide_Targets FILE_SET HEADERS COMPONENT Halide_Development DESTINATION ${Halide_INSTALL_TOOLSDIR} ) @@ -128,7 +127,7 @@ if (NOT CMAKE_INSTALL_RPATH) ) endif () -install(TARGETS ${utils} EXPORT Halide_Interfaces COMPONENT Halide_Development) +install(TARGETS ${utils} EXPORT Halide_Targets COMPONENT Halide_Development) ## # READMEs and other top-level documentation @@ -196,34 +195,34 @@ install( FILES "${Halide_SOURCE_DIR}/cmake/FindHalide_LLVM.cmake" "${Halide_SOURCE_DIR}/cmake/FindV8.cmake" - DESTINATION ${Halide_INSTALL_CMAKEDIR} + DESTINATION ${Halide_INSTALL_COMPILERDIR} COMPONENT Halide_Development ) install( - EXPORT Halide_Targets - DESTINATION ${Halide_INSTALL_CMAKEDIR} + EXPORT HalideCompiler_Targets + DESTINATION ${Halide_INSTALL_COMPILERDIR} NAMESPACE Halide:: - FILE Halide-${type}-targets.cmake + FILE HalideCompiler-${type}-targets.cmake COMPONENT Halide_Development ) install( - EXPORT Halide_Interfaces - DESTINATION ${Halide_INSTALL_HELPERSDIR} + EXPORT Halide_Targets + DESTINATION ${Halide_INSTALL_CMAKEDIR} NAMESPACE Halide:: - FILE Halide-Interfaces.cmake + FILE Halide-targets.cmake COMPONENT Halide_Development ) -write_basic_package_version_file(HalideConfigVersion.cmake COMPATIBILITY SameMajorVersion) - write_basic_package_version_file( - HalideHelpersConfigVersion.cmake + HalideConfigVersion.cmake COMPATIBILITY SameMajorVersion ARCH_INDEPENDENT ) +write_basic_package_version_file(HalideCompilerConfigVersion.cmake COMPATIBILITY SameMajorVersion) + if (WITH_PYTHON_BINDINGS) set(extra_paths Halide_Python_INSTALL_CMAKEDIR) else () @@ -232,46 +231,47 @@ endif () configure_package_config_file( common/HalideConfig.cmake HalideConfig.cmake - PATH_VARS Halide_INSTALL_HELPERSDIR ${extra_paths} + PATH_VARS Halide_INSTALL_COMPILERDIR INSTALL_DESTINATION "${Halide_INSTALL_CMAKEDIR}" NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) configure_package_config_file( - common/HalideHelpersConfig.cmake HalideHelpersConfig.cmake - INSTALL_DESTINATION "${Halide_INSTALL_HELPERSDIR}" - NO_SET_AND_CHECK_MACRO + common/HalideCompilerConfig.cmake HalideCompilerConfig.cmake + PATH_VARS ${extra_paths} + INSTALL_DESTINATION "${Halide_INSTALL_COMPILERDIR}" + NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) install( FILES ${CMAKE_CURRENT_BINARY_DIR}/HalideConfig.cmake ${CMAKE_CURRENT_BINARY_DIR}/HalideConfigVersion.cmake + ${Halide_SOURCE_DIR}/cmake/HalideGeneratorHelpers.cmake + ${Halide_SOURCE_DIR}/cmake/FindHalide_WebGPU.cmake + ${Halide_SOURCE_DIR}/cmake/HalideTargetHelpers.cmake + ${Halide_SOURCE_DIR}/cmake/TargetExportScript.cmake + ${Halide_SOURCE_DIR}/cmake/MutexCopy.ps1 DESTINATION ${Halide_INSTALL_CMAKEDIR} COMPONENT Halide_Development ) install( FILES - ${CMAKE_CURRENT_BINARY_DIR}/HalideHelpersConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/HalideHelpersConfigVersion.cmake - ${Halide_SOURCE_DIR}/cmake/HalideGeneratorHelpers.cmake - ${Halide_SOURCE_DIR}/cmake/FindHalide_WebGPU.cmake - ${Halide_SOURCE_DIR}/cmake/HalideTargetHelpers.cmake - ${Halide_SOURCE_DIR}/cmake/TargetExportScript.cmake - ${Halide_SOURCE_DIR}/cmake/MutexCopy.ps1 - DESTINATION ${Halide_INSTALL_HELPERSDIR} + ${CMAKE_CURRENT_BINARY_DIR}/HalideCompilerConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/HalideCompilerConfigVersion.cmake + DESTINATION ${Halide_INSTALL_COMPILERDIR} COMPONENT Halide_Development ) ## -# Compute find_dependency calls for Halide +# Compute find_dependency calls for HalideCompiler ## _Halide_install_pkgdeps( - FILE_NAME Halide-${type}-deps.cmake - EXPORT_FILE Halide-${type}-targets.cmake - DESTINATION "${Halide_INSTALL_CMAKEDIR}" + FILE_NAME HalideCompiler-${type}-deps.cmake + EXPORT_FILE HalideCompiler-${type}-targets.cmake + DESTINATION "${Halide_INSTALL_COMPILERDIR}" COMPONENT Halide_Development ) diff --git a/packaging/common/HalideCompilerConfig.cmake b/packaging/common/HalideCompilerConfig.cmake new file mode 100644 index 000000000000..3c3716a43795 --- /dev/null +++ b/packaging/common/HalideCompilerConfig.cmake @@ -0,0 +1,208 @@ +cmake_minimum_required(VERSION 3.28) +@PACKAGE_INIT@ + +macro(Halide_fail) # nolint -- required for find_package scoping + string(JOIN " " ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE ${ARGN}) + set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) + return() +endmacro() + +macro(Halide_find_component_dependency comp dep) # nolint + set(Halide_quiet) + if (${CMAKE_FIND_PACKAGE_NAME}_FIND_QUIETLY) + set(Halide_quiet QUIET) + endif () + + find_package(${dep} ${ARGN} ${Halide_quiet}) + + if (NOT ${dep}_FOUND AND ${CMAKE_FIND_PACKAGE_NAME}_FIND_REQUIRED_${comp}) + Halide_fail( + "${CMAKE_FIND_PACKAGE_NAME} could not be found because dependency ${dep} could not be found." + ) + endif () +endmacro() + +set(HalideCompiler_known_components Python static shared) +set(HalideCompiler_components "") + +foreach (HalideCompiler_comp IN LISTS HalideCompiler_known_components) + set(HalideCompiler_comp_${HalideCompiler_comp} NO) +endforeach () + +if (${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS) + set(HalideCompiler_components ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS}) +endif () + +# Parse components for static/shared preference. Note that HalideCompiler has +# no notion of image-format (PNG/JPEG) components -- Halide::ImageIO is +# header-only and lives entirely in the Halide package. +foreach (HalideCompiler_comp IN LISTS HalideCompiler_components) + if (HalideCompiler_comp IN_LIST HalideCompiler_known_components) + set(HalideCompiler_comp_${HalideCompiler_comp} YES) + else () + Halide_fail("HalideCompiler does not recognize component `${HalideCompiler_comp}`.") + endif () +endforeach () + +if (HalideCompiler_comp_static AND HalideCompiler_comp_shared) + Halide_fail("HalideCompiler `static` and `shared` components are mutually exclusive.") +endif () + +# Inform downstreams of potential compatibility issues. For instance, exceptions +# and RTTI must both be enabled to build Python bindings and ASAN builds should +# not be mixed with non-ASAN builds. +set(WITH_AUTOSCHEDULERS "@WITH_AUTOSCHEDULERS@") +set(Halide_ENABLE_EXCEPTIONS "@Halide_ENABLE_EXCEPTIONS@") +set(Halide_ENABLE_RTTI "@Halide_ENABLE_RTTI@") +set(Halide_ASAN_ENABLED "@Halide_ASAN_ENABLED@") + +## +## Select static or shared and load CMake scripts +## + +set(HalideCompiler_static_targets "${CMAKE_CURRENT_LIST_DIR}/HalideCompiler-static-targets.cmake") +set(HalideCompiler_shared_targets "${CMAKE_CURRENT_LIST_DIR}/HalideCompiler-shared-targets.cmake") + +set(HalideCompiler_static_deps "${CMAKE_CURRENT_LIST_DIR}/HalideCompiler-static-deps.cmake") +set(HalideCompiler_shared_deps "${CMAKE_CURRENT_LIST_DIR}/HalideCompiler-shared-deps.cmake") + +macro(Halide_load_targets type) # nolint + if (NOT EXISTS "${HalideCompiler_${type}_targets}") + Halide_fail("HalideCompiler `${type}` libraries were requested but not found.") + endif () + + include("${HalideCompiler_${type}_targets}") + + # install(EXPORT)-generated target files define their (non-GLOBAL) + # imported targets with a multiple-inclusion guard that silently no-ops + # the include() above if Halide::Halide is already visible in this + # directory scope -- which happens whenever an ancestor directory (not a + # sibling: imported targets propagate down to subdirectories, not + # sideways) already loaded the other flavor. Detect that mismatch here + # and fail loudly rather than silently handing back the wrong flavor. + if (TARGET Halide::Halide) + get_target_property(Halide_actual_type Halide::Halide TYPE) + if ("${type}" STREQUAL "static") + set(Halide_expected_type STATIC_LIBRARY) + else () + set(Halide_expected_type SHARED_LIBRARY) + endif () + + if (NOT Halide_actual_type STREQUAL Halide_expected_type) + Halide_fail( + "HalideCompiler `${type}` libraries were requested, but the" + "${Halide_actual_type} flavor of Halide::Halide was already loaded by an" + "ancestor directory in this CMake configure. Only one flavor can be loaded" + "per directory scope (and its subdirectories) -- make sure every" + "find_package(Halide/HalideCompiler ...) call along this directory's" + "ancestor chain agrees on static vs. shared." + ) + endif () + unset(Halide_actual_type) + unset(Halide_expected_type) + endif () + + # The generated deps file may find_dependency() packages (e.g. Halide_LLVM, + # V8) whose Find modules are bundled alongside this config file rather than + # provided by CMake itself, so make sure they're on CMAKE_MODULE_PATH while + # it runs. + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") + include("${HalideCompiler_${type}_deps}" OPTIONAL) + list(REMOVE_ITEM CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +endmacro() + +# An explicit `static`/`shared` component on this package always wins. +# Otherwise, honor a preference recorded by the `Halide` package -- if a +# caller requested `find_package(Halide COMPONENTS static/shared)` without +# forcing a compiler load, that preference is stashed in a plain (directory- +# and call-site-scoped, see HalideConfig.cmake) variable so it's still +# respected whenever HalideCompiler does eventually get loaded (e.g. lazily, +# from add_halide_generator, in the same directory). Failing that, fall back +# to the ambient `Halide_SHARED_LIBS` variable, then `BUILD_SHARED_LIBS`. +# +# An explicit component that contradicts an already-recorded preference is a +# real conflict, not just an override -- fail loudly rather than silently +# picking one. +if (DEFINED _Halide_SHARED_LIBS_preference) + if (HalideCompiler_comp_static AND _Halide_SHARED_LIBS_preference) + Halide_fail( + "HalideCompiler `static` component conflicts with a `shared`" + "preference already recorded in this scope (e.g. via" + "find_package(Halide COMPONENTS shared))." + ) + elseif (HalideCompiler_comp_shared AND NOT _Halide_SHARED_LIBS_preference) + Halide_fail( + "HalideCompiler `shared` component conflicts with a `static`" + "preference already recorded in this scope (e.g. via" + "find_package(Halide COMPONENTS static))." + ) + endif () +endif () + +if (HalideCompiler_comp_static) + Halide_load_targets(static) +elseif (HalideCompiler_comp_shared) + Halide_load_targets(shared) +elseif (DEFINED _Halide_SHARED_LIBS_preference AND _Halide_SHARED_LIBS_preference) + Halide_load_targets(shared) +elseif (DEFINED _Halide_SHARED_LIBS_preference AND NOT _Halide_SHARED_LIBS_preference) + Halide_load_targets(static) +elseif (DEFINED Halide_SHARED_LIBS AND Halide_SHARED_LIBS) + Halide_load_targets(shared) +elseif (DEFINED Halide_SHARED_LIBS AND NOT Halide_SHARED_LIBS) + Halide_load_targets(static) +elseif (BUILD_SHARED_LIBS OR NOT DEFINED BUILD_SHARED_LIBS) + if (EXISTS "${HalideCompiler_shared_targets}") + Halide_load_targets(shared) + else () + Halide_load_targets(static) + endif () +else () + if (EXISTS "${HalideCompiler_static_targets}") + Halide_load_targets(static) + else () + Halide_load_targets(shared) + endif () +endif () + +## Load Python component +if (HalideCompiler_comp_Python OR "@WITH_PYTHON_BINDINGS@") + Halide_find_component_dependency( + Python Halide_Python + HINTS "@PACKAGE_Halide_Python_INSTALL_CMAKEDIR@" + ) +endif () + +## Hide variables and helper macros that are not part of our API. + +# Delete internal component tracking +foreach (comp IN LISTS HalideCompiler_known_components) + unset(HalideCompiler_comp_${comp}) +endforeach () + +unset(HalideCompiler_components) +unset(HalideCompiler_known_components) + +# Delete paths to generated CMake files +unset(HalideCompiler_shared_deps) +unset(HalideCompiler_shared_targets) +unset(HalideCompiler_static_deps) +unset(HalideCompiler_static_targets) + +# Delete internal macros -- CMake saves redefined macros and functions with a +# single underscore prefixed so, for example, Halide_fail is still available as +# _Halide_fail after one redefinition. Doing it twice overwrites both since the +# saving behavior doesn't continue past the first. +foreach (i RANGE 0 1) + macro(Halide_fail) # nolint -- poisoning internal APIs + message(FATAL_ERROR "Cannot call internal API: Halide_fail") + endmacro() + + macro(Halide_find_component_dependency) # nolint + message(FATAL_ERROR "Cannot call internal API: Halide_find_component_dependency") + endmacro() + + macro(Halide_load_targets) # nolint + message(FATAL_ERROR "Cannot call internal API: Halide_load_targets") + endmacro() +endforeach () diff --git a/packaging/common/HalideConfig.cmake b/packaging/common/HalideConfig.cmake index cf2d166e9449..89c2d83c3a36 100644 --- a/packaging/common/HalideConfig.cmake +++ b/packaging/common/HalideConfig.cmake @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.28) @PACKAGE_INIT@ -macro(Halide_fail message) # nolint -- required for find_package scoping - set(${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE "${message}") +macro(Halide_fail) # nolint -- required for find_package scoping + string(JOIN " " ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE ${ARGN}) set(${CMAKE_FIND_PACKAGE_NAME}_FOUND FALSE) return() endmacro() @@ -22,8 +22,8 @@ macro(Halide_find_component_dependency comp dep) # nolint endif () endmacro() -set(Halide_known_components Halide Python PNG JPEG static shared) -set(Halide_components Halide PNG JPEG) +set(Halide_known_components JIT Python PNG JPEG static shared) +set(Halide_components PNG JPEG) foreach (Halide_comp IN LISTS Halide_known_components) set(Halide_comp_${Halide_comp} NO) @@ -33,7 +33,11 @@ if (${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS) set(Halide_components ${${CMAKE_FIND_PACKAGE_NAME}_FIND_COMPONENTS}) endif () -# Parse components for static/shared preference. +# Parse components. `JIT` and `Python` force the compiled HalideCompiler package +# to be loaded below, even while cross-compiling. `PNG`/`JPEG` are resolved +# locally (Halide::ImageIO is header-only and lives in this package). `static`/ +# `shared` merely record a preference for whichever package eventually loads +# the compiled compiler -- see below. foreach (Halide_comp IN LISTS Halide_components) if (Halide_comp IN_LIST Halide_known_components) set(Halide_comp_${Halide_comp} YES) @@ -46,21 +50,20 @@ if (Halide_comp_static AND Halide_comp_shared) Halide_fail("Halide `static` and `shared` components are mutually exclusive.") endif () -# Inform downstreams of potential compatibility issues. For instance, exceptions -# and RTTI must both be enabled to build Python bindings and ASAN builds should -# not be mixed with non-ASAN builds. -set(WITH_AUTOSCHEDULERS "@WITH_AUTOSCHEDULERS@") -set(Halide_ENABLE_EXCEPTIONS "@Halide_ENABLE_EXCEPTIONS@") -set(Halide_ENABLE_RTTI "@Halide_ENABLE_RTTI@") -set(Halide_ASAN_ENABLED "@Halide_ASAN_ENABLED@") - ## -## Find dependencies based on components +# Load the platform-independent helpers: CMake authoring functions and +# header-only/interface targets. This never touches a compiled binary. ## -include(CMakeFindDependencyMacro) +set(Halide_HOST_TARGET @Halide_HOST_TARGET@) -find_dependency(HalideHelpers "@Halide_VERSION@" EXACT HINTS "@PACKAGE_Halide_INSTALL_HELPERSDIR@") +include(${CMAKE_CURRENT_LIST_DIR}/Halide-targets.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/HalideTargetHelpers.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/HalideGeneratorHelpers.cmake) + +## +# Image format components -- resolved locally, never forwarded to HalideCompiler. +## if (Halide_comp_PNG) Halide_find_component_dependency(PNG PNG) @@ -71,69 +74,76 @@ if (Halide_comp_JPEG) endif () ## -## Select static or shared and load CMake scripts +# Record a static/shared preference for whichever package eventually loads the +# compiled compiler, without forcing that load to happen now. This is a plain +# (not CACHE, not GLOBAL PROPERTY) variable, so it is naturally scoped to this +# directory and any subdirectories added after this point -- independent +# find_package(Halide ...) calls in unrelated directories of the same project +# can each record their own preference without conflicting. It is +# intentionally NOT unset below with the rest of our internal state, since its +# entire purpose is to still be visible when HalideCompiler is eventually +# loaded, possibly much later (e.g. lazily, from add_halide_generator). ## -set(Halide_static_targets "${CMAKE_CURRENT_LIST_DIR}/Halide-static-targets.cmake") -set(Halide_shared_targets "${CMAKE_CURRENT_LIST_DIR}/Halide-shared-targets.cmake") +if (Halide_comp_static) + set(_Halide_SHARED_LIBS_preference NO) +elseif (Halide_comp_shared) + set(_Halide_SHARED_LIBS_preference YES) +endif () -set(Halide_static_deps "${CMAKE_CURRENT_LIST_DIR}/Halide-static-deps.cmake") -set(Halide_shared_deps "${CMAKE_CURRENT_LIST_DIR}/Halide-shared-deps.cmake") +## +# Optionally load the compiled compiler/JIT package. This always happens for a +# native (non-cross-compiling) build, to preserve historical behavior for the +# common case. While cross-compiling, it only happens if explicitly requested +# via the `JIT` or `Python` components, since the point of this package is to +# be usable without ever touching a platform-specific binary. +## -macro(Halide_load_targets type) # nolint - if (NOT EXISTS "${Halide_${type}_targets}") - Halide_fail("Halide `${type}` libraries were requested but not found.") - endif () +include(CMakeFindDependencyMacro) - include("${Halide_${type}_targets}") - include("${Halide_${type}_deps}" OPTIONAL) -endmacro() +if (Halide_comp_JIT OR Halide_comp_Python OR NOT CMAKE_CROSSCOMPILING) + set(Halide_compiler_components "") + if (Halide_comp_Python) + list(APPEND Halide_compiler_components Python) + endif () -if (Halide_comp_static) - Halide_load_targets(static) -elseif (Halide_comp_shared) - Halide_load_targets(shared) -elseif (DEFINED Halide_SHARED_LIBS AND Halide_SHARED_LIBS) - Halide_load_targets(shared) -elseif (DEFINED Halide_SHARED_LIBS AND NOT Halide_SHARED_LIBS) - Halide_load_targets(static) -elseif (BUILD_SHARED_LIBS OR NOT DEFINED BUILD_SHARED_LIBS) - if (EXISTS "${Halide_shared_targets}") - Halide_load_targets(shared) - else () - Halide_load_targets(static) + # Forward an explicit static/shared request as a real component here, + # rather than relying solely on the `_Halide_SHARED_LIBS_preference` + # variable above. find_dependency() skips its underlying find_package() + # call when called with arguments identical to a previous call in this + # directory (or a descendant) -- since that preference variable isn't + # part of those arguments, two calls that differ only by preference + # would otherwise collide and silently reuse whichever flavor loaded + # first, defeating HalideCompilerConfig.cmake's own conflict detection. + # Forwarding the component keeps this common case (an explicit + # `static`/`shared` request) correctly distinguished. + if (Halide_comp_static) + list(APPEND Halide_compiler_components static) + elseif (Halide_comp_shared) + list(APPEND Halide_compiler_components shared) endif () -else () - if (EXISTS "${Halide_static_targets}") - Halide_load_targets(static) + + if (Halide_compiler_components) + find_dependency( + HalideCompiler "@Halide_VERSION@" EXACT HINTS "@PACKAGE_Halide_INSTALL_COMPILERDIR@" + COMPONENTS ${Halide_compiler_components} + ) else () - Halide_load_targets(shared) + find_dependency( + HalideCompiler "@Halide_VERSION@" EXACT HINTS "@PACKAGE_Halide_INSTALL_COMPILERDIR@" + ) endif () endif () -## Load Python component -if (Halide_comp_Python OR "@WITH_PYTHON_BINDINGS@") - Halide_find_component_dependency( - Python Halide_Python - HINTS "@PACKAGE_Halide_Python_INSTALL_CMAKEDIR@" - ) -endif () - ## Hide variables and helper macros that are not part of our API. -# Delete internal component tracking foreach (comp IN LISTS Halide_known_components) unset(Halide_comp_${comp}) endforeach () unset(Halide_components) unset(Halide_known_components) - -# Delete paths to generated CMake files -unset(Halide_shared_deps) -unset(Halide_shared_targets) -unset(Halide_static_deps) -unset(Halide_static_targets) +unset(Halide_compiler_components) # Delete internal macros -- CMake saves redefined macros and functions with a # single underscore prefixed so, for example, Halide_fail is still available as @@ -147,8 +157,4 @@ foreach (i RANGE 0 1) macro(Halide_find_component_dependency) # nolint message(FATAL_ERROR "Cannot call internal API: Halide_find_component_dependency") endmacro() - - macro(Halide_load_targets) # nolint - message(FATAL_ERROR "Cannot call internal API: Halide_load_targets") - endmacro() endforeach () diff --git a/packaging/common/HalideHelpersConfig.cmake b/packaging/common/HalideHelpersConfig.cmake deleted file mode 100644 index 57462d747f68..000000000000 --- a/packaging/common/HalideHelpersConfig.cmake +++ /dev/null @@ -1,11 +0,0 @@ -cmake_minimum_required(VERSION 3.28) -@PACKAGE_INIT@ - -set(Halide_HOST_TARGET @Halide_HOST_TARGET@) - -include(${CMAKE_CURRENT_LIST_DIR}/Halide-Interfaces.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/HalideTargetHelpers.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/HalideGeneratorHelpers.cmake) -include(${CMAKE_CURRENT_LIST_DIR}/TargetExportScript.cmake) - -check_required_components(${CMAKE_FIND_PACKAGE_NAME}) diff --git a/packaging/pip/CMakeLists.txt b/packaging/pip/CMakeLists.txt index d7d443be4e0f..b319a605bf7c 100644 --- a/packaging/pip/CMakeLists.txt +++ b/packaging/pip/CMakeLists.txt @@ -10,7 +10,7 @@ function(configure_trampoline PACKAGE INSTALL_DIR) endfunction() configure_trampoline(Halide "${Halide_INSTALL_CMAKEDIR}") -configure_trampoline(HalideHelpers "${Halide_INSTALL_HELPERSDIR}") +configure_trampoline(HalideCompiler "${Halide_INSTALL_COMPILERDIR}") install( FILES @@ -29,12 +29,12 @@ install( COMPONENT Halide_Python ) -# Same thing for HalideHelpers +# Same thing for HalideCompiler install( FILES - "${CMAKE_CURRENT_BINARY_DIR}/HalideHelpersConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/../HalideHelpersConfigVersion.cmake" - DESTINATION "${SKBUILD_DATA_DIR}/share/cmake/HalideHelpers" + "${CMAKE_CURRENT_BINARY_DIR}/HalideCompilerConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/../HalideCompilerConfigVersion.cmake" + DESTINATION "${SKBUILD_DATA_DIR}/share/cmake/HalideCompiler" COMPONENT Halide_Python ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e2229eca2c7..7c2fc39512a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -533,7 +533,7 @@ endif () # Build serialization, enabled by default if (WITH_SERIALIZATION) find_package(FlatBuffers REQUIRED) - _Halide_pkgdep(FlatBuffers) + _Halide_pkgdep(FlatBuffers NAMESPACE flatbuffers) target_link_libraries(Halide PRIVATE flatbuffers::flatbuffers) diff --git a/test/integration/CMakeLists.txt b/test/integration/CMakeLists.txt index 40e20e7b72c8..2479caea63dc 100644 --- a/test/integration/CMakeLists.txt +++ b/test/integration/CMakeLists.txt @@ -7,6 +7,19 @@ enable_testing() # Single-linkage JIT integration tests. ## +# Prefer ldd (Linux); fall back to otool -L (macOS). Both list a binary's +# linked dynamic libraries by path, so a "libHalide" match works the same way +# for either. If neither is available, the check_linkage_* tests below are +# skipped rather than added as tests that can never run. +find_program(Halide_LDD_EXECUTABLE ldd) +find_program(Halide_OTOOL_EXECUTABLE otool) + +if (Halide_LDD_EXECUTABLE) + set(Halide_check_linkage_command "${Halide_LDD_EXECUTABLE}") +elseif (Halide_OTOOL_EXECUTABLE) + set(Halide_check_linkage_command "${Halide_OTOOL_EXECUTABLE}" -L) +endif () + foreach (bsl IN ITEMS "" "-DBUILD_SHARED_LIBS=NO" "-DBUILD_SHARED_LIBS=YES") foreach (hsl IN ITEMS "" "-DHalide_SHARED_LIBS=NO" "-DHalide_SHARED_LIBS=YES") foreach (comp IN ITEMS "" "-Djit_HALIDE_COMPONENTS=static" "-Djit_HALIDE_COMPONENTS=shared") @@ -52,25 +65,35 @@ foreach (bsl IN ITEMS "" "-DBUILD_SHARED_LIBS=NO" "-DBUILD_SHARED_LIBS=YES") --test-command ${CMAKE_CTEST_COMMAND} --output-on-failure ) - # Run ldd on the output binary. The pass/fail regexes are set later. - add_test(NAME "${check_link_step}" COMMAND ldd "${build_dir}/main") - - # Make sure we don't run ldd before building... set_tests_properties("${build_step}" PROPERTIES FIXTURES_SETUP "${test_name}") - set_tests_properties("${check_link_step}" PROPERTIES FIXTURES_REQUIRED "${test_name}") - if (expect_shared) - set_tests_properties( - "${check_link_step}" - PROPERTIES - PASS_REGULAR_EXPRESSION "libHalide" + if (Halide_check_linkage_command) + # List the output binary's linked libraries. The pass/fail regexes are set below. + add_test( + NAME "${check_link_step}" + COMMAND ${Halide_check_linkage_command} "${build_dir}/main" ) - else () + + # Make sure we don't check linkage before building... set_tests_properties( "${check_link_step}" PROPERTIES - FAIL_REGULAR_EXPRESSION "libHalide" + FIXTURES_REQUIRED "${test_name}" ) + + if (expect_shared) + set_tests_properties( + "${check_link_step}" + PROPERTIES + PASS_REGULAR_EXPRESSION "libHalide" + ) + else () + set_tests_properties( + "${check_link_step}" + PROPERTIES + FAIL_REGULAR_EXPRESSION "libHalide" + ) + endif () endif () endforeach () endforeach () @@ -131,6 +154,38 @@ set_tests_properties( FAIL_REGULAR_EXPRESSION "Autoscheduler Halide::[A-Za-z0-9_]+ does not exist" ) +## +# Multi-directory static/shared independence tests +## + +add_test( + NAME multi_dir_independent_siblings + COMMAND + ${CMAKE_CTEST_COMMAND} + --build-and-test "${CMAKE_CURRENT_LIST_DIR}/multi_dir" "${CMAKE_CURRENT_BINARY_DIR}/multi-dir" + --build-generator Ninja + --build-options -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_BUILD_TYPE=Release + --test-command ${CMAKE_CTEST_COMMAND} --output-on-failure +) + +add_test( + NAME multi_dir_conflict_fails_cleanly + COMMAND + ${CMAKE_CTEST_COMMAND} + --build-and-test "${CMAKE_CURRENT_LIST_DIR}/multi_dir" + "${CMAKE_CURRENT_BINARY_DIR}/multi-dir-conflict" + --build-generator Ninja + --build-options -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} -DCMAKE_BUILD_TYPE=Release + -Dmulti_dir_TEST_CONFLICT=YES + --test-command ${CMAKE_CTEST_COMMAND} --output-on-failure +) +set_tests_properties( + multi_dir_conflict_fails_cleanly + PROPERTIES + WILL_FAIL TRUE + FAIL_REGULAR_EXPRESSION "already loaded by an ancestor directory" +) + ## # Cross compiling test ## @@ -161,4 +216,45 @@ if (CMAKE_HOST_SYSTEM_NAME MATCHES "Linux") set_tests_properties(cross_compile_host PROPERTIES FIXTURES_SETUP xc-host) set_tests_properties(cross_compile_aarch64 PROPERTIES FIXTURES_REQUIRED xc-host) + + # Verifies that explicitly forcing the JIT component while cross-compiling + # (find_package(Halide REQUIRED COMPONENTS JIT)) fails cleanly via a normal + # find_package error when no matching HalideCompiler package exists for the + # target, rather than silently finding a mismatched one. Requires a prefix + # containing only the Halide (helpers) package and no HalideCompiler; left + # empty, this test is skipped, since building that prefix requires extra CI + # setup that isn't expected of a typical local dev configure. + set(xc_HELPERS_ONLY_PREFIX "" + CACHE PATH + "A CMAKE_PREFIX_PATH entry containing only the installed Halide (helpers) \ +package and no HalideCompiler. See cross_compile_aarch64_force_jit_fails_cleanly." + ) + + if (xc_HELPERS_ONLY_PREFIX) + add_test( + NAME cross_compile_aarch64_force_jit_fails_cleanly + COMMAND + ${CMAKE_CTEST_COMMAND} + --build-and-test "${CMAKE_CURRENT_LIST_DIR}/xc" + "${CMAKE_CURRENT_BINARY_DIR}/xc-aarch64-force-jit" + --build-generator Ninja + --build-options + -DCMAKE_TOOLCHAIN_FILE=${CMAKE_CURRENT_LIST_DIR}/../../cmake/toolchain.linux-aarch64.cmake + -DCMAKE_BUILD_TYPE=Release + -Dxc_FORCE_JIT=YES + --test-command ${CMAKE_CTEST_COMMAND} --output-on-failure + ) + set_tests_properties( + cross_compile_aarch64_force_jit_fails_cleanly + PROPERTIES + # Override, not append: the ambient CMAKE_PREFIX_PATH (used by every + # other test in this file) points at a prefix that also has a + # (host-arch) HalideCompiler installed, which would otherwise still + # be found here and defeat the point of this test. + ENVIRONMENT "CMAKE_PREFIX_PATH=${xc_HELPERS_ONLY_PREFIX}" + WILL_FAIL TRUE + FAIL_REGULAR_EXPRESSION + "Could not find a package configuration file provided by \"HalideCompiler\"" + ) + endif () endif () diff --git a/test/integration/multi_dir/CMakeLists.txt b/test/integration/multi_dir/CMakeLists.txt new file mode 100644 index 000000000000..6becf534df1c --- /dev/null +++ b/test/integration/multi_dir/CMakeLists.txt @@ -0,0 +1,28 @@ +cmake_minimum_required(VERSION 3.28) +project(multi_dir) + +enable_testing() + +# main.cpp lives here (rather than duplicated in each consumer) since it's +# identical for both flavors; shared_consumer and static_consumer each +# reference it via a relative path. + +# Two independent find_package(Halide COMPONENTS shared/static) calls in +# sibling directories -- neither an ancestor of the other -- can each select +# their own flavor of Halide::Halide without conflict. +add_subdirectory(shared_consumer) +add_subdirectory(static_consumer) + +# A subdirectory that requests a flavor conflicting with the one already +# loaded by an ancestor directory must fail cleanly instead of silently +# keeping the ancestor's flavor. Off by default; see +# multi_dir_conflict_fails_cleanly in ../CMakeLists.txt, which configures +# this same project a second time with this option set. +option(multi_dir_TEST_CONFLICT + "Load HalideCompiler (shared) here, then probe a conflicting static request from a subdirectory" + OFF +) +if (multi_dir_TEST_CONFLICT) + find_package(Halide REQUIRED COMPONENTS shared JIT) + add_subdirectory(conflicting_consumer) +endif () diff --git a/test/integration/multi_dir/conflicting_consumer/CMakeLists.txt b/test/integration/multi_dir/conflicting_consumer/CMakeLists.txt new file mode 100644 index 000000000000..069b945a2d5d --- /dev/null +++ b/test/integration/multi_dir/conflicting_consumer/CMakeLists.txt @@ -0,0 +1,4 @@ +# The ancestor (parent) directory already loaded HalideCompiler as `shared` +# (see ../CMakeLists.txt, multi_dir_TEST_CONFLICT). Requesting `static` here +# must fail cleanly rather than silently keep the ancestor's `shared` flavor. +find_package(Halide REQUIRED COMPONENTS static JIT) diff --git a/test/integration/multi_dir/main.cpp b/test/integration/multi_dir/main.cpp new file mode 100644 index 000000000000..ae6775bb5769 --- /dev/null +++ b/test/integration/multi_dir/main.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +using namespace Halide; + +int main() { + Var x{"x"}, y{"y"}; + Func test{"test"}; + + test(x, y) = x + y; + Buffer output = test.realize({4, 4}); + + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + if (output(i, j) != (i + j)) { + fprintf(stderr, "output(%d, %d) = %d, expected %d", i, j, output(i, j), i + j); + return EXIT_FAILURE; + } + } + } + + printf("Success!\n"); + return EXIT_SUCCESS; +} diff --git a/test/integration/multi_dir/shared_consumer/CMakeLists.txt b/test/integration/multi_dir/shared_consumer/CMakeLists.txt new file mode 100644 index 000000000000..0bcebbf6b9d8 --- /dev/null +++ b/test/integration/multi_dir/shared_consumer/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Halide REQUIRED COMPONENTS shared JIT) + +get_target_property(multi_dir_actual_type Halide::Halide TYPE) +if (NOT multi_dir_actual_type STREQUAL "SHARED_LIBRARY") + message( + FATAL_ERROR "shared_consumer expected Halide::Halide to be SHARED_LIBRARY, got ${multi_dir_actual_type}" + ) +endif () +unset(multi_dir_actual_type) + +add_executable(shared_consumer_main "${CMAKE_CURRENT_SOURCE_DIR}/../main.cpp") +target_link_libraries(shared_consumer_main PRIVATE Halide::Halide) + +add_test(NAME shared_consumer_validate COMMAND shared_consumer_main) +set_tests_properties(shared_consumer_validate PROPERTIES PASS_REGULAR_EXPRESSION "Success!") diff --git a/test/integration/multi_dir/static_consumer/CMakeLists.txt b/test/integration/multi_dir/static_consumer/CMakeLists.txt new file mode 100644 index 000000000000..24ece097538f --- /dev/null +++ b/test/integration/multi_dir/static_consumer/CMakeLists.txt @@ -0,0 +1,15 @@ +find_package(Halide REQUIRED COMPONENTS static JIT) + +get_target_property(multi_dir_actual_type Halide::Halide TYPE) +if (NOT multi_dir_actual_type STREQUAL "STATIC_LIBRARY") + message( + FATAL_ERROR "static_consumer expected Halide::Halide to be STATIC_LIBRARY, got ${multi_dir_actual_type}" + ) +endif () +unset(multi_dir_actual_type) + +add_executable(static_consumer_main "${CMAKE_CURRENT_SOURCE_DIR}/../main.cpp") +target_link_libraries(static_consumer_main PRIVATE Halide::Halide) + +add_test(NAME static_consumer_validate COMMAND static_consumer_main) +set_tests_properties(static_consumer_validate PROPERTIES PASS_REGULAR_EXPRESSION "Success!") diff --git a/test/integration/xc/CMakeLists.txt b/test/integration/xc/CMakeLists.txt index 7220b2c09581..c34f98060524 100644 --- a/test/integration/xc/CMakeLists.txt +++ b/test/integration/xc/CMakeLists.txt @@ -4,7 +4,15 @@ project(xc) enable_testing() # Only need the platform-independent / source-only helpers. -find_package(HalideHelpers REQUIRED) +find_package(Halide REQUIRED) + +option(xc_FORCE_JIT + "Also require the JIT component, to test forcing HalideCompiler to load while cross-compiling" + OFF +) +if (xc_FORCE_JIT) + find_package(Halide REQUIRED COMPONENTS JIT) +endif () add_halide_generator(add_gen add.cpp) add_halide_library(add FROM xc::halide_generators::add_gen REGISTRATION add_reg_cpp) From beec3a04a6f88e34eb3ced6148a82ed3efc3b77b Mon Sep 17 00:00:00 2001 From: Alex Reinking Date: Wed, 22 Jul 2026 16:33:41 -0400 Subject: [PATCH 2/2] Set Halide_LLVM_ROOT for test/integration's static-flavor sub-builds --- .github/workflows/testing-ecosystem.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing-ecosystem.yml b/.github/workflows/testing-ecosystem.yml index 94cb69ff756f..510a9224e7b2 100644 --- a/.github/workflows/testing-ecosystem.yml +++ b/.github/workflows/testing-ecosystem.yml @@ -90,6 +90,7 @@ jobs: - name: Test the installed CMake package (test/integration) env: CMAKE_PREFIX_PATH: ${{ github.workspace }}/pkg-install + Halide_LLVM_ROOT: /usr/lib/llvm-${{ env.LLVM_VERSION }} run: | cmake -G Ninja -S test/integration -B build-integration \ -DCMAKE_CXX_COMPILER="clang++-${LLVM_VERSION}" \