Skip to content

Commit

Permalink
Scan generated export files to determine dependencies. (#8385)
Browse files Browse the repository at this point in the history
This commit contains a module for declaring that an export file might
depend on another CMake package that was found by find_package. Such
dependencies are collected in a project-wide property (rather than a
variable) along with a snippet of code that reconstructs the original
call.

Then, after we have installed an export file via install(EXPORT), we can
call a helper to add install rules that will read the file as-generated
by CMake to check whether any of these packages could be required.

CMake does not like to expose this information, in part because
generator expressions make computing the eventual link set undecidable.
Even so, for our purposes if Pkg:: appears in our link-libraries list,
then we need to find_package(Pkg).

This module implements that heuristic.

So why is this hard? It's because checking whether a dependency is
actually included is very complicated. A library will appear if:

1. It is SHARED or MODULE
2. It linked privately to a STATIC target
    - These appear as $<LINK_ONLY:${dep}>
3. It is STATIC and linked publicly to a SHARED target;
4. It is INTERFACE or ALIAS and linked publicly
5. It is included transitively via (4) and meets (1), (2), or (3)
6. I am not sure this set of rules is exhaustive.

There is an experimental feature in CMake 3.30 that will some day
replace this module.
  • Loading branch information
alexreinking authored Aug 10, 2024
1 parent 7b53a88 commit 3cdeb53
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 20 deletions.
5 changes: 4 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ include(CheckCXXSymbolExists)

# Make our custom helpers available throughout the project via include().
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/cmake)
include(HalideGeneratorHelpers)
include(HalideFeatures)
include(HalideGeneratorHelpers)
include(HalidePackageConfigHelpers)

# Build Halide as a shared lib by default, but still honor command-line settings.
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
Expand Down Expand Up @@ -187,6 +188,8 @@ find_package(Halide_LLVM 17...20 REQUIRED
COMPONENTS WebAssembly X86
OPTIONAL_COMPONENTS AArch64 ARM Hexagon NVPTX PowerPC RISCV)

_Halide_pkgdep(Halide_LLVM PACKAGE_VARS Halide_LLVM_SHARED_LIBS)

## Image formats

# This changes how find_xxx() commands work; the default is to find frameworks before
Expand Down
107 changes: 107 additions & 0 deletions cmake/HalidePackageConfigHelpers.cmake
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#[==========================================================================[
HalidePackageConfigHelpers
This module contains a system for declaring that an export file might
depend on another CMake package that was found by find_package. Such
dependencies are collected in a project-wide property (rather than a
variable) along with a snippet of code that reconstructs the original
call.
Then, after we have installed an export file via install(EXPORT), we can
call a helper to add install rules that will read the file as-generated
by CMake to check whether any of these packages could be required.
CMake does not like to expose this information, in part because generator
expressions make computing the eventual link set undecidable. Even so,
for our purposes if `Pkg::` appears in our link-libraries list, then
we need to find_package(Pkg). This module implements that heuristic.
So why is this hard? It's because checking whether a dependency is
actually included is very complicated. A library will appear if:
1. It is SHARED or MODULE
2. It linked privately to a STATIC target
- These appear as $<LINK_ONLY:${dep}>
3. It is STATIC and linked publicly to a SHARED target;
4. It is INTERFACE or ALIAS and linked publicly
5. It is included transitively via (4) and meets (1), (2), or (3)
6. I am not sure this set of rules is exhaustive.
There is an experimental feature in CMake 3.30 that will some day
replace this module.
#]==========================================================================]

##
# Helper for registering package dependencies

function(_Halide_pkgdep PKG)
cmake_parse_arguments(PARSE_ARGV 1 ARG "" "" "PACKAGE_VARS")

set(code "")
foreach (var IN LISTS ARG_PACKAGE_VARS)
string(APPEND code "set(${var} [[${${var}}]])\n")
endforeach ()

if ("${${PKG}_COMPONENTS}" STREQUAL "")
string(APPEND code "find_dependency(${PKG} ${${PKG}_VERSION})")
else ()
string(APPEND code
"find_dependency(\n"
" ${PKG} ${${PKG}_VERSION}\n"
" COMPONENTS ${${PKG}_COMPONENTS}\n"
")")
endif ()

set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" APPEND PROPERTY pkgdeps "${PKG}")
set_property(DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "pkgdeps[${PKG}]" "${code}")
endfunction()

##
# Helper for generating a file containing find_dependency() invocations
# by applying a heuristic to the actual dependency set.

function(_Halide_install_code)
# This is just to keep the code in cmake_install.cmake readable.
set(code "")
set(sep "")
math(EXPR ARGC "${ARGC} - 1")
foreach (i RANGE "${ARGC}")
string(APPEND code "${sep}${ARGV${i}}")
set(sep "\n ")
endforeach ()
install(CODE "${code}" COMPONENT "${ARG_COMPONENT}")
endfunction()

function(_Halide_install_pkgdeps)
cmake_parse_arguments(
PARSE_ARGV 0 ARG "" "COMPONENT;DESTINATION;FILE_NAME;EXPORT_FILE" ""
)

set(depFile "${CMAKE_CURRENT_BINARY_DIR}/${ARG_FILE_NAME}")

_Halide_install_code(
"file(READ \"\${CMAKE_INSTALL_PREFIX}/${ARG_DESTINATION}/${ARG_EXPORT_FILE}\" target_cmake)"
"file(WRITE \"${depFile}.in\" \"\")"
)

get_property(pkgdeps DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY pkgdeps)
foreach (dep IN LISTS pkgdeps)
get_property(pkgcode DIRECTORY "${PROJECT_SOURCE_DIR}" PROPERTY "pkgdeps[${dep}]")
_Halide_install_code(
"if (target_cmake MATCHES \"${dep}::\")"
" file(APPEND \"${depFile}.in\""
" [===[${pkgcode}]===] \"\\n\")"
"endif ()"
)
endforeach ()

_Halide_install_code(
"configure_file(\"${depFile}.in\" \"${depFile}\" COPYONLY)"
)

install(
FILES "${depFile}"
DESTINATION "${ARG_DESTINATION}"
COMPONENT "${ARG_COMPONENT}"
)
endfunction()
35 changes: 16 additions & 19 deletions packaging/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -138,25 +138,11 @@ else ()
set(type static)
endif ()

# If Halide explicitly links against shared LLVM or if it is a static library
# and we are not bundling our static dependencies, then end-users must have
# the relevant system libraries installed.
if (Halide_LLVM_SHARED_LIBS OR (NOT BUILD_SHARED_LIBS AND NOT Halide_BUNDLE_LLVM))
set(depFile "${CMAKE_CURRENT_BINARY_DIR}/Halide-${type}-deps.cmake")
file(CONFIGURE
OUTPUT "${depFile}"
CONTENT [[
set(Halide_LLVM_SHARED_LIBS @Halide_LLVM_SHARED_LIBS@)
find_dependency(
Halide_LLVM @Halide_LLVM_VERSION@
COMPONENTS @Halide_LLVM_COMPONENTS@
)
]] @ONLY)

install(FILES "${depFile}" "${Halide_SOURCE_DIR}/cmake/FindHalide_LLVM.cmake"
DESTINATION ${Halide_INSTALL_CMAKEDIR}
COMPONENT Halide_Development)
endif ()
install(FILES
"${Halide_SOURCE_DIR}/cmake/FindHalide_LLVM.cmake"
"${Halide_SOURCE_DIR}/cmake/FindV8.cmake"
DESTINATION ${Halide_INSTALL_CMAKEDIR}
COMPONENT Halide_Development)

install(EXPORT Halide_Targets
DESTINATION ${Halide_INSTALL_CMAKEDIR}
Expand Down Expand Up @@ -203,6 +189,17 @@ install(FILES
DESTINATION ${Halide_INSTALL_HELPERSDIR}
COMPONENT Halide_Development)

##
# Compute find_dependency calls for Halide
##

_Halide_install_pkgdeps(
FILE_NAME Halide-${type}-deps.cmake
EXPORT_FILE Halide-${type}-targets.cmake
DESTINATION "${Halide_INSTALL_CMAKEDIR}"
COMPONENT Halide_Development
)

##
# Documentation
##
Expand Down
3 changes: 3 additions & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,7 @@ if (WITH_SERIALIZATION)
flatbuffers 23.5.26 REQUIRED
NAMES flatbuffers Flatbuffers FlatBuffers
)
_Halide_pkgdep(flatbuffers)

if (Halide_USE_FETCHCONTENT AND NOT BUILD_SHARED_LIBS)
target_sources(Halide PRIVATE "$<TARGET_OBJECTS:flatbuffers::flatbuffers>")
Expand Down Expand Up @@ -585,6 +586,7 @@ endif ()

if (Halide_WASM_BACKEND STREQUAL "wabt")
find_package(wabt 1.0.36 REQUIRED)
_Halide_pkgdep(wabt)

if (Halide_USE_FETCHCONTENT AND NOT BUILD_SHARED_LIBS)
target_sources(Halide PRIVATE "$<TARGET_OBJECTS:wabt::wabt>")
Expand All @@ -596,6 +598,7 @@ if (Halide_WASM_BACKEND STREQUAL "wabt")
target_compile_definitions(Halide PRIVATE WITH_WABT)
elseif (Halide_WASM_BACKEND STREQUAL "V8")
find_package(V8 REQUIRED)
_Halide_pkgdep(V8)
target_link_libraries(Halide PRIVATE V8::V8)
target_compile_definitions(Halide PRIVATE WITH_V8)
elseif (Halide_WASM_BACKEND)
Expand Down

0 comments on commit 3cdeb53

Please sign in to comment.