From 57884eccec75c97b37141b1b638414fa036b3317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 11 Mar 2026 14:12:10 -0400 Subject: [PATCH 01/15] build: replace autotools with CMake for PCRE2 and atomic_ops MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PCRE2: Switch from autotools to CMake-based ExternalProject_Add. Update library naming from MinGW format to MSVC format. Handle PCRE2's internal debug postfix 'd' for Debug builds. Use RV_ADD_IMPORTED_LIBRARY and RV_STAGE_DEPENDENCY_LIBS macros. Atomic Ops: Use CMake build on all platforms, removing the autotools path (sh/autogen.sh, sh/configure, make). Use portable library naming via CMAKE_STATIC_LIBRARY_PREFIX/SUFFIX. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/atomic_ops.cmake | 37 +++----- cmake/dependencies/pcre2.cmake | 132 ++++++++++++++-------------- 2 files changed, 77 insertions(+), 92 deletions(-) diff --git a/cmake/dependencies/atomic_ops.cmake b/cmake/dependencies/atomic_ops.cmake index f509ce273..e175512bc 100644 --- a/cmake/dependencies/atomic_ops.cmake +++ b/cmake/dependencies/atomic_ops.cmake @@ -29,35 +29,17 @@ SET(_lib_dir ${_install_dir}/lib ) -IF(RV_TARGET_WINDOWS) - SET(_atomic_ops_lib_name - libatomic_ops.a - ) -ELSE() - SET(_atomic_ops_lib_name - ${CMAKE_STATIC_LIBRARY_PREFIX}atomic_ops${CMAKE_STATIC_LIBRARY_SUFFIX} - ) -ENDIF() +SET(_atomic_ops_lib_name + ${CMAKE_STATIC_LIBRARY_PREFIX}atomic_ops${CMAKE_STATIC_LIBRARY_SUFFIX} +) SET(_atomic_ops_lib ${_lib_dir}/${_atomic_ops_lib_name} ) -SET(_make_command - make -) -SET(_configure_command - sh ./configure -) -SET(_autogen_command - sh ./autogen.sh -) - -# Make sure NOT to enable GPL -SET(_configure_args - "--disable-gpl" +SET(_build_dir + ${RV_DEPS_BASE_DIR}/${_target}/build ) -LIST(APPEND _configure_args "--prefix=${_install_dir}") EXTERNALPROJECT_ADD( ${_target} @@ -67,10 +49,11 @@ EXTERNALPROJECT_ADD( URL_MD5 ${_download_hash} DOWNLOAD_NAME ${_target}_${_version}.zip DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - CONFIGURE_COMMAND ${_autogen_command} && ${_configure_command} ${_configure_args} - BUILD_COMMAND ${_make_command} -j${_cpu_count} - INSTALL_COMMAND ${_make_command} install - BUILD_IN_SOURCE TRUE + CONFIGURE_COMMAND ${CMAKE_COMMAND} -S ${RV_DEPS_BASE_DIR}/${_target}/src -B ${_build_dir} -DCMAKE_INSTALL_PREFIX=${_install_dir} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -Denable_gpl=OFF + BUILD_COMMAND ${CMAKE_COMMAND} --build ${_build_dir} --config ${CMAKE_BUILD_TYPE} -j${_cpu_count} + INSTALL_COMMAND ${CMAKE_COMMAND} --install ${_build_dir} --prefix ${_install_dir} --config ${CMAKE_BUILD_TYPE} + BUILD_IN_SOURCE FALSE BUILD_ALWAYS FALSE BUILD_BYPRODUCTS ${_atomic_ops_lib} USES_TERMINAL_BUILD TRUE diff --git a/cmake/dependencies/pcre2.cmake b/cmake/dependencies/pcre2.cmake index 1c8dc1474..c1352e139 100644 --- a/cmake/dependencies/pcre2.cmake +++ b/cmake/dependencies/pcre2.cmake @@ -17,18 +17,30 @@ SET(_download_hash # PCRE is not used for Linux and MacOS (Boost regex is used) in the current code. IF(RV_TARGET_WINDOWS) + # PCRE2's CMakeLists.txt sets CMAKE_DEBUG_POSTFIX to "d" internally, which cannot be overridden via cache variable. Account for it here. + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + SET(_pcre2_debug_postfix + "d" + ) + ELSE() + SET(_pcre2_debug_postfix + "" + ) + ENDIF() + + # MSVC library naming (CMake build) SET(_pcre2_libname - libpcre2-8-0${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_SHARED_LIBRARY_PREFIX}pcre2-8${_pcre2_debug_postfix}${CMAKE_SHARED_LIBRARY_SUFFIX} ) SET(_pcre2_libname_posix - libpcre2-posix-3${CMAKE_SHARED_LIBRARY_SUFFIX} + ${CMAKE_SHARED_LIBRARY_PREFIX}pcre2-posix${_pcre2_debug_postfix}${CMAKE_SHARED_LIBRARY_SUFFIX} ) SET(_pcre2_implibname - libpcre2-8.dll.a + ${CMAKE_IMPORT_LIBRARY_PREFIX}pcre2-8${_pcre2_debug_postfix}${CMAKE_IMPORT_LIBRARY_SUFFIX} ) SET(_pcre2_implibname_posix - libpcre2-posix.dll.a + ${CMAKE_IMPORT_LIBRARY_PREFIX}pcre2-posix${_pcre2_debug_postfix}${CMAKE_IMPORT_LIBRARY_SUFFIX} ) SET(_pcre2_libpath @@ -50,22 +62,17 @@ SET(_pcre2_include_dir ${_install_dir}/include ) -SET(_pcre2_configure_command - sh ./configure -) - -SET(_pcre2_autogen_command - sh ./autogen.sh -) - -LIST(APPEND _pcre2_configure_args "--prefix=${_install_dir}") -# Build as shared library -LIST(APPEND _pcre2_configure_args "--disable-static") -LIST(APPEND _pcre2_configure_args "--disable-pcre2grep-libbz2") -LIST(APPEND _pcre2_configure_args "--disable-pcre2grep-libz") +# PCRE2-specific CMake options (replaces autotools configure args) +LIST(APPEND _configure_options "-DBUILD_SHARED_LIBS=ON") +LIST(APPEND _configure_options "-DBUILD_STATIC_LIBS=OFF") +LIST(APPEND _configure_options "-DPCRE2_BUILD_PCRE2GREP=OFF") +LIST(APPEND _configure_options "-DPCRE2_BUILD_TESTS=OFF") +LIST(APPEND _configure_options "-DPCRE2_SUPPORT_LIBBZ2=OFF") +LIST(APPEND _configure_options "-DPCRE2_SUPPORT_LIBZ=OFF") +LIST(APPEND _configure_options "-DINSTALL_MSVC_PDB=OFF") IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") - LIST(APPEND _pcre2_configure_args "--enable-debug") + LIST(APPEND _configure_options "-DPCRE2_DEBUG=ON") ENDIF() EXTERNALPROJECT_ADD( @@ -78,66 +85,61 @@ EXTERNALPROJECT_ADD( SOURCE_DIR ${_source_dir} INSTALL_DIR ${_install_dir} DEPENDS ZLIB::ZLIB - CONFIGURE_COMMAND ${_pcre2_autogen_command} && ${_pcre2_configure_command} ${_pcre2_configure_args} - BUILD_COMMAND make -j${_cpu_count} + CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} + BUILD_COMMAND ${_cmake_build_command} + INSTALL_COMMAND ${_cmake_install_command} BUILD_IN_SOURCE TRUE BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_pcre2_libname} ${_pcre2_libname_posix} ${_pcre2_implibname} ${_pcre2_implibname_posix} + BUILD_BYPRODUCTS ${_pcre2_libpath} ${_pcre2_libpath_posix} ${_pcre2_implibpath} ${_pcre2_implibpath_posix} USES_TERMINAL_BUILD TRUE ) -# PCRE is not used for Linux and MacOS (Boost regex is used) in the current code. Copy library files manually since there are tools that are not needed in the -# bin folder. -ADD_CUSTOM_COMMAND( - COMMENT "Staging ${_target}'s shared library into ${RV_STAGE_BIN_DIR}" - OUTPUT ${RV_STAGE_BIN_DIR}/${_pcre2_libname} ${RV_STAGE_BIN_DIR}/${_pcre2_libname_posix} - COMMAND ${CMAKE_COMMAND} -E copy ${_pcre2_libpath} ${_pcre2_libpath_posix} -t ${RV_STAGE_BIN_DIR} - DEPENDS ${_target} -) - -ADD_CUSTOM_TARGET( - ${_target}-stage-target ALL - DEPENDS ${RV_STAGE_BIN_DIR}/${_pcre2_libname} ${RV_STAGE_BIN_DIR}/${_pcre2_libname_posix} -) - -ADD_DEPENDENCIES(dependencies ${_target}-stage-target) - -ADD_LIBRARY(pcre2-8 SHARED IMPORTED GLOBAL) -ADD_LIBRARY(pcre2-posix SHARED IMPORTED GLOBAL) - -ADD_DEPENDENCIES(pcre2-8 ${_target}) -ADD_DEPENDENCIES(pcre2-posix ${_target}) - -# Setup includes -SET(_pcre2_include_dir - ${_install_dir}/include +RV_STAGE_DEPENDENCY_LIBS( + TARGET + ${_target} + BIN_DIR + ${_bin_dir} + OUTPUTS + ${RV_STAGE_BIN_DIR}/${_pcre2_libname} + ${RV_STAGE_BIN_DIR}/${_pcre2_libname_posix} ) -FILE(MAKE_DIRECTORY ${_pcre2_include_dir}) -# Setup pcre2 8-bits target -SET_TARGET_PROPERTIES( - pcre2-8 - PROPERTIES IMPORTED_LOCATION ${_pcre2_libpath} - IMPORTED_IMPLIB ${_pcre2_implibpath} -) -TARGET_INCLUDE_DIRECTORIES( +RV_ADD_IMPORTED_LIBRARY( + NAME pcre2-8 - INTERFACE ${_pcre2_include_dir} + TYPE + SHARED + LOCATION + ${_pcre2_libpath} + SONAME + ${_pcre2_libname} + IMPLIB + ${_pcre2_implibpath} + INCLUDE_DIRS + ${_pcre2_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST ) TARGET_COMPILE_DEFINITIONS( pcre2-8 INTERFACE PCRE2_CODE_UNIT_WIDTH=8 ) -# Setup pcre2-posix target -SET_TARGET_PROPERTIES( - pcre2-posix - PROPERTIES IMPORTED_LOCATION ${_pcre2_libpath_posix} - IMPORTED_IMPLIB ${_pcre2_implibpath_posix} -) -TARGET_INCLUDE_DIRECTORIES( +RV_ADD_IMPORTED_LIBRARY( + NAME pcre2-posix - INTERFACE ${_pcre2_include_dir} + TYPE + SHARED + LOCATION + ${_pcre2_libpath_posix} + SONAME + ${_pcre2_libname_posix} + IMPLIB + ${_pcre2_implibpath_posix} + INCLUDE_DIRS + ${_pcre2_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST ) - -LIST(APPEND RV_DEPS_LIST pcre2-8 pcre2-posix) From 31d79fa89ec32261005dd56492ba2d9649cc4af0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 15:08:06 -0400 Subject: [PATCH 02/15] build: add find_package/pkg_config resolution infrastructure and dav1d dispatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add RV_FIND_DEPENDENCY macro with CONFIG and pkg_config fallback strategies. Refactor dav1d as the first dependency using the new dispatcher pattern, with pkg_config support for libraries that don't ship CMake config files. Signed-off-by: Cédrik Fuoco --- cmake/defaults/CYCOMMON.cmake | 7 +- cmake/defaults/rv_options.cmake | 11 ++ cmake/dependencies/CMakeLists.txt | 6 + cmake/dependencies/build/dav1d.cmake | 103 +++++++++++ cmake/dependencies/dav1d.cmake | 155 ++++++++-------- cmake/macros/rv_create_std_deps_vars.cmake | 198 +++++++++++++++++++++ cmake/macros/rv_find_dependency.cmake | 116 ++++++++++++ cmake/macros/rv_stage.cmake | 32 +++- 8 files changed, 544 insertions(+), 84 deletions(-) create mode 100644 cmake/dependencies/build/dav1d.cmake create mode 100644 cmake/macros/rv_find_dependency.cmake diff --git a/cmake/defaults/CYCOMMON.cmake b/cmake/defaults/CYCOMMON.cmake index 472db3ac2..70a711c48 100644 --- a/cmake/defaults/CYCOMMON.cmake +++ b/cmake/defaults/CYCOMMON.cmake @@ -16,10 +16,13 @@ SET(RV_DEPS_ATOMIC_OPS_DOWNLOAD_HASH # dav1d https://github.com/videolan/dav1d SET(RV_DEPS_DAV1D_VERSION - "1.4.3" + "1.5.3" ) SET(RV_DEPS_DAV1D_DOWNLOAD_HASH - "2c62106fda87a69122dc8709243a34e8" + "6a195752588586acf13349a1cceedab8" +) +SET(RV_DEPS_DAV1D_VERSION_LIB + "7" ) # doctest https://github.com/doctest/doctest diff --git a/cmake/defaults/rv_options.cmake b/cmake/defaults/rv_options.cmake index db9483325..c70c981fb 100644 --- a/cmake/defaults/rv_options.cmake +++ b/cmake/defaults/rv_options.cmake @@ -116,3 +116,14 @@ SET_PROPERTY( CACHE RV_FFMPEG PROPERTY STRINGS ${_RV_FFMPEG} ) + +# +# Dependency resolution option +# +# When ON, try find_package() for each dependency before building from source. +# When OFF (default), always build dependencies from source (current behavior). +# +# Per-dependency override: set RV_DEPS__FORCE_BUILD=ON to force building +# a specific dependency from source even when RV_DEPS_PREFER_INSTALLED=ON. +# +OPTION(RV_DEPS_PREFER_INSTALLED "Try find_package() for dependencies before building from source" OFF) diff --git a/cmake/dependencies/CMakeLists.txt b/cmake/dependencies/CMakeLists.txt index 02c78e304..c08f98fe9 100644 --- a/cmake/dependencies/CMakeLists.txt +++ b/cmake/dependencies/CMakeLists.txt @@ -8,6 +8,12 @@ INCLUDE(rv_create_std_deps_vars) INCLUDE(rv_make_std_lib_name) INCLUDE(rv_stage_dependency_libs) INCLUDE(rv_add_imported_library) +INCLUDE(rv_find_dependency) + +# All imported targets created by find_package() are automatically GLOBAL, so subdirectories can reference them without manual promotion. +SET(CMAKE_FIND_PACKAGE_TARGETS_GLOBAL + TRUE +) INCLUDE(ProcessorCount) PROCESSORCOUNT(_cpu_count) diff --git a/cmake/dependencies/build/dav1d.cmake b/cmake/dependencies/build/dav1d.cmake new file mode 100644 index 000000000..809114ce0 --- /dev/null +++ b/cmake/dependencies/build/dav1d.cmake @@ -0,0 +1,103 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Build dav1d from source via ExternalProject_Add. Included by cmake/dependencies/dav1d.cmake when no installed package is found. +# +# Expects these variables from the caller (set by RV_CREATE_STANDARD_DEPS_VARIABLES): _target, _version, _source_dir, _install_dir, _lib_dir, _bin_dir, +# _include_dir, _configure_command, _make_command And these dep-specific variables from the caller: _david_lib_name, _dav1d_lib +# + +SET(_download_url + "https://github.com/videolan/dav1d/archive/refs/tags/${_version}.zip" +) +SET(_download_hash + ${RV_DEPS_DAV1D_DOWNLOAD_HASH} +) + +# _lib_dir_name is needed for the meson --libdir option +IF(RHEL_VERBOSE) + SET(_lib_dir_name + lib64 + ) +ELSE() + SET(_lib_dir_name + lib + ) +ENDIF() + +IF(APPLE) + # Cross-file must be specified because if Rosetta is used to compile for x86_64 from ARM64, Meson still detects ARM64 as the default architecture. + IF(RV_TARGET_APPLE_X86_64) + SET(_meson_cross_file + "${PROJECT_SOURCE_DIR}/src/build/meson_arch_x86_64.txt" + ) + ELSEIF(RV_TARGET_APPLE_ARM64) + SET(_meson_cross_file + "${PROJECT_SOURCE_DIR}/src/build/meson_arch_arm64.txt" + ) + ENDIF() + + SET(_configure_command + ${_configure_command} "--cross-file" ${_meson_cross_file} + ) +ENDIF() + +SET(_default_library + shared +) + +EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${_source_dir} + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_configure_command} ./_build --libdir=${_lib_dir_name} --default-library=${_default_library} --prefix=${_install_dir} -Denable_tests=false + -Denable_tools=false + BUILD_COMMAND ${_make_command} -C _build + INSTALL_COMMAND ${_make_command} -C _build install + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_dav1d_lib} + USES_TERMINAL_BUILD TRUE +) + +IF(RV_TARGET_WINDOWS) + RV_ADD_IMPORTED_LIBRARY( + NAME + dav1d::dav1d + TYPE + SHARED + LOCATION + ${_dav1d_lib} + IMPLIB + ${_dav1d_implib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST + ) +ELSE() + RV_ADD_IMPORTED_LIBRARY( + NAME + dav1d::dav1d + TYPE + SHARED + LOCATION + ${_dav1d_lib} + SONAME + ${_david_lib_name} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST + ) +ENDIF() diff --git a/cmake/dependencies/dav1d.cmake b/cmake/dependencies/dav1d.cmake index 6107578af..e412a7270 100644 --- a/cmake/dependencies/dav1d.cmake +++ b/cmake/dependencies/dav1d.cmake @@ -7,101 +7,100 @@ RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_DAV1D" "${RV_DEPS_DAV1D_VERSION}" "ninja" "meson") RV_SHOW_STANDARD_DEPS_VARIABLES() -SET(_download_url - "https://github.com/videolan/dav1d/archive/refs/tags/${_version}.zip" -) -SET(_download_hash - ${RV_DEPS_DAV1D_DOWNLOAD_HASH} -) - -# _lib_dir_name is needed for the meson --libdir option -IF(RHEL_VERBOSE) - SET(_lib_dir_name - lib64 +# --- Library naming (shared on all platforms, same filenames for find and build) --- +IF(RV_TARGET_DARWIN) + SET(_david_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}dav1d.${RV_DEPS_DAV1D_VERSION_LIB}${CMAKE_SHARED_LIBRARY_SUFFIX} ) -ELSE() - SET(_lib_dir_name - lib +ELSEIF(RV_TARGET_LINUX) + SET(_david_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}dav1d${CMAKE_SHARED_LIBRARY_SUFFIX}.${RV_DEPS_DAV1D_VERSION_LIB} + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_david_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}dav1d${CMAKE_SHARED_LIBRARY_SUFFIX} ) ENDIF() -SET(_david_lib_name - ${CMAKE_STATIC_LIBRARY_PREFIX}dav1d${CMAKE_STATIC_LIBRARY_SUFFIX} -) - -SET(_dav1d_lib - ${_lib_dir}/${_david_lib_name} +# --- Try to find installed package --- +RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + dav1d + VERSION + ${RV_DEPS_DAV1D_VERSION} + PKG_CONFIG_NAME + dav1d + DEPS_LIST_TARGETS + dav1d::dav1d ) -IF(APPLE) - # Cross-file must be specified because if Rosetta is used to compile for x86_64 from ARM64, Meson still detects ARM64 as the default architecture. - - IF(RV_TARGET_APPLE_X86_64) - SET(_meson_cross_file - "${PROJECT_SOURCE_DIR}/src/build/meson_arch_x86_64.txt" - ) - ELSEIF(RV_TARGET_APPLE_ARM64) - SET(_meson_cross_file - "${PROJECT_SOURCE_DIR}/src/build/meson_arch_arm64.txt" - ) - ENDIF() - - SET(_configure_command - ${_configure_command} "--cross-file" ${_meson_cross_file} - ) -ENDIF() - +# Compute full library paths AFTER RV_FIND_DEPENDENCY (which may override _lib_dir, _bin_dir) IF(RV_TARGET_WINDOWS) - SET(_default_library - shared + SET(_dav1d_lib + ${_bin_dir}/${_david_lib_name} + ) + SET(_dav1d_implib_name + dav1d${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + SET(_dav1d_implib + ${_lib_dir}/${_dav1d_implib_name} ) ELSE() - SET(_default_library - static + SET(_dav1d_lib + ${_lib_dir}/${_david_lib_name} ) ENDIF() -EXTERNALPROJECT_ADD( - ${_target} - DOWNLOAD_NAME ${_target}_${_version}.zip - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${_source_dir} - INSTALL_DIR ${_install_dir} - URL ${_download_url} - URL_MD5 ${_download_hash} - CONFIGURE_COMMAND ${_configure_command} ./_build --libdir=${_lib_dir_name} --default-library=${_default_library} --prefix=${_install_dir} -Denable_tests=false - -Denable_tools=false - BUILD_COMMAND ${_make_command} -C _build - INSTALL_COMMAND ${_make_command} -C _build install - COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_dav1d_lib} - USES_TERMINAL_BUILD TRUE -) - -RV_ADD_IMPORTED_LIBRARY( - NAME - dav1d::dav1d - TYPE - STATIC - LOCATION - ${_dav1d_lib} - INCLUDE_DIRS - ${_include_dir} - DEPENDS - ${_target} - ADD_TO_DEPS_LIST -) +# --- Build from source if not found --- +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/dav1d.cmake) +ELSE() + # Found via find_package or pkg-config — create proper imported target with LOCATION + IF(NOT TARGET dav1d::dav1d) + IF(RV_TARGET_WINDOWS) + RV_ADD_IMPORTED_LIBRARY( + NAME + dav1d::dav1d + TYPE + SHARED + LOCATION + ${_dav1d_lib} + IMPLIB + ${_dav1d_implib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ELSE() + RV_ADD_IMPORTED_LIBRARY( + NAME + dav1d::dav1d + TYPE + SHARED + LOCATION + ${_dav1d_lib} + SONAME + ${_david_lib_name} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() + ENDIF() +ENDIF() +# --- Staging --- IF(RV_TARGET_WINDOWS) - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_install_dir}/bin OUTPUTS ${RV_STAGE_LIB_DIR}/${_david_lib_name}) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} USE_FLAG_FILE) ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_david_lib_name}) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} USE_FLAG_FILE) ENDIF() -# FFmpeg customization adding dav1d codec support to FFmpeg +# --- FFmpeg customization adding dav1d codec support --- SET_PROPERTY( GLOBAL APPEND PROPERTY "RV_FFMPEG_DEPENDS" RV_DEPS_DAV1D diff --git a/cmake/macros/rv_create_std_deps_vars.cmake b/cmake/macros/rv_create_std_deps_vars.cmake index 55a46ff17..bf0675567 100644 --- a/cmake/macros/rv_create_std_deps_vars.cmake +++ b/cmake/macros/rv_create_std_deps_vars.cmake @@ -161,3 +161,201 @@ MACRO(RV_SHOW_STANDARD_DEPS_VARIABLES) MESSAGE(DEBUG " ${_target}_VERSION='${${_target}_VERSION}'") MESSAGE(DEBUG " ${_target}_ROOT_DIR='${${_target}_ROOT_DIR}'") ENDMACRO() + +# +# RV_RESOLVE_IMPORTED_LOCATION — Resolve an imported target's library path across config variants +# +# MODULE-found targets (e.g. FindZLIB) often only set config-specific variants (IMPORTED_LOCATION_RELEASE, IMPORTED_LOCATION_NOCONFIG) not plain +# IMPORTED_LOCATION. This macro tries each variant and stores the first hit. +# +# Must be a MACRO so the output variable propagates to the caller's scope. +# +# Usage: RV_RESOLVE_IMPORTED_LOCATION( ) +# +MACRO(RV_RESOLVE_IMPORTED_LOCATION _rril_target _rril_out_var) + SET(${_rril_out_var} + "" + ) + IF(TARGET ${_rril_target}) + STRING(TOUPPER "${CMAKE_BUILD_TYPE}" _rril_config_upper) + FOREACH( + _rril_prop + IMPORTED_LOCATION IMPORTED_LOCATION_${_rril_config_upper} IMPORTED_LOCATION_RELEASE IMPORTED_LOCATION_NOCONFIG + ) + IF(NOT ${_rril_out_var}) + GET_TARGET_PROPERTY(${_rril_out_var} ${_rril_target} ${_rril_prop}) + ENDIF() + ENDFOREACH() + ENDIF() +ENDMACRO() + +# +# RV_RESOLVE_DARWIN_INSTALL_NAME — Get a macOS dylib's actual install name (LC_ID_DYLIB) +# +# On macOS, the install name recorded in a binary by the linker comes from the library's LC_ID_DYLIB, which may differ from the file path (e.g. Homebrew symlink +# paths). This macro resolves the real install name via otool -D and caches the result as a target property (RV_DARWIN_INSTALL_NAME) for later use in rpath +# fixup. +# +# For built-from-source deps where the library doesn't exist at configure time, this is a no-op. +# +MACRO(RV_RESOLVE_DARWIN_INSTALL_NAME _rdain_target) + IF(RV_TARGET_DARWIN + AND TARGET ${_rdain_target} + ) + RV_RESOLVE_IMPORTED_LOCATION(${_rdain_target} _rdain_loc) + IF(_rdain_loc + AND EXISTS "${_rdain_loc}" + ) + EXECUTE_PROCESS( + COMMAND otool -D "${_rdain_loc}" + OUTPUT_VARIABLE _rdain_otool_out + OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET + ) + # otool -D output: first line is file path, second line is the install name + STRING(REGEX MATCH "[^\n]+$" _rdain_install_name "${_rdain_otool_out}") + IF(_rdain_install_name) + SET_PROPERTY( + TARGET ${_rdain_target} + PROPERTY RV_DARWIN_INSTALL_NAME "${_rdain_install_name}" + ) + ENDIF() + ENDIF() + ENDIF() +ENDMACRO() + +# +# RV_MAKE_TARGETS_GLOBAL — Promote imported targets to GLOBAL visibility +# +# Imported targets created by find_package(CONFIG) are scoped to the calling directory. This promotes them to GLOBAL so all subdirectories can reference them. +# +MACRO(RV_MAKE_TARGETS_GLOBAL) + FOREACH( + _rmtg_target + ${ARGN} + ) + IF(TARGET ${_rmtg_target}) + SET_PROPERTY( + TARGET ${_rmtg_target} + PROPERTY IMPORTED_GLOBAL TRUE + ) + ENDIF() + ENDFOREACH() +ENDMACRO() + +# +# RV_SET_FOUND_PACKAGE_DIRS + +# Set directory variables from a found package +# +# When a dependency is found via find_package() instead of built from source, the _lib_dir, _bin_dir, _include_dir, and _install_dir variables set by +# RV_CREATE_STANDARD_DEPS_VARIABLES point at the (non-existent) ExternalProject install dir. +# This macro overrides them to point at the found package's actuallocation, +# so downstream code (including RV_STAGE_DEPENDENCY_LIBS) works identically for both paths. +# +# Also creates a dummy custom target for the dependency so that DEPENDS ${_target} works in the find path where there's no ExternalProject target. +# +# Must be a MACRO so the variable overrides propagate to the caller's scope. +# +MACRO(RV_SET_FOUND_PACKAGE_DIRS rv_deps_target find_package_name) + # ${find_package_name}_DIR is set by find_package(CONFIG) to the directory containing the CMake config files, typically: /lib/cmake// (3 + # levels up) /share/cmake// (3 levels up) + SET(_sfpd_config_dir + "${${find_package_name}_DIR}" + ) + + # Walk up 3 levels from the config dir to find the package root + GET_FILENAME_COMPONENT(_sfpd_root_3 "${_sfpd_config_dir}/../../.." ABSOLUTE) + GET_FILENAME_COMPONENT(_sfpd_root_2 "${_sfpd_config_dir}/../.." ABSOLUTE) + + IF(EXISTS "${_sfpd_root_3}/include" + OR EXISTS "${_sfpd_root_3}/lib" + ) + SET(_install_dir + "${_sfpd_root_3}" + ) + ELSEIF( + EXISTS "${_sfpd_root_2}/include" + OR EXISTS "${_sfpd_root_2}/lib" + ) + SET(_install_dir + "${_sfpd_root_2}" + ) + ELSE() + SET(_install_dir + "${_sfpd_root_3}" + ) + ENDIF() + + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + CACHE INTERNAL "" FORCE + ) + SET(_include_dir + "${_install_dir}/include" + ) + SET(_bin_dir + "${_install_dir}/bin" + ) + IF(RHEL_VERBOSE + AND EXISTS "${_install_dir}/lib64" + ) + SET(_lib_dir + "${_install_dir}/lib64" + ) + ELSE() + SET(_lib_dir + "${_install_dir}/lib" + ) + ENDIF() + + IF(NOT TARGET ${rv_deps_target}) + ADD_CUSTOM_TARGET(${rv_deps_target}) + ENDIF() +ENDMACRO() + +# +# RV_SET_FOUND_PKGCONFIG_DIRS + +# Set directory variables from a pkg-config found package +# +# Parallel to RV_SET_FOUND_PACKAGE_DIRS but uses variables set by pkg_check_modules() instead of find_package(CONFIG). +# Sets _lib_dir, _include_dir, _install_dir, _bin_dir in the caller's scope and creates a dummy custom target. +# _install_dir, _bin_dir in the caller's scope and creates a dummy custom target. +# +# Must be a MACRO so the variable overrides propagate to the caller's scope. +# +MACRO(RV_SET_FOUND_PKGCONFIG_DIRS rv_deps_target pc_prefix) + LIST(GET ${pc_prefix}_LIBRARY_DIRS 0 _lib_dir) + LIST(GET ${pc_prefix}_INCLUDE_DIRS 0 _include_dir) + + GET_FILENAME_COMPONENT(_install_dir "${_lib_dir}/.." ABSOLUTE) + SET(_bin_dir + "${_install_dir}/bin" + ) + + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + CACHE INTERNAL "" FORCE + ) + + IF(NOT TARGET ${rv_deps_target}) + ADD_CUSTOM_TARGET(${rv_deps_target}) + ENDIF() +ENDMACRO() + +# +# RV_PRINT_PACKAGE_INFO — Print diagnostic info for a found package +# +MACRO(RV_PRINT_PACKAGE_INFO package_name) + MESSAGE(STATUS " ${package_name} package info:") + IF(DEFINED ${package_name}_VERSION) + MESSAGE(STATUS " Version: ${${package_name}_VERSION}") + ENDIF() + IF(DEFINED ${package_name}_DIR) + MESSAGE(STATUS " Config dir: ${${package_name}_DIR}") + ENDIF() + MESSAGE(STATUS " Root: ${_install_dir}") + MESSAGE(STATUS " Include: ${_include_dir}") + MESSAGE(STATUS " Lib: ${_lib_dir}") + MESSAGE(STATUS " Bin: ${_bin_dir}") +ENDMACRO() diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake new file mode 100644 index 000000000..9d49b854f --- /dev/null +++ b/cmake/macros/rv_find_dependency.cmake @@ -0,0 +1,116 @@ +# +# Copyright (C) 2026 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# RV_FIND_DEPENDENCY — Try to find a pre-built package, set ${TARGET}_FOUND +# +# Checks RV_DEPS_PREFER_INSTALLED and per-dep RV_DEPS__FORCE_BUILD. Tries find_package(CONFIG) first, then falls back to pkg-config if PKG_CONFIG_NAME is +# set. If found: sets up directory vars, adds to RV_DEPS_LIST. If not found or finding disabled: sets ${TARGET}_FOUND=FALSE (caller handles fallback). +# +# Imported targets from find_package(CONFIG) are automatically GLOBAL via CMAKE_FIND_PACKAGE_TARGETS_GLOBAL (set in cmake/dependencies/CMakeLists.txt). For +# pkg-config, the caller is responsible for creating proper imported targets (with LOCATION) after this macro returns — INTERFACE targets won't work because +# rv_stage.cmake expects LOCATION on all RV_DEPS_LIST entries. +# +# Must be a MACRO because RV_SET_FOUND_PACKAGE_DIRS / RV_SET_FOUND_PKGCONFIG_DIRS set _lib_dir, _bin_dir, _include_dir in the caller's scope via text +# substitution. +# +# cmake-format: off +# Usage: +# RV_FIND_DEPENDENCY( +# TARGET # REQUIRED: e.g., RV_DEPS_IMATH +# PACKAGE # REQUIRED: e.g., Imath +# [VERSION ] # Optional minimum version +# [PKG_CONFIG_NAME ] # Optional pkg-config module name for fallback +# [DEPS_LIST_TARGETS ...] # Targets to append to RV_DEPS_LIST +# ) +# cmake-format: on +MACRO(RV_FIND_DEPENDENCY) + CMAKE_PARSE_ARGUMENTS(_RFD "" "TARGET;PACKAGE;VERSION;PKG_CONFIG_NAME" "DEPS_LIST_TARGETS" ${ARGN}) + + SET(${_RFD_TARGET}_FOUND + FALSE + ) + SET(_RFD_FOUND_VIA + "" + ) + + IF(RV_DEPS_PREFER_INSTALLED + AND NOT RV_DEPS_${_RFD_TARGET}_FORCE_BUILD + ) + + # Strategy 1: CMake CONFIG mode + IF(_RFD_VERSION) + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} CONFIG) + ELSE() + FIND_PACKAGE(${_RFD_PACKAGE} CONFIG) + ENDIF() + + IF(${_RFD_PACKAGE}_FOUND) + SET(_RFD_FOUND_VIA + "config" + ) + ENDIF() + + # Strategy 2: pkg-config fallback + IF(NOT ${_RFD_PACKAGE}_FOUND + AND _RFD_PKG_CONFIG_NAME + ) + FIND_PACKAGE(PkgConfig QUIET) + IF(PKG_CONFIG_FOUND) + IF(_RFD_VERSION) + PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET "${_RFD_PKG_CONFIG_NAME}>=${_RFD_VERSION}") + ELSE() + PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET ${_RFD_PKG_CONFIG_NAME}) + ENDIF() + + IF(${_RFD_TARGET}_PC_FOUND) + SET(${_RFD_PACKAGE}_FOUND + TRUE + ) + SET(${_RFD_PACKAGE}_VERSION + "${${_RFD_TARGET}_PC_VERSION}" + ) + SET(_RFD_FOUND_VIA + "pkgconfig" + ) + ENDIF() + ENDIF() + ENDIF() + + # Common handling for found packages + IF(${_RFD_PACKAGE}_FOUND) + SET(${_RFD_TARGET}_FOUND + TRUE + ) + + IF(_RFD_FOUND_VIA STREQUAL "config") + MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via CMake config. Using installed package.") + RV_SET_FOUND_PACKAGE_DIRS(${_RFD_TARGET} ${_RFD_PACKAGE}) + ELSEIF(_RFD_FOUND_VIA STREQUAL "pkgconfig") + MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via pkg-config. Using installed package.") + RV_SET_FOUND_PKGCONFIG_DIRS(${_RFD_TARGET} ${_RFD_TARGET}_PC) + ENDIF() + + FOREACH( + _rfd_dep + ${_RFD_DEPS_LIST_TARGETS} + ) + LIST(APPEND RV_DEPS_LIST ${_rfd_dep}) + RV_RESOLVE_DARWIN_INSTALL_NAME(${_rfd_dep}) + ENDFOREACH() + + STRING(TOUPPER ${_RFD_PACKAGE} _RFD_PKG_UPPER) + SET(RV_DEPS_${_RFD_PKG_UPPER}_VERSION + ${${_RFD_PACKAGE}_VERSION} + CACHE INTERNAL "" FORCE + ) + + RV_PRINT_PACKAGE_INFO(${_RFD_PACKAGE}) + ELSE() + MESSAGE(STATUS "${_RFD_PACKAGE} not found — will build from source") + ENDIF() + ENDIF() +ENDMACRO() diff --git a/cmake/macros/rv_stage.cmake b/cmake/macros/rv_stage.cmake index 399fc3dd9..4e3212bc9 100644 --- a/cmake/macros/rv_stage.cmake +++ b/cmake/macros/rv_stage.cmake @@ -92,6 +92,9 @@ FUNCTION(rv_stage) IF(_native_target_type STREQUAL "EXECUTABLE" OR _native_target_type STREQUAL "SHARED_LIBRARY" ) + # Batch all -change arguments into a single install_name_tool invocation per target. Multiple separate invocations each re-sign the binary, which can + # trigger macOS code signing lockouts ("Operation not permitted") on arm64. + SET(_change_args) FOREACH( dep ${RV_DEPS_LIST} @@ -102,13 +105,34 @@ FUNCTION(rv_stage) TARGET ${dep} PROPERTY LOCATION ) - GET_FILENAME_COMPONENT(dep_file_name ${dep_file_path} NAME) - ADD_CUSTOM_COMMAND( - COMMENT "Fixing ${dep_file_name}'s rpath in ${arg_TARGET}" TARGET ${arg_TARGET} POST_BUILD - COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change "${dep_file_path}" "@rpath/${dep_file_name}" "$" + # For found packages (e.g. Homebrew), the install name recorded by the linker may differ from the target's LOCATION (different symlink paths). Use + # the cached install name from RV_RESOLVE_DARWIN_INSTALL_NAME if available; this is a no-op for built-from-source deps where the library doesn't + # exist at configure time. + GET_PROPERTY( + _dep_install_name + TARGET ${dep} + PROPERTY RV_DARWIN_INSTALL_NAME ) + IF(_dep_install_name) + SET(dep_change_path + "${_dep_install_name}" + ) + GET_FILENAME_COMPONENT(dep_file_name "${_dep_install_name}" NAME) + ELSE() + SET(dep_change_path + "${dep_file_path}" + ) + GET_FILENAME_COMPONENT(dep_file_name ${dep_file_path} NAME) + ENDIF() + LIST(APPEND _change_args -change "${dep_change_path}" "@rpath/${dep_file_name}") ENDIF() ENDFOREACH() + IF(_change_args) + ADD_CUSTOM_COMMAND( + COMMENT "Fixing dependency rpaths in ${arg_TARGET}" TARGET ${arg_TARGET} POST_BUILD + COMMAND ${CMAKE_INSTALL_NAME_TOOL} ${_change_args} "$" + ) + ENDIF() ENDIF() ENDIF() ENDIF() From 2e40100258fbc17f848e980e1485cc6ccceb2ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 15:08:10 -0400 Subject: [PATCH 03/15] build: add Imath find_package dispatcher and version match options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor Imath to use the find_package CONFIG dispatcher pattern. Add RV_DEPS_VERSION_MATCH and per dependency RV_DEPS__VERSION_MATCH options to control EXACT vs MINIMUM version matching behavior. Improve diagnostic messages to include version and verbose output when a package is not found. Signed-off-by: Cédrik Fuoco --- cmake/defaults/rv_options.cmake | 23 +++++-- cmake/dependencies/build/imath.cmake | 65 ++++++++++++++++++ cmake/dependencies/imath.cmake | 98 ++++++++++++--------------- cmake/macros/rv_find_dependency.cmake | 27 ++++++-- 4 files changed, 150 insertions(+), 63 deletions(-) create mode 100644 cmake/dependencies/build/imath.cmake diff --git a/cmake/defaults/rv_options.cmake b/cmake/defaults/rv_options.cmake index c70c981fb..806d0094a 100644 --- a/cmake/defaults/rv_options.cmake +++ b/cmake/defaults/rv_options.cmake @@ -120,10 +120,25 @@ SET_PROPERTY( # # Dependency resolution option # -# When ON, try find_package() for each dependency before building from source. -# When OFF (default), always build dependencies from source (current behavior). +# When ON, try find_package() for each dependency before building from source. When OFF (default), always build dependencies from source (current behavior). # -# Per-dependency override: set RV_DEPS__FORCE_BUILD=ON to force building -# a specific dependency from source even when RV_DEPS_PREFER_INSTALLED=ON. +# Per-dependency override: set RV_DEPS__FORCE_BUILD=ON to force building a specific dependency from source even when RV_DEPS_PREFER_INSTALLED=ON. # OPTION(RV_DEPS_PREFER_INSTALLED "Try find_package() for dependencies before building from source" OFF) + +# +# Version matching mode for dependency resolution. +# +# Controls how RV_FIND_DEPENDENCY matches versions when RV_DEPS_PREFER_INSTALLED=ON. EXACT — require the exact version specified in CY*.cmake (default, +# recommended) MINIMUM — accept the specified version or newer (standard find_package behavior) +# +# Per-dependency override: set RV_DEPS__VERSION_MATCH=EXACT or MINIMUM to override the global setting for a specific dependency. +# +SET(RV_DEPS_VERSION_MATCH + "EXACT" + CACHE STRING "Version matching mode for find_package: EXACT or MINIMUM" +) +SET_PROPERTY( + CACHE RV_DEPS_VERSION_MATCH + PROPERTY STRINGS EXACT MINIMUM +) diff --git a/cmake/dependencies/build/imath.cmake b/cmake/dependencies/build/imath.cmake new file mode 100644 index 000000000..348d5e457 --- /dev/null +++ b/cmake/dependencies/build/imath.cmake @@ -0,0 +1,65 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Build Imath from source via ExternalProject_Add. Included by cmake/dependencies/imath.cmake when no installed package is found. +# +# Expects these variables from the caller (set by RV_CREATE_STANDARD_DEPS_VARIABLES): _target, _version, _install_dir, _configure_options, _cmake_build_command, +# _cmake_install_command And these dep-specific variables from the caller: _libname, _libpath, _implibpath (Windows only) +# + +SET(_download_url + "https://github.com/AcademySoftwareFoundation/Imath/archive/refs/tags/v${_version}.zip" +) + +SET(_download_hash + "${RV_DEPS_IMATH_DOWNLOAD_HASH}" +) + +# Override include dir to Imath subdirectory for build-from-source +SET(_include_dir + ${_install_dir}/include/Imath +) + +LIST(APPEND _imath_byproducts ${_libpath}) + +IF(RV_TARGET_WINDOWS) + LIST(APPEND _imath_byproducts ${_implibpath}) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} + BUILD_COMMAND ${_cmake_build_command} + INSTALL_COMMAND ${_cmake_install_command} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_imath_byproducts} + USES_TERMINAL_BUILD TRUE +) + +RV_ADD_IMPORTED_LIBRARY( + NAME + Imath::Imath + TYPE + SHARED + LOCATION + ${_libpath} + SONAME + ${_libname} + IMPLIB + ${_implibpath} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST +) diff --git a/cmake/dependencies/imath.cmake b/cmake/dependencies/imath.cmake index c82458665..50a0e1683 100644 --- a/cmake/dependencies/imath.cmake +++ b/cmake/dependencies/imath.cmake @@ -6,18 +6,7 @@ RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_IMATH" "${RV_DEPS_IMATH_VERSION}" "" "") -SET(_download_url - "https://github.com/AcademySoftwareFoundation/Imath/archive/refs/tags/v${_version}.zip" -) - -SET(_download_hash - "${RV_DEPS_IMATH_DOWNLOAD_HASH}" -) - -SET(_include_dir - ${_install_dir}/include/Imath -) - +# --- Library naming (shared on all platforms, same filenames for find and build) --- IF(RV_TARGET_DARWIN) SET(_libname ${CMAKE_SHARED_LIBRARY_PREFIX}Imath-${RV_DEPS_IMATH_LIB_MAJOR}${RV_DEBUG_POSTFIX}.${RV_DEPS_IMATH_LIB_VER}${CMAKE_SHARED_LIBRARY_SUFFIX} @@ -32,62 +21,61 @@ ELSEIF(RV_TARGET_WINDOWS) ) ENDIF() +# --- Try to find installed package --- +RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + Imath + VERSION + ${RV_DEPS_IMATH_VERSION} + DEPS_LIST_TARGETS + Imath::Imath +) + +# Compute paths AFTER RV_FIND_DEPENDENCY (which may override _lib_dir) +SET(_libpath + ${_lib_dir}/${_libname} +) + SET(RV_DEPS_IMATH_CMAKE_DIR ${_lib_dir}/cmake/Imath CACHE STRING "Path to Imath CMake files ${_target}" ) - SET(RV_DEPS_IMATH_CMAKE_DIR ${_lib_dir}/cmake/Imath ) -SET(_libpath - ${_lib_dir}/${_libname} -) - -LIST(APPEND _imath_byproducts ${_libpath}) - IF(RV_TARGET_WINDOWS) SET(_implibpath ${_install_dir}/lib/${CMAKE_IMPORT_LIBRARY_PREFIX}Imath-${RV_DEPS_IMATH_LIB_MAJOR}${RV_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} ) - LIST(APPEND _imath_byproducts ${_implibpath}) ENDIF() -EXTERNALPROJECT_ADD( - ${_target} - URL ${_download_url} - URL_MD5 ${_download_hash} - DOWNLOAD_NAME ${_target}_${_version}.zip - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src - INSTALL_DIR ${_install_dir} - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} - BUILD_COMMAND ${_cmake_build_command} - INSTALL_COMMAND ${_cmake_install_command} - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_imath_byproducts} - USES_TERMINAL_BUILD TRUE -) +# --- Build from source if not found --- +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/imath.cmake) +ELSE() + # CONFIG found — Imath::Imath target exists with proper LOCATION. For pkg-config (unlikely), create proper imported target. + IF(NOT TARGET Imath::Imath) + RV_ADD_IMPORTED_LIBRARY( + NAME + Imath::Imath + TYPE + SHARED + LOCATION + ${_libpath} + SONAME + ${_libname} + IMPLIB + ${_implibpath} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() +ENDIF() +# --- Staging (shared — same library name for both paths) --- RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} LIBNAME ${_libname}) - -RV_ADD_IMPORTED_LIBRARY( - NAME - Imath::Imath - TYPE - SHARED - LOCATION - ${_libpath} - SONAME - ${_libname} - IMPLIB - ${_implibpath} - INCLUDE_DIRS - ${_include_dir} - DEPENDS - ${_target} - ADD_TO_DEPS_LIST -) diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake index 9d49b854f..188300be5 100644 --- a/cmake/macros/rv_find_dependency.cmake +++ b/cmake/macros/rv_find_dependency.cmake @@ -22,7 +22,7 @@ # RV_FIND_DEPENDENCY( # TARGET # REQUIRED: e.g., RV_DEPS_IMATH # PACKAGE # REQUIRED: e.g., Imath -# [VERSION ] # Optional minimum version +# [VERSION ] # Optional version (EXACT or MINIMUM per RV_DEPS_VERSION_MATCH) # [PKG_CONFIG_NAME ] # Optional pkg-config module name for fallback # [DEPS_LIST_TARGETS ...] # Targets to append to RV_DEPS_LIST # ) @@ -41,9 +41,24 @@ MACRO(RV_FIND_DEPENDENCY) AND NOT RV_DEPS_${_RFD_TARGET}_FORCE_BUILD ) + # Determine version match mode: per-dep override > global default + IF(DEFINED RV_DEPS_${_RFD_TARGET}_VERSION_MATCH) + SET(_rfd_match_mode + "${RV_DEPS_${_RFD_TARGET}_VERSION_MATCH}" + ) + ELSE() + SET(_rfd_match_mode + "${RV_DEPS_VERSION_MATCH}" + ) + ENDIF() + # Strategy 1: CMake CONFIG mode IF(_RFD_VERSION) - FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} CONFIG) + IF(_rfd_match_mode STREQUAL "EXACT") + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} EXACT CONFIG) + ELSE() + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} CONFIG) + ENDIF() ELSE() FIND_PACKAGE(${_RFD_PACKAGE} CONFIG) ENDIF() @@ -61,7 +76,11 @@ MACRO(RV_FIND_DEPENDENCY) FIND_PACKAGE(PkgConfig QUIET) IF(PKG_CONFIG_FOUND) IF(_RFD_VERSION) - PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET "${_RFD_PKG_CONFIG_NAME}>=${_RFD_VERSION}") + IF(_rfd_match_mode STREQUAL "EXACT") + PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET "${_RFD_PKG_CONFIG_NAME}=${_RFD_VERSION}") + ELSE() + PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET "${_RFD_PKG_CONFIG_NAME}>=${_RFD_VERSION}") + ENDIF() ELSE() PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET ${_RFD_PKG_CONFIG_NAME}) ENDIF() @@ -110,7 +129,7 @@ MACRO(RV_FIND_DEPENDENCY) RV_PRINT_PACKAGE_INFO(${_RFD_PACKAGE}) ELSE() - MESSAGE(STATUS "${_RFD_PACKAGE} not found — will build from source") + MESSAGE(STATUS "${_RFD_PACKAGE} ${_RFD_VERSION} not found. It will be build from source") ENDIF() ENDIF() ENDMACRO() From 6faa14e1b7326bfd5745d67b1795ea7f3bbf1e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 15:08:21 -0400 Subject: [PATCH 04/15] build: add Boost find_package dispatcher and fix Homebrew include path contamination MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor Boost to the find first dispatcher pattern: prefer an installed Boost (e.g. Homebrew) via find_package(CONFIG COMPONENTS ...) and fall back to building from source. Extract ExternalProject_Add build logic into cmake/dependencies/build/boost.cmake. RV_FIND_DEPENDENCY gains a COMPONENTS parameter to forward component lists to find_package. Fix a critical include path contamination issue on macOS with Homebrew. CMake normalizes ".." before resolving symlinks, so walking up from a symlinked config dir lands on the shared prefix (/opt/homebrew) instead of the Cellar root. This caused unrelated Homebrew headers to leak into compilation commands. RV_SET_FOUND_PACKAGE_DIRS now resolves symlinks before walking up. RV_FIND_DEPENDENCY replaces stale include dirs on imported targets. boost.cmake fixes Boost::headers specifically. RV_STAGE_DEPENDENCY_LIBS gains TARGET_LIBS for staging via generator expressions, with make_directory to ensure destination dirs exist and VERBATIM to prevent shell metacharacter misinterpretation. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/boost.cmake | 237 ++++++++------------ cmake/dependencies/build/boost.cmake | 144 ++++++++++++ cmake/macros/rv_create_std_deps_vars.cmake | 39 +++- cmake/macros/rv_find_dependency.cmake | 42 +++- cmake/macros/rv_stage_dependency_libs.cmake | 145 +++++++++++- 5 files changed, 442 insertions(+), 165 deletions(-) create mode 100644 cmake/dependencies/build/boost.cmake diff --git a/cmake/dependencies/boost.cmake b/cmake/dependencies/boost.cmake index e1929cbc3..d9bf5ad8c 100644 --- a/cmake/dependencies/boost.cmake +++ b/cmake/dependencies/boost.cmake @@ -15,18 +15,10 @@ SET(_ext_boost_version SET(_major_minor_version ${RV_DEPS_BOOST_MAJOR_MINOR_VERSION} ) -SET(_download_hash - ${RV_DEPS_BOOST_DOWNLOAD_HASH} -) -RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_BOOST" "${_ext_boost_version}" "" "") +RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_BOOST" "${_ext_boost_version}" "" "" FORCE_LIB) RV_SHOW_STANDARD_DEPS_VARIABLES() -STRING(REPLACE "." "_" _version_with_underscore ${_version}) -SET(_download_url - "https://archives.boost.io/release/${_version}/source/boost_${_version_with_underscore}.tar.gz" -) - SET(_boost_libs atomic chrono @@ -44,10 +36,32 @@ SET(_boost_libs timer ) -SET(_lib_dir - ${_install_dir}/lib +# Build DEPS_LIST_TARGETS from _boost_libs +SET(_boost_deps_list_targets + "" +) +FOREACH( + _boost_lib + ${_boost_libs} +) + LIST(APPEND _boost_deps_list_targets Boost::${_boost_lib}) +ENDFOREACH() + +# --- Try to find installed package --- +RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + Boost + VERSION + ${_ext_boost_version} + COMPONENTS + ${_boost_libs} + DEPS_LIST_TARGETS + ${_boost_deps_list_targets} ) +# --- Library naming (shared between find and build paths) --- # Note: Boost has a custom lib naming scheme on windows IF(RV_TARGET_WINDOWS) SET(BOOST_SHARED_LIBRARY_PREFIX @@ -80,6 +94,7 @@ ELSE() ) ENDIF() +# Compute per-lib paths AFTER RV_FIND_DEPENDENCY (which may override _lib_dir) FOREACH( _boost_lib ${_boost_libs} @@ -99,149 +114,77 @@ FOREACH( ENDIF() ENDFOREACH() -LIST(APPEND _boost_b2_options "-s") -LIST(APPEND _boost_b2_options "NO_LZMA=1") +# --- Build from source if not found --- +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/boost.cmake) -IF(RV_VERBOSE_INVOCATION) - LIST(APPEND _boost_b2_options "-d+2") -ELSE() - LIST(APPEND _boost_b2_options "-d+0") -ENDIF() - -IF(RV_TARGET_DARWIN) - SET(_toolset - "clang" - ) - -ELSEIF(RV_TARGET_LINUX) - SET(_toolset - "gcc" - ) -ELSEIF(RV_TARGET_WINDOWS) - SET(_toolset - "msvc-14.3" + # Build path: we control the filenames, use OUTPUTS for precise tracking + FOREACH( + _boost_lib + ${_boost_libs} ) -ELSE() - MESSAGE(FATAL_ERROR "Unsupported (yet) target for Boost") -ENDIF() - -SET(_b2_command - ./b2 -) - -IF(RV_TARGET_WINDOWS) - SET(_bootstrap_command - ./bootstrap.bat - ) -ELSE() - SET(_bootstrap_command - ./bootstrap.sh - ) -ENDIF() - -IF(RV_TARGET_WINDOWS) - SET(_boost_python_bin - ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/python.exe - ) -ELSE() - SET(_boost_python_bin - ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/bin/python - ) -ENDIF() - -STRING(TOLOWER ${CMAKE_BUILD_TYPE} _boost_variant) - -LIST( - TRANSFORM _boost_libs - PREPEND "--with-" - OUTPUT_VARIABLE _boost_with_list -) - -SET(__boost_arch__ - x86 -) -IF(APPLE) - IF(RV_TARGET_APPLE_ARM64) - SET(__boost_arch__ - arm - ) + LIST(APPEND _boost_stage_output ${RV_STAGE_LIB_DIR}/${_boost_${_boost_lib}_lib_name}) + ENDFOREACH() + # Note: On Windows, Boost's b2 puts both .lib and .dll in lib/, so we copy _lib_dir to both RV_STAGE_LIB_DIR and RV_STAGE_BIN_DIR. + IF(RV_TARGET_WINDOWS) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} EXTRA_LIB_DIRS ${RV_STAGE_BIN_DIR} OUTPUTS ${_boost_stage_output}) + ELSE() + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${_boost_stage_output}) ENDIF() -ENDIF() - -EXTERNALPROJECT_ADD( - ${_target} - DEPENDS Python::Python - DOWNLOAD_NAME ${_target}_${_version}.tar.gz - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src - INSTALL_DIR ${_install_dir} - URL ${_download_url} - URL_MD5 ${_download_hash} - CONFIGURE_COMMAND ${_bootstrap_command} --with-toolset=${_toolset} --with-python=${_boost_python_bin} - BUILD_COMMAND - # Ref.: https://www.boost.org/doc/libs/1_70_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags Ref.: - # https://www.boost.org/doc/libs/1_76_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags - ./b2 -a -q toolset=${_toolset} cxxstd=${RV_CPP_STANDARD} variant=${_boost_variant} link=shared threading=multi architecture=${__boost_arch__} - address-model=64 ${_boost_with_list} ${_boost_b2_options} -j${_cpu_count} install --prefix=${_install_dir} - INSTALL_COMMAND echo "Boost was both built and installed in the build stage" - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_boost_byproducts} - USES_TERMINAL_BUILD TRUE -) - -IF(RV_TARGET_WINDOWS) - SET(_include_dir - ${_install_dir}/include/boost-${_major_minor_version} - ) ELSE() - SET(_include_dir - ${_install_dir}/include - ) -ENDIF() - -FILE(MAKE_DIRECTORY ${_include_dir}) - -FOREACH( - _boost_lib - ${_boost_libs} -) - ADD_LIBRARY(Boost::${_boost_lib} SHARED IMPORTED GLOBAL) - ADD_DEPENDENCIES(Boost::${_boost_lib} ${_target}) - SET_PROPERTY( - TARGET Boost::${_boost_lib} - PROPERTY IMPORTED_LOCATION ${_boost_${_boost_lib}_lib} - ) - SET_PROPERTY( - TARGET Boost::${_boost_lib} - PROPERTY IMPORTED_SONAME ${_boost_${_boost_lib}_lib_name} - ) - + # CONFIG found — Boost::xxx targets already exist with proper LOCATION. Create any missing targets as a safety net. IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET Boost::${_boost_lib} - PROPERTY IMPORTED_IMPLIB ${_boost_${_boost_lib}_implib} + SET(_include_dir + ${_install_dir}/include/boost-${_major_minor_version} ) ENDIF() - TARGET_INCLUDE_DIRECTORIES( - Boost::${_boost_lib} - INTERFACE ${_include_dir} - ) - LIST(APPEND RV_DEPS_LIST Boost::${_boost_lib}) - LIST(APPEND _boost_stage_output ${RV_STAGE_LIB_DIR}/${_boost_${_boost_lib}_lib_name}) -ENDFOREACH() - -ADD_LIBRARY(Boost::headers INTERFACE IMPORTED GLOBAL) -SET_TARGET_PROPERTIES( - Boost::headers - PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}" -) + # Boost::headers is not in DEPS_LIST_TARGETS so the general symlink fixup in RV_FIND_DEPENDENCY doesn't reach it. Fix it here: Boost's config has a symlink + # resolution bug where get_filename_component(REALPATH) on "../" normalizes before resolving, landing on the shared prefix include dir (e.g. + # /opt/homebrew/include) instead of the package-specific one. + IF(TARGET Boost::headers) + GET_TARGET_PROPERTY(_boost_headers_inc Boost::headers INTERFACE_INCLUDE_DIRECTORIES) + IF(_boost_headers_inc + AND NOT "${_boost_headers_inc}" STREQUAL "${_include_dir}" + ) + SET_TARGET_PROPERTIES( + Boost::headers + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}" + ) + MESSAGE(STATUS " Fixed Boost::headers include: ${_boost_headers_inc} -> ${_include_dir}") + ENDIF() + ELSE() + ADD_LIBRARY(Boost::headers INTERFACE IMPORTED GLOBAL) + SET_TARGET_PROPERTIES( + Boost::headers + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}" + ) + ENDIF() -# Note: On Windows, Boost's b2 puts both .lib and .dll in lib/, so we copy _lib_dir to both RV_STAGE_LIB_DIR and RV_STAGE_BIN_DIR. -IF(RV_TARGET_WINDOWS) - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} EXTRA_LIB_DIRS ${RV_STAGE_BIN_DIR} OUTPUTS ${_boost_stage_output}) -ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${_boost_stage_output}) + FOREACH( + _boost_lib + ${_boost_libs} + ) + IF(NOT TARGET Boost::${_boost_lib}) + RV_ADD_IMPORTED_LIBRARY( + NAME + Boost::${_boost_lib} + TYPE + SHARED + LOCATION + ${_boost_${_boost_lib}_lib} + SONAME + ${_boost_${_boost_lib}_lib_name} + IMPLIB + ${_boost_${_boost_lib}_implib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() + ENDFOREACH() + + # Found path: actual filenames may differ (e.g. -mt suffix), use TARGET_LIBS to resolve at build time + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_boost_deps_list_targets} USE_FLAG_FILE) ENDIF() diff --git a/cmake/dependencies/build/boost.cmake b/cmake/dependencies/build/boost.cmake new file mode 100644 index 000000000..6235568a1 --- /dev/null +++ b/cmake/dependencies/build/boost.cmake @@ -0,0 +1,144 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Build Boost from source via ExternalProject_Add. Included by cmake/dependencies/boost.cmake when no installed package is found. +# +# Expects these variables from the caller (set by RV_CREATE_STANDARD_DEPS_VARIABLES): _target, _version, _install_dir, _lib_dir, _include_dir, _source_dir And +# these dep-specific variables from the caller: _major_minor_version, _boost_libs, _boost_byproducts, _boost_${lib}_lib, _boost_${lib}_lib_name, +# _boost_${lib}_implib (Windows) + +STRING(REPLACE "." "_" _version_with_underscore ${_version}) +SET(_download_url + "https://archives.boost.io/release/${_version}/source/boost_${_version_with_underscore}.tar.gz" +) +SET(_download_hash + ${RV_DEPS_BOOST_DOWNLOAD_HASH} +) + +LIST(APPEND _boost_b2_options "-s") +LIST(APPEND _boost_b2_options "NO_LZMA=1") + +IF(RV_VERBOSE_INVOCATION) + LIST(APPEND _boost_b2_options "-d+2") +ELSE() + LIST(APPEND _boost_b2_options "-d+0") +ENDIF() + +IF(RV_TARGET_DARWIN) + SET(_toolset + "clang" + ) +ELSEIF(RV_TARGET_LINUX) + SET(_toolset + "gcc" + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_toolset + "msvc-14.3" + ) +ELSE() + MESSAGE(FATAL_ERROR "Unsupported (yet) target for Boost") +ENDIF() + +IF(RV_TARGET_WINDOWS) + SET(_bootstrap_command + ./bootstrap.bat + ) +ELSE() + SET(_bootstrap_command + ./bootstrap.sh + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + SET(_boost_python_bin + ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/python.exe + ) +ELSE() + SET(_boost_python_bin + ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/bin/python + ) +ENDIF() + +STRING(TOLOWER ${CMAKE_BUILD_TYPE} _boost_variant) + +LIST( + TRANSFORM _boost_libs + PREPEND "--with-" + OUTPUT_VARIABLE _boost_with_list +) + +SET(__boost_arch__ + x86 +) +IF(APPLE) + IF(RV_TARGET_APPLE_ARM64) + SET(__boost_arch__ + arm + ) + ENDIF() +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + DEPENDS Python::Python + DOWNLOAD_NAME ${_target}_${_version}.tar.gz + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_bootstrap_command} --with-toolset=${_toolset} --with-python=${_boost_python_bin} + BUILD_COMMAND + # Ref.: https://www.boost.org/doc/libs/1_70_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags Ref.: + # https://www.boost.org/doc/libs/1_76_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags + ./b2 -a -q toolset=${_toolset} cxxstd=${RV_CPP_STANDARD} variant=${_boost_variant} link=shared threading=multi architecture=${__boost_arch__} + address-model=64 ${_boost_with_list} ${_boost_b2_options} -j${_cpu_count} install --prefix=${_install_dir} + INSTALL_COMMAND echo "Boost was both built and installed in the build stage" + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_boost_byproducts} + USES_TERMINAL_BUILD TRUE +) + +IF(RV_TARGET_WINDOWS) + SET(_include_dir + ${_install_dir}/include/boost-${_major_minor_version} + ) +ELSE() + SET(_include_dir + ${_install_dir}/include + ) +ENDIF() + +FOREACH( + _boost_lib + ${_boost_libs} +) + RV_ADD_IMPORTED_LIBRARY( + NAME + Boost::${_boost_lib} + TYPE + SHARED + LOCATION + ${_boost_${_boost_lib}_lib} + SONAME + ${_boost_${_boost_lib}_lib_name} + IMPLIB + ${_boost_${_boost_lib}_implib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST + ) +ENDFOREACH() + +ADD_LIBRARY(Boost::headers INTERFACE IMPORTED GLOBAL) +SET_TARGET_PROPERTIES( + Boost::headers + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}" +) diff --git a/cmake/macros/rv_create_std_deps_vars.cmake b/cmake/macros/rv_create_std_deps_vars.cmake index bf0675567..d24a8fe8e 100644 --- a/cmake/macros/rv_create_std_deps_vars.cmake +++ b/cmake/macros/rv_create_std_deps_vars.cmake @@ -8,6 +8,10 @@ # Create the standard variables common to most RV_DEPS_xyz modules MACRO(RV_CREATE_STANDARD_DEPS_VARIABLES target_name version make_command configure_command) + # Parse optional keyword arguments after the 4 positional params. FORCE_LIB: always use lib/ instead of lib64/ even when RHEL_VERBOSE is set. Use for + # dependencies whose CMake install does not honor GNUInstallDirs lib64 (e.g. zlib). + CMAKE_PARSE_ARGUMENTS(_RCSDV "FORCE_LIB" "" "" ${ARGN}) + SET(_target ${target_name} ) @@ -33,7 +37,11 @@ MACRO(RV_CREATE_STANDARD_DEPS_VARIABLES target_name version make_command configu SET(_source_dir ${_base_dir}/src ) - IF(RHEL_VERBOSE) + IF(_RCSDV_FORCE_LIB) + SET(_lib_dir + ${_install_dir}/lib + ) + ELSEIF(RHEL_VERBOSE) SET(_lib_dir ${_install_dir}/lib64 ) @@ -248,9 +256,8 @@ ENDMACRO() # Set directory variables from a found package # # When a dependency is found via find_package() instead of built from source, the _lib_dir, _bin_dir, _include_dir, and _install_dir variables set by -# RV_CREATE_STANDARD_DEPS_VARIABLES point at the (non-existent) ExternalProject install dir. -# This macro overrides them to point at the found package's actuallocation, -# so downstream code (including RV_STAGE_DEPENDENCY_LIBS) works identically for both paths. +# RV_CREATE_STANDARD_DEPS_VARIABLES point at the (non-existent) ExternalProject install dir. This macro overrides them to point at the found package's +# actuallocation, so downstream code (including RV_STAGE_DEPENDENCY_LIBS) works identically for both paths. # # Also creates a dummy custom target for the dependency so that DEPENDS ${_target} works in the find path where there's no ExternalProject target. # @@ -259,11 +266,17 @@ ENDMACRO() MACRO(RV_SET_FOUND_PACKAGE_DIRS rv_deps_target find_package_name) # ${find_package_name}_DIR is set by find_package(CONFIG) to the directory containing the CMake config files, typically: /lib/cmake// (3 # levels up) /share/cmake// (3 levels up) - SET(_sfpd_config_dir - "${${find_package_name}_DIR}" - ) + # + # Resolve through symlinks FIRST, then walk up. This is critical for package managers like Homebrew that symlink cmake config dirs into a shared prefix (e.g., + # /opt/homebrew/lib/cmake/Boost-1.85.0 → Cellar/boost@1.85/.../lib/cmake/Boost-1.85.0). Without REALPATH, walking up lands on the shared prefix + # (/opt/homebrew) instead of the package-specific root, causing include path contamination. + GET_FILENAME_COMPONENT(_sfpd_config_dir "${${find_package_name}_DIR}" REALPATH) + + # Also compute the unresolved root so callers can detect and fix imported targets whose config files have the same symlink issue. + GET_FILENAME_COMPONENT(_sfpd_config_dir_unresolved "${${find_package_name}_DIR}" ABSOLUTE) + GET_FILENAME_COMPONENT(_sfpd_unresolved_root_3 "${_sfpd_config_dir_unresolved}/../../.." ABSOLUTE) - # Walk up 3 levels from the config dir to find the package root + # Walk up 3 levels from the resolved config dir to find the package root GET_FILENAME_COMPONENT(_sfpd_root_3 "${_sfpd_config_dir}/../../.." ABSOLUTE) GET_FILENAME_COMPONENT(_sfpd_root_2 "${_sfpd_config_dir}/../.." ABSOLUTE) @@ -286,6 +299,10 @@ MACRO(RV_SET_FOUND_PACKAGE_DIRS rv_deps_target find_package_name) ) ENDIF() + SET(_sfpd_unresolved_include_dir + "${_sfpd_unresolved_root_3}/include" + ) + SET(${rv_deps_target}_ROOT_DIR "${_install_dir}" CACHE INTERNAL "" FORCE @@ -318,9 +335,9 @@ ENDMACRO() # Set directory variables from a pkg-config found package # -# Parallel to RV_SET_FOUND_PACKAGE_DIRS but uses variables set by pkg_check_modules() instead of find_package(CONFIG). -# Sets _lib_dir, _include_dir, _install_dir, _bin_dir in the caller's scope and creates a dummy custom target. -# _install_dir, _bin_dir in the caller's scope and creates a dummy custom target. +# Parallel to RV_SET_FOUND_PACKAGE_DIRS but uses variables set by pkg_check_modules() instead of find_package(CONFIG). Sets _lib_dir, _include_dir, +# _install_dir, _bin_dir in the caller's scope and creates a dummy custom target. _install_dir, _bin_dir in the caller's scope and creates a dummy custom +# target. # # Must be a MACRO so the variable overrides propagate to the caller's scope. # diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake index 188300be5..5f609b360 100644 --- a/cmake/macros/rv_find_dependency.cmake +++ b/cmake/macros/rv_find_dependency.cmake @@ -24,11 +24,12 @@ # PACKAGE # REQUIRED: e.g., Imath # [VERSION ] # Optional version (EXACT or MINIMUM per RV_DEPS_VERSION_MATCH) # [PKG_CONFIG_NAME ] # Optional pkg-config module name for fallback +# [COMPONENTS ...] # Optional find_package COMPONENTS (e.g., Boost components) # [DEPS_LIST_TARGETS ...] # Targets to append to RV_DEPS_LIST # ) # cmake-format: on MACRO(RV_FIND_DEPENDENCY) - CMAKE_PARSE_ARGUMENTS(_RFD "" "TARGET;PACKAGE;VERSION;PKG_CONFIG_NAME" "DEPS_LIST_TARGETS" ${ARGN}) + CMAKE_PARSE_ARGUMENTS(_RFD "" "TARGET;PACKAGE;VERSION;PKG_CONFIG_NAME" "DEPS_LIST_TARGETS;COMPONENTS" ${ARGN}) SET(${_RFD_TARGET}_FOUND FALSE @@ -52,15 +53,25 @@ MACRO(RV_FIND_DEPENDENCY) ) ENDIF() + # Build optional COMPONENTS argument list + SET(_rfd_components_args + "" + ) + IF(_RFD_COMPONENTS) + SET(_rfd_components_args + COMPONENTS ${_RFD_COMPONENTS} + ) + ENDIF() + # Strategy 1: CMake CONFIG mode IF(_RFD_VERSION) IF(_rfd_match_mode STREQUAL "EXACT") - FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} EXACT CONFIG) + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} EXACT CONFIG ${_rfd_components_args}) ELSE() - FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} CONFIG) + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} CONFIG ${_rfd_components_args}) ENDIF() ELSE() - FIND_PACKAGE(${_RFD_PACKAGE} CONFIG) + FIND_PACKAGE(${_RFD_PACKAGE} CONFIG ${_rfd_components_args}) ENDIF() IF(${_RFD_PACKAGE}_FOUND) @@ -108,6 +119,29 @@ MACRO(RV_FIND_DEPENDENCY) IF(_RFD_FOUND_VIA STREQUAL "config") MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via CMake config. Using installed package.") RV_SET_FOUND_PACKAGE_DIRS(${_RFD_TARGET} ${_RFD_PACKAGE}) + + # Package config files may suffer the same symlink resolution bug as our walk-up logic (CMake normalizes "../" before resolving symlinks). If the + # resolved and unresolved include dirs differ, fix imported targets that inherited the stale prefix-level path. + IF(NOT "${_sfpd_unresolved_include_dir}" STREQUAL "${_include_dir}") + FOREACH( + _rfd_dep + ${_RFD_DEPS_LIST_TARGETS} + ) + IF(TARGET ${_rfd_dep}) + GET_TARGET_PROPERTY(_rfd_inc_dirs ${_rfd_dep} INTERFACE_INCLUDE_DIRECTORIES) + IF(_rfd_inc_dirs) + STRING(REPLACE "${_sfpd_unresolved_include_dir}" "${_include_dir}" _rfd_fixed_inc_dirs "${_rfd_inc_dirs}") + IF(NOT "${_rfd_fixed_inc_dirs}" STREQUAL "${_rfd_inc_dirs}") + SET_TARGET_PROPERTIES( + ${_rfd_dep} + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_rfd_fixed_inc_dirs}" + ) + MESSAGE(STATUS " Fixed ${_rfd_dep} include: ${_rfd_inc_dirs} -> ${_rfd_fixed_inc_dirs}") + ENDIF() + ENDIF() + ENDIF() + ENDFOREACH() + ENDIF() ELSEIF(_RFD_FOUND_VIA STREQUAL "pkgconfig") MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via pkg-config. Using installed package.") RV_SET_FOUND_PKGCONFIG_DIRS(${_RFD_TARGET} ${_RFD_TARGET}_PC) diff --git a/cmake/macros/rv_stage_dependency_libs.cmake b/cmake/macros/rv_stage_dependency_libs.cmake index 7f6979b3b..6d31c938c 100644 --- a/cmake/macros/rv_stage_dependency_libs.cmake +++ b/cmake/macros/rv_stage_dependency_libs.cmake @@ -20,6 +20,9 @@ # [STAGE_LIB_DIR ] # Override RV_STAGE_LIB_DIR (e.g., for OpenSSL/Linux) # [EXTRA_LIB_DIRS ...] # Additional lib dirs to copy to # [FILES [file2...]] # Individual files to copy_if_different to STAGE_LIB_DIR (alternative to LIB_DIR) +# [TARGET_LIBS [t2...]] # Imported targets to stage via $ generatos (requires USE_FLAG_FILE). +# # Resolves actual library paths at build time — handles any naming convention. +# # On Windows: DLL→STAGE_BIN_DIR, import lib→STAGE_LIB_DIR. # [DEPENDS [dep2...]] # Dependencies (default: ${TARGET}) # [PRE_COMMANDS COMMAND [COMMAND ...]] # Commands to run before copy; each must be prefixed with COMMAND keyword # [LIBNAME ] # Platform-aware shorthand: on Windows uses BIN_DIR+STAGE_BIN_DIR, otherwise STAGE_LIB_DIR @@ -29,7 +32,7 @@ # cmake-format: on FUNCTION(RV_STAGE_DEPENDENCY_LIBS) CMAKE_PARSE_ARGUMENTS( - _ARG "USE_FLAG_FILE" "TARGET;LIB_DIR;BIN_DIR;INCLUDE_DIR;STAGE_LIB_DIR;LIBNAME" "OUTPUTS;EXTRA_LIB_DIRS;DEPENDS;PRE_COMMANDS;FILES" ${ARGN} + _ARG "USE_FLAG_FILE" "TARGET;LIB_DIR;BIN_DIR;INCLUDE_DIR;STAGE_LIB_DIR;LIBNAME" "OUTPUTS;EXTRA_LIB_DIRS;DEPENDS;PRE_COMMANDS;FILES;TARGET_LIBS" ${ARGN} ) IF(_ARG_UNPARSED_ARGUMENTS) @@ -68,9 +71,17 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ENDIF() ENDIF() + # Validate TARGET_LIBS requires USE_FLAG_FILE (generators can't be used in OUTPUTS) + IF(_ARG_TARGET_LIBS + AND NOT _ARG_USE_FLAG_FILE + ) + MESSAGE(FATAL_ERROR "RV_STAGE_DEPENDENCY_LIBS: TARGET_LIBS requires USE_FLAG_FILE (generator expressions cannot be used in OUTPUTS)") + ENDIF() + # Default LIB_DIR to caller's _lib_dir if not explicitly passed IF(NOT _ARG_LIB_DIR AND NOT _ARG_FILES + AND NOT _ARG_TARGET_LIBS ) IF(DEFINED _lib_dir) SET(_ARG_LIB_DIR @@ -81,8 +92,9 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) IF(NOT _ARG_LIB_DIR AND NOT _ARG_FILES + AND NOT _ARG_TARGET_LIBS ) - MESSAGE(FATAL_ERROR "RV_STAGE_DEPENDENCY_LIBS: Either LIB_DIR or FILES is required") + MESSAGE(FATAL_ERROR "RV_STAGE_DEPENDENCY_LIBS: Either LIB_DIR, FILES, or TARGET_LIBS is required") ENDIF() # Default depends to TARGET @@ -126,6 +138,131 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ENDFOREACH() ENDIF() + # Copy imported target libraries via generator expressions (resolved at build time). Ensure destination dirs exist first — copy_if_different doesn't create + # them, and for found packages (no ExternalProject) the stage dirs may not yet exist at this point in the build. + IF(_ARG_TARGET_LIBS) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + make_directory + ${_ARG_STAGE_LIB_DIR} + ) + IF(RV_TARGET_WINDOWS) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + make_directory + ${RV_STAGE_BIN_DIR} + ) + ENDIF() + + FOREACH( + _tgt + ${_ARG_TARGET_LIBS} + ) + IF(RV_TARGET_WINDOWS) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + echo + " Staging $ -> ${RV_STAGE_BIN_DIR}/" + ) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + copy_if_different + $ + ${RV_STAGE_BIN_DIR}/ + ) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + echo + " Staging $ -> ${_ARG_STAGE_LIB_DIR}/" + ) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + copy_if_different + $ + ${_ARG_STAGE_LIB_DIR}/ + ) + ELSE() + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + echo + " Staging $ -> ${_ARG_STAGE_LIB_DIR}/" + ) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_COMMAND} + -E + copy_if_different + $ + ${_ARG_STAGE_LIB_DIR}/ + ) + ENDIF() + + # On macOS, fix the staged copy's install name to @rpath so it can be resolved via rpath at runtime. For found packages (e.g. Homebrew) the install name + # is an absolute path; for built-from-source it's typically already @rpath but -id is idempotent. + IF(RV_TARGET_DARWIN) + GET_PROPERTY( + _rsdl_install_name + TARGET ${_tgt} + PROPERTY RV_DARWIN_INSTALL_NAME + ) + IF(_rsdl_install_name) + GET_FILENAME_COMPONENT(_rsdl_install_fname "${_rsdl_install_name}" NAME) + LIST( + APPEND + _commands + COMMAND + ${CMAKE_INSTALL_NAME_TOOL} + -id + "@rpath/${_rsdl_install_fname}" + "${_ARG_STAGE_LIB_DIR}/$" + ) + # Re-sign with ad-hoc after modifying the install name. Homebrew libraries carry a Homebrew signature that install_name_tool invalidates; on arm64 + # macOS loading a library with an invalid signature causes SIGKILL. + LIST( + APPEND + _commands + COMMAND + codesign + --force + --sign + - + "${_ARG_STAGE_LIB_DIR}/$" + ) + ENDIF() + ENDIF() + ENDFOREACH() + ENDIF() + # Copy lib dir if specified IF(_ARG_LIB_DIR) LIST( @@ -213,11 +350,13 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ENDIF() # Create the custom command (always OUTPUT-based for proper incremental builds). Note: ${_commands} contains COMMAND keywords which CMake's keyword parser - # uses as section boundaries, so they are correctly parsed as commands, not as additional OUTPUT entries. + # uses as section boundaries, so they are correctly parsed as commands, not as additional OUTPUT entries. VERBATIM ensures arguments with shell metacharacters + # (e.g., ">" in echo messages) are properly escaped. ADD_CUSTOM_COMMAND( COMMENT "Staging ${_ARG_TARGET} libs into ${_ARG_STAGE_LIB_DIR}" OUTPUT ${_tracking_outputs} ${_commands} DEPENDS ${_ARG_DEPENDS} + VERBATIM ) # Create the stage target From 013c0c7ca0e3c1935640334d0944d738f98f4364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 19:41:10 -0400 Subject: [PATCH 05/15] build: add OpenEXR find_package dispatcher and fix symlink include fixup for auxiliary targets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor OpenEXR to the find first dispatcher pattern: prefer an installed OpenEXR via find_package(CONFIG) and fall back to building from source. Extract ExternalProject_Add build logic into cmake/dependencies/build/openexr.cmake. Fix the symlink include path fixup to cover ALL imported targets from a package, not just DEPS_LIST_TARGETS. Config files often create auxiliary interface targets (e.g. Imath::ImathConfig, OpenEXR::OpenEXRConfig) that carry include directories and are linked transitively. The fix now queries IMPORTED_TARGETS from the directory scope and fixes any target matching the package prefix. Also warns if IMPORTED_TARGETS is empty. Fix double prefix bug in per dependency version match and force build variable names: RV_DEPS_${_RFD_TARGET}_VERSION_MATCH expanded to RV_DEPS_RV_DEPS_OPENEXR_VERSION_MATCH since _RFD_TARGET already contains the RV_DEPS_ prefix. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/build/openexr.cmake | 149 +++++++++++++++++ cmake/dependencies/openexr.cmake | 222 ++++++++----------------- cmake/macros/rv_find_dependency.cmake | 22 ++- 3 files changed, 236 insertions(+), 157 deletions(-) create mode 100644 cmake/dependencies/build/openexr.cmake diff --git a/cmake/dependencies/build/openexr.cmake b/cmake/dependencies/build/openexr.cmake new file mode 100644 index 000000000..2bb1ca82f --- /dev/null +++ b/cmake/dependencies/build/openexr.cmake @@ -0,0 +1,149 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Build OpenEXR from source via ExternalProject_Add. Included by cmake/dependencies/openexr.cmake when no installed package is found. +# +# Expects these variables from the caller (set by RV_CREATE_STANDARD_DEPS_VARIABLES): _target, _version, _install_dir, _lib_dir, _bin_dir, _include_dir, +# _source_dir And these dep-specific variables from the caller: _openexr_libname_suffix_, LIB_VERSION_SUFFIX, _openexr_byproducts _openexr_lib, _openexr_name, +# _openexrcore_lib, _openexrcore_name _ilmthread_lib, _ilmthread_name, _iex_lib, _iex_name _openexr_implib, _ilmthread_implib, _iex_implib (Windows) + +SET(_make_command + make +) +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) +ENDIF() +IF(RV_TARGET_WINDOWS) + # MSYS2/CMake defaults to Ninja + SET(_make_command + ninja + ) +ENDIF() + +SET(_openexr_patch_file_ + "${CMAKE_CURRENT_SOURCE_DIR}/patch/${RV_DEPS_OPENEXR_PATCH_NAME}.patch" +) + +IF(EXISTS "${_openexr_patch_file_}") + SET(_patch_command + patch -p1 < ${_openexr_patch_file_} + ) + MESSAGE(STATUS "Patch command set for ${_target}: ${_patch_command}") +ELSE() + # If it does not exist, set the command to an empty string. ExternalProject_Add will skip the patch step if the command is empty. + SET(_patch_command + "" + ) + MESSAGE(STATUS "ERROR Patch file not found, skipping patch for ${_target}: ${_patch_file}") +ENDIF() + +LIST(APPEND _configure_options "-DCMAKE_PREFIX_PATH=${RV_DEPS_IMATH_CMAKE_DIR}") +LIST(APPEND _configure_options "-DBUILD_TESTING=OFF") +IF(RV_TARGET_WINDOWS) + GET_TARGET_PROPERTY(_zlib_implibpath ZLIB::ZLIB IMPORTED_IMPLIB) + LIST(APPEND _configure_options "-DZLIB_INCLUDE_DIR=${RV_DEPS_ZLIB_INCLUDE_DIR}") + LIST(APPEND _configure_options "-DZLIB_LIBRARY=${_zlib_implibpath}") +ENDIF() + +# OpenEXR tools are not needed. +LIST(APPEND _configure_options "-DOPENEXR_BUILD_TOOLS=OFF") + +# Disable OpenEXR's automatic rpath setup to avoid conflicts with RV's rpath management OpenEXR 3.3+ automatically adds @loader_path/../lib which conflicts with +# our install scripts +LIST(APPEND _configure_options "-DCMAKE_INSTALL_RPATH=") + +EXTERNALPROJECT_ADD( + ${_target} + URL "https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v${_version}.zip" + URL_MD5 ${RV_DEPS_OPENEXR_DOWNLOAD_HASH} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + DEPENDS Imath::Imath ZLIB::ZLIB + INSTALL_DIR ${_install_dir} + PATCH_COMMAND ${_patch_command} + CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} + BUILD_COMMAND ${_cmake_build_command} + INSTALL_COMMAND ${_cmake_install_command} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_openexr_byproducts} + USES_TERMINAL_BUILD TRUE +) + +SET(_include_dir + ${_install_dir}/include/OpenEXR +) + +FILE(MAKE_DIRECTORY ${_include_dir}) + +ADD_LIBRARY(OpenEXR::IlmThread SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenEXR::IlmThread ${_target}) +SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_LOCATION ${_ilmthread_lib} +) +SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_SONAME ${_ilmthread_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_IMPLIB ${_ilmthread_implib} + ) +ENDIF() +LIST(APPEND RV_DEPS_LIST OpenEXR::IlmThread) + +ADD_LIBRARY(OpenEXR::Iex SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenEXR::Iex ${_target}) +SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_LOCATION ${_iex_lib} +) +SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_SONAME ${_iex_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_IMPLIB ${_iex_implib} + ) +ENDIF() +LIST(APPEND RV_DEPS_LIST OpenEXR::Iex) + +ADD_LIBRARY(OpenEXR::OpenEXR SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenEXR::OpenEXR ${_target}) +SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_LOCATION ${_openexr_lib} +) +SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_SONAME ${_openexr_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_IMPLIB ${_openexr_implib} + ) +ENDIF() + +TARGET_INCLUDE_DIRECTORIES( + OpenEXR::OpenEXR + INTERFACE ${_include_dir} +) +TARGET_LINK_LIBRARIES( + OpenEXR::OpenEXR + INTERFACE Imath::Imath OpenEXR::Iex OpenEXR::IlmThread ZLIB::ZLIB +) +LIST(APPEND RV_DEPS_LIST OpenEXR::OpenEXR) diff --git a/cmake/dependencies/openexr.cmake b/cmake/dependencies/openexr.cmake index d90318144..1dab72e3c 100644 --- a/cmake/dependencies/openexr.cmake +++ b/cmake/dependencies/openexr.cmake @@ -6,23 +6,23 @@ RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_OPENEXR" "${RV_DEPS_OPENEXR_VERSION}" "" "") -SET(_make_command - make +SET(_openexr_deps_list_targets + OpenEXR::OpenEXR OpenEXR::IlmThread OpenEXR::Iex ) -IF(${RV_OSX_EMULATION}) - SET(_darwin_x86_64 - "arch" "${RV_OSX_EMULATION_ARCH}" - ) - SET(_make_command - ${_darwin_x86_64} ${_make_command} - ) -ENDIF() -IF(RV_TARGET_WINDOWS) - # MSYS2/CMake defaults to Ninja - SET(_make_command - ninja - ) -ENDIF() + +# --- Try to find installed package --- +RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + OpenEXR + VERSION + ${RV_DEPS_OPENEXR_VERSION} + DEPS_LIST_TARGETS + ${_openexr_deps_list_targets} +) + +# --- Library naming (shared between find and build paths) --- SET(_openexr_libname_suffix_ "${RV_DEPS_OPENEXR_LIBNAME_SUFFIX}" ) @@ -114,140 +114,60 @@ IF(RV_TARGET_WINDOWS) LIST(APPEND _openexr_byproducts ${_openexr_implib} ${_ilmthread_implib} ${_iex_implib}) ENDIF() -SET(_openexr_patch_file_ - "${CMAKE_CURRENT_SOURCE_DIR}/patch/${RV_DEPS_OPENEXR_PATCH_NAME}.patch" -) - -IF(EXISTS "${_openexr_patch_file_}") - SET(_patch_command - patch -p1 < ${_openexr_patch_file_} - ) - MESSAGE(STATUS "Patch command set for ${_target}: ${_patch_command}") +# --- Build from source if not found --- +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/openexr.cmake) + + # Build path: we control the filenames, use OUTPUTS for precise tracking + IF(RV_TARGET_WINDOWS) + RV_STAGE_DEPENDENCY_LIBS( + TARGET + ${_target} + BIN_DIR + ${_bin_dir} + OUTPUTS + ${RV_STAGE_BIN_DIR}/${_openexr_name} + ${RV_STAGE_BIN_DIR}/${_openexrcore_name} + ${RV_STAGE_BIN_DIR}/${_ilmthread_name} + ${RV_STAGE_BIN_DIR}/${_iex_name} + ) + ELSE() + RV_STAGE_DEPENDENCY_LIBS( + TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_openexrcore_name} ${RV_STAGE_LIB_DIR}/${_ilmthread_name} ${RV_STAGE_LIB_DIR}/${_iex_name} + ) + ENDIF() ELSE() - # If it does not exist, set the command to an empty string. ExternalProject_Add will skip the patch step if the command is empty. - SET(_patch_command - "" - ) - MESSAGE(STATUS "ERROR Patch file not found, skipping patch for ${_target}: ${_patch_file}") + # CONFIG found — OpenEXR::xxx targets already exist. Create any missing targets as a safety net. + FOREACH( + _exr_target + ${_openexr_deps_list_targets} + ) + IF(NOT TARGET ${_exr_target}) + RV_ADD_IMPORTED_LIBRARY( + NAME + ${_exr_target} + TYPE + SHARED + LOCATION + ${_lib_dir}/${_openexr_name} + SONAME + ${_openexr_name} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() + ENDFOREACH() + + # Found path: actual filenames may differ, use TARGET_LIBS to resolve at build time. Include OpenEXRCore which exists when found via CONFIG but has no + # imported target in build path. + SET(_openexr_stage_targets + ${_openexr_deps_list_targets} + ) + IF(TARGET OpenEXR::OpenEXRCore) + LIST(APPEND _openexr_stage_targets OpenEXR::OpenEXRCore) + ENDIF() + + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_openexr_stage_targets} USE_FLAG_FILE) ENDIF() - -LIST(APPEND _configure_options "-DCMAKE_PREFIX_PATH=${RV_DEPS_IMATH_CMAKE_DIR}") -LIST(APPEND _configure_options "-DBUILD_TESTING=OFF") -IF(RV_TARGET_WINDOWS) - GET_TARGET_PROPERTY(_zlib_implibpath ZLIB::ZLIB IMPORTED_IMPLIB) - LIST(APPEND _configure_options "-DZLIB_INCLUDE_DIR=${RV_DEPS_ZLIB_INCLUDE_DIR}") - LIST(APPEND _configure_options "-DZLIB_LIBRARY=${_zlib_implibpath}") -ENDIF() - -# OpenEXR tools are not needed. -LIST(APPEND _configure_options "-DOPENEXR_BUILD_TOOLS=OFF") - -# Disable OpenEXR's automatic rpath setup to avoid conflicts with RV's rpath management OpenEXR 3.3+ automatically adds @loader_path/../lib which conflicts with -# our install scripts -LIST(APPEND _configure_options "-DCMAKE_INSTALL_RPATH=") - -EXTERNALPROJECT_ADD( - ${_target} - URL "https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v${_version}.zip" - URL_MD5 ${RV_DEPS_OPENEXR_DOWNLOAD_HASH} - DOWNLOAD_NAME ${_target}_${_version}.zip - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src - DEPENDS Imath::Imath ZLIB::ZLIB - INSTALL_DIR ${_install_dir} - PATCH_COMMAND ${_patch_command} - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} - BUILD_COMMAND ${_cmake_build_command} - INSTALL_COMMAND ${_cmake_install_command} - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_openexr_byproducts} - USES_TERMINAL_BUILD TRUE -) - -SET(_include_dir - ${_install_dir}/include/OpenEXR -) - -FILE(MAKE_DIRECTORY ${_include_dir}) - -IF(RV_TARGET_WINDOWS) - RV_STAGE_DEPENDENCY_LIBS( - TARGET - ${_target} - BIN_DIR - ${_bin_dir} - OUTPUTS - ${RV_STAGE_BIN_DIR}/${_openexr_name} - ${RV_STAGE_BIN_DIR}/${_openexrcore_name} - ${RV_STAGE_BIN_DIR}/${_ilmthread_name} - ${RV_STAGE_BIN_DIR}/${_iex_name} - ) -ELSE() - RV_STAGE_DEPENDENCY_LIBS( - TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_openexrcore_name} ${RV_STAGE_LIB_DIR}/${_ilmthread_name} ${RV_STAGE_LIB_DIR}/${_iex_name} - ) -ENDIF() - -ADD_LIBRARY(OpenEXR::IlmThread SHARED IMPORTED GLOBAL) -SET_PROPERTY( - TARGET OpenEXR::IlmThread - PROPERTY IMPORTED_LOCATION ${_ilmthread_lib} -) -SET_PROPERTY( - TARGET OpenEXR::IlmThread - PROPERTY IMPORTED_SONAME ${_ilmthread_name} -) -IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET OpenEXR::IlmThread - PROPERTY IMPORTED_IMPLIB ${_ilmthread_implib} - ) -ENDIF() -LIST(APPEND RV_DEPS_LIST OpenEXR::IlmThread) - -ADD_LIBRARY(OpenEXR::Iex SHARED IMPORTED GLOBAL) -SET_PROPERTY( - TARGET OpenEXR::Iex - PROPERTY IMPORTED_LOCATION ${_iex_lib} -) -SET_PROPERTY( - TARGET OpenEXR::Iex - PROPERTY IMPORTED_SONAME ${_iex_name} -) -IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET OpenEXR::Iex - PROPERTY IMPORTED_IMPLIB ${_iex_implib} - ) -ENDIF() -LIST(APPEND RV_DEPS_LIST OpenEXR::Iex) - -ADD_LIBRARY(OpenEXR::OpenEXR SHARED IMPORTED GLOBAL) -SET_PROPERTY( - TARGET OpenEXR::OpenEXR - PROPERTY IMPORTED_LOCATION ${_openexr_lib} -) -SET_PROPERTY( - TARGET OpenEXR::OpenEXR - PROPERTY IMPORTED_SONAME ${_openexr_name} -) -IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET OpenEXR::OpenEXR - PROPERTY IMPORTED_IMPLIB ${_openexr_implib} - ) -ENDIF() - -TARGET_INCLUDE_DIRECTORIES( - OpenEXR::OpenEXR - INTERFACE ${_include_dir} -) -TARGET_LINK_LIBRARIES( - OpenEXR::OpenEXR - INTERFACE Imath::Imath OpenEXR::Iex OpenEXR::IlmThread ZLIB::ZLIB -) -LIST(APPEND RV_DEPS_LIST OpenEXR::OpenEXR) - -ADD_DEPENDENCIES(OpenEXR::OpenEXR ${_target}) diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake index 5f609b360..63fbad839 100644 --- a/cmake/macros/rv_find_dependency.cmake +++ b/cmake/macros/rv_find_dependency.cmake @@ -39,13 +39,13 @@ MACRO(RV_FIND_DEPENDENCY) ) IF(RV_DEPS_PREFER_INSTALLED - AND NOT RV_DEPS_${_RFD_TARGET}_FORCE_BUILD + AND NOT ${_RFD_TARGET}_FORCE_BUILD ) # Determine version match mode: per-dep override > global default - IF(DEFINED RV_DEPS_${_RFD_TARGET}_VERSION_MATCH) + IF(DEFINED ${_RFD_TARGET}_VERSION_MATCH) SET(_rfd_match_mode - "${RV_DEPS_${_RFD_TARGET}_VERSION_MATCH}" + "${${_RFD_TARGET}_VERSION_MATCH}" ) ELSE() SET(_rfd_match_mode @@ -121,13 +121,23 @@ MACRO(RV_FIND_DEPENDENCY) RV_SET_FOUND_PACKAGE_DIRS(${_RFD_TARGET} ${_RFD_PACKAGE}) # Package config files may suffer the same symlink resolution bug as our walk-up logic (CMake normalizes "../" before resolving symlinks). If the - # resolved and unresolved include dirs differ, fix imported targets that inherited the stale prefix-level path. + # resolved and unresolved include dirs differ, fix ALL imported targets from this package (not just DEPS_LIST_TARGETS). Config files often create + # auxiliary interface targets (e.g. Imath::ImathConfig, OpenEXR::OpenEXRConfig) that carry include dirs and are linked transitively by the library + # targets. IF(NOT "${_sfpd_unresolved_include_dir}" STREQUAL "${_include_dir}") + GET_PROPERTY( + _rfd_all_imported_targets + DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" + PROPERTY IMPORTED_TARGETS + ) + IF(NOT _rfd_all_imported_targets) + MESSAGE(WARNING "RV_FIND_DEPENDENCY: IMPORTED_TARGETS is empty for ${_RFD_PACKAGE} — symlink include fixup skipped") + ENDIF() FOREACH( _rfd_dep - ${_RFD_DEPS_LIST_TARGETS} + ${_rfd_all_imported_targets} ) - IF(TARGET ${_rfd_dep}) + IF("${_rfd_dep}" MATCHES "^${_RFD_PACKAGE}::") GET_TARGET_PROPERTY(_rfd_inc_dirs ${_rfd_dep} INTERFACE_INCLUDE_DIRECTORIES) IF(_rfd_inc_dirs) STRING(REPLACE "${_sfpd_unresolved_include_dir}" "${_include_dir}" _rfd_fixed_inc_dirs "${_rfd_inc_dirs}") From 77614f30014583839fa5636cdd42c8b854a650c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 19:58:22 -0400 Subject: [PATCH 06/15] build: add GLEW find_package dispatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor GLEW to the find first dispatcher pattern: prefer an installed GLEW via find_package(CONFIG) and fall back to building from source with the existing Makefile based ExternalProject_Add. Extract Makefile build logic into cmake/dependencies/build/glew.cmake. GLEW's CMake config does not ship a version file, so VERSION is omitted from the RV_FIND_DEPENDENCY call to avoid find_package failure. The config file automatically creates GLEW::GLEW as a copy of GLEW::glew. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/build/glew.cmake | 48 +++++++++++++++++++++ cmake/dependencies/glew.cmake | 65 +++++++++++++---------------- 2 files changed, 76 insertions(+), 37 deletions(-) create mode 100644 cmake/dependencies/build/glew.cmake diff --git a/cmake/dependencies/build/glew.cmake b/cmake/dependencies/build/glew.cmake new file mode 100644 index 000000000..8f960c056 --- /dev/null +++ b/cmake/dependencies/build/glew.cmake @@ -0,0 +1,48 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# Build GLEW from source via ExternalProject_Add (Makefile-based build). Included by cmake/dependencies/glew.cmake when no installed package is found. +# +# Expects these variables from the caller (set by RV_CREATE_STANDARD_DEPS_VARIABLES): _target, _version, _install_dir, _lib_dir, _include_dir, _make_command, +# _cpu_count And these dep-specific variables from the caller: _glew_lib, _glew_lib_name + +SET(_download_url + "https://github.com/nigels-com/glew/archive/refs/tags/glew-${_version}.tar.gz" +) + +SET(_download_hash + ${RV_DEPS_GLEW_DOWNLOAD_HASH} +) + +EXTERNALPROJECT_ADD( + ${_target} + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME glew-glew-${_version}.tar.gz + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + CONFIGURE_COMMAND cd auto && ${_make_command} && cd .. && ${_make_command} + BUILD_COMMAND ${_make_command} -j${_cpu_count} GLEW_DEST=${_install_dir} + INSTALL_COMMAND ${_make_command} install LIBDIR=${_lib_dir} GLEW_DEST=${_install_dir} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_glew_lib} + USES_TERMINAL_BUILD TRUE +) + +RV_ADD_IMPORTED_LIBRARY( + NAME + GLEW::GLEW + TYPE + STATIC + LOCATION + ${_glew_lib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST +) diff --git a/cmake/dependencies/glew.cmake b/cmake/dependencies/glew.cmake index 6401858fd..501eccc2f 100644 --- a/cmake/dependencies/glew.cmake +++ b/cmake/dependencies/glew.cmake @@ -6,14 +6,11 @@ RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_GLEW" "${RV_DEPS_GLEW_VERSION}" "make" "") -SET(_download_url - "https://github.com/nigels-com/glew/archive/refs/tags/glew-${_version}.tar.gz" -) - -SET(_download_hash - ${RV_DEPS_GLEW_DOWNLOAD_HASH} -) +# --- Try to find installed package --- +# GLEW's CMake config does not ship a version file, so omit VERSION to avoid find_package failure. +RV_FIND_DEPENDENCY(TARGET ${_target} PACKAGE GLEW DEPS_LIST_TARGETS GLEW::GLEW) +# --- Library naming (shared between find and build paths) --- IF(RV_TARGET_DARWIN) SET(_glew_lib_name ${CMAKE_SHARED_LIBRARY_PREFIX}GLEW.${RV_DEPS_GLEW_VERSION_LIB}${CMAKE_SHARED_LIBRARY_SUFFIX} @@ -27,35 +24,29 @@ SET(_glew_lib ${_lib_dir}/${_glew_lib_name} ) -EXTERNALPROJECT_ADD( - ${_target} - SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src - INSTALL_DIR ${_install_dir} - URL ${_download_url} - URL_MD5 ${_download_hash} - DOWNLOAD_NAME glew-glew-${_version}.tar.gz - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - CONFIGURE_COMMAND cd auto && ${_make_command} && cd .. && ${_make_command} - BUILD_COMMAND ${_make_command} -j${_cpu_count} GLEW_DEST=${_install_dir} - INSTALL_COMMAND ${_make_command} install LIBDIR=${_lib_dir} GLEW_DEST=${_install_dir} - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_glew_lib} - USES_TERMINAL_BUILD TRUE -) +# --- Build from source if not found --- +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/glew.cmake) -RV_ADD_IMPORTED_LIBRARY( - NAME - GLEW::GLEW - TYPE - STATIC - LOCATION - ${_glew_lib} - INCLUDE_DIRS - ${_include_dir} - DEPENDS - ${_target} - ADD_TO_DEPS_LIST -) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_glew_lib_name}) +ELSE() + # CONFIG found — GLEW::GLEW target already exists (created by glew-config.cmake from GLEW::glew). + IF(NOT TARGET GLEW::GLEW) + RV_ADD_IMPORTED_LIBRARY( + NAME + GLEW::GLEW + TYPE + SHARED + LOCATION + ${_glew_lib} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST + ) + ENDIF() -RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_glew_lib_name}) + # Found path: use TARGET_LIBS to resolve actual library path at build time + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS GLEW::GLEW USE_FLAG_FILE) +ENDIF() From ab4fa6d3116332f05228ef920310a94f7a07efdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 23:15:23 -0400 Subject: [PATCH 07/15] build: add zlib find_package dispatcher and MODULE mode resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor zlib to the find first dispatcher pattern: prefer an installed zlib via find_package(MODULE) or pkg-config and fall back to building from source. Extract ExternalProject_Add build logic into cmake/dependencies/build/zlib.cmake. RV_FIND_DEPENDENCY gains ALLOW_MODULE to try CMake built-in Find modules (e.g. FindZLIB.cmake) as a strategy between CONFIG and pkg-config. Add RV_SET_FOUND_MODULE_DIRS to derive directory variables from MODULE mode results. Enable PKG_CONFIG_USE_CMAKE_PREFIX_PATH temporarily during pkg-config fallback so CMAKE_PREFIX_PATH hints expose .pc files to pkg-config. Track contaminating prefixes detected during symlink resolution in RV_DEPS_IGNORE_PREFIXES (CACHE INTERNAL) so ExternalProject sub-builds can react to Homebrew include path contamination. Fix variable shadowing bug in RV_SET_FOUND_PACKAGE_DIRS, RV_SET_FOUND_PKGCONFIG_DIRS, and RV_SET_FOUND_MODULE_DIRS: the regular variable set by RV_CREATE_STANDARD_DEPS_VARIABLES shadowed the CACHE INTERNAL variable, causing downstream code to read stale ExternalProject install paths instead of the resolved found-package paths. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/build/zlib.cmake | 61 +++++++ cmake/dependencies/zlib.cmake | 177 ++++++++++++--------- cmake/macros/rv_create_std_deps_vars.cmake | 84 ++++++++++ cmake/macros/rv_find_dependency.cmake | 52 +++++- 4 files changed, 300 insertions(+), 74 deletions(-) create mode 100644 cmake/dependencies/build/zlib.cmake diff --git a/cmake/dependencies/build/zlib.cmake b/cmake/dependencies/build/zlib.cmake new file mode 100644 index 000000000..d51796e11 --- /dev/null +++ b/cmake/dependencies/build/zlib.cmake @@ -0,0 +1,61 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# The patch comes from the ZLIB port in Vcpkg repository. The name of the patch is kept as is. See https://github.com/microsoft/vcpkg/tree/master/ports/zlib +# Description: Fix unistd.h being incorrectly required when imported from a project defining HAVE_UNISTD_H=0 +SET(_patch_command + patch -p1 < ${CMAKE_CURRENT_SOURCE_DIR}/patch/zconf.h.cmakein_prevent_invalid_inclusions.patch && patch -p1 < + ${CMAKE_CURRENT_SOURCE_DIR}/patch/zconf.h.in_prevent_invalid_inclusions.patch +) + +EXTERNALPROJECT_ADD( + ${_target} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + PATCH_COMMAND ${_patch_command} + CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} + BUILD_COMMAND ${_cmake_build_command} + INSTALL_COMMAND ${_cmake_install_command} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_zlib_byproducts} + USES_TERMINAL_BUILD TRUE +) + +IF(RV_TARGET_WINDOWS) + # FFmpeg expects "zlib" in Release and Debug. + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Renaming the ZLIB import debug lib to the name FFmpeg is expecting (release name)" + COMMAND ${CMAKE_COMMAND} -E copy ${_implibpath} ${_lib_dir}/zlib.lib + ) + ENDIF() +ENDIF() + +RV_ADD_IMPORTED_LIBRARY( + NAME + ZLIB::ZLIB + TYPE + SHARED + LOCATION + ${_libpath} + SONAME + ${_libname} + IMPLIB + ${_implibpath} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ADD_TO_DEPS_LIST +) diff --git a/cmake/dependencies/zlib.cmake b/cmake/dependencies/zlib.cmake index cef51c747..e7fe2c7a7 100644 --- a/cmake/dependencies/zlib.cmake +++ b/cmake/dependencies/zlib.cmake @@ -4,20 +4,32 @@ # SPDX-License-Identifier: Apache-2.0 # -RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_ZLIB" "${RV_DEPS_ZLIB_VERSION}" "" "") +RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_ZLIB" "${RV_DEPS_ZLIB_VERSION}" "" "" FORCE_LIB) + +# zlib has no CMake CONFIG files, so ALLOW_MODULE lets the built-in FindZLIB.cmake create ZLIB::ZLIB. Also falls back to pkg-config (useful if ZLIB_ROOT points +# to a keg-only Homebrew cellar). +RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + ZLIB + VERSION + ${_version} + PKG_CONFIG_NAME + zlib + ALLOW_MODULE + DEPS_LIST_TARGETS + ZLIB::ZLIB +) SET(_download_url "https://github.com/madler/zlib/archive/refs/tags/v${_version}.zip" ) - SET(_download_hash ${RV_DEPS_ZLIB_DOWNLOAD_HASH} ) -SET(_lib_dir - ${_install_dir}/lib -) - +# Shared naming logic (used by both build and found paths for staging) IF(RV_TARGET_WINDOWS) IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") SET(_zlibname @@ -42,7 +54,6 @@ SET(_libpath ) IF(RV_TARGET_WINDOWS) - # Zlib is a Shared lib (see _libname): On Windows it'll go in the bin dir. SET(_libpath ${_bin_dir}/${_libname} ) @@ -60,79 +71,99 @@ IF(RV_TARGET_WINDOWS) LIST(APPEND _zlib_byproducts ${_implibpath}) ENDIF() -# The patch comes from the ZLIB port in Vcpkg repository. The name of the patch is kept as is. See https://github.com/microsoft/vcpkg/tree/master/ports/zlib -# Description: Fix unistd.h being incorrectly required when imported from a project defining HAVE_UNISTD_H=0 -SET(_patch_command - patch -p1 < ${CMAKE_CURRENT_SOURCE_DIR}/patch/zconf.h.cmakein_prevent_invalid_inclusions.patch && patch -p1 < - ${CMAKE_CURRENT_SOURCE_DIR}/patch/zconf.h.in_prevent_invalid_inclusions.patch -) +IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/zlib.cmake) + + IF(RV_TARGET_WINDOWS) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} OUTPUTS ${RV_STAGE_BIN_DIR}/${_libname}) + ELSEIF(RV_TARGET_DARWIN) + RV_STAGE_DEPENDENCY_LIBS( + TARGET + ${_target} + OUTPUTS + ${RV_STAGE_LIB_DIR}/${_libname} + PRE_COMMANDS + COMMAND + ${CMAKE_INSTALL_NAME_TOOL} + -id + "@rpath/${_libname}" + "${_lib_dir}/${_libname}" + ) + ELSE() + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_libname}) + ENDIF() +ELSE() + IF(NOT TARGET ZLIB::ZLIB) + RV_ADD_IMPORTED_LIBRARY( + NAME + ZLIB::ZLIB + TYPE + SHARED + LOCATION + ${_lib_dir}/${_libname} + SONAME + ${_libname} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() -EXTERNALPROJECT_ADD( - ${_target} - URL ${_download_url} - URL_MD5 ${_download_hash} - DOWNLOAD_NAME ${_target}_${_version}.zip - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src - INSTALL_DIR ${_install_dir} - PATCH_COMMAND ${_patch_command} - CONFIGURE_COMMAND ${CMAKE_COMMAND} ${_configure_options} - BUILD_COMMAND ${_cmake_build_command} - INSTALL_COMMAND ${_cmake_install_command} - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_zlib_byproducts} - USES_TERMINAL_BUILD TRUE -) + IF(RV_TARGET_DARWIN) + # Found zlib may carry a versioned install name (e.g. libz.1.dylib from Homebrew, libz.1.3.1.dylib from MacPorts). Stage with the actual SONAME, rewrite the + # install name to @rpath, and create an unversioned symlink when needed. + # + # Resolve the actual library path, then fall back to ZLIB_LIBRARIES (always set by FindZLIB.cmake). + RV_RESOLVE_IMPORTED_LOCATION(ZLIB::ZLIB _zlib_realpath) + IF(NOT _zlib_realpath + AND ZLIB_LIBRARIES + ) + SET(_zlib_realpath + "${ZLIB_LIBRARIES}" + ) + ENDIF() + + # Resolve symlinks to get the real file, then extract SONAME from install name + GET_FILENAME_COMPONENT(_zlib_realpath "${_zlib_realpath}" REALPATH) + EXECUTE_PROCESS( + COMMAND otool -D "${_zlib_realpath}" + OUTPUT_VARIABLE _zlib_otool_out + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + # otool -D output: first line is the file path, second line is the install name + STRING(REGEX MATCH "[^\n]+$" _zlib_install_name "${_zlib_otool_out}") + GET_FILENAME_COMPONENT(_zlib_soname "${_zlib_install_name}" NAME) + + # Only create the unversioned symlink if the SONAME is versioned (e.g. libz.1.dylib != libz.dylib). When they match (e.g. vcpkg/Conan providing libz.dylib + # directly), skip to avoid a self-referencing symlink. + SET(_zlib_symlink_cmd + "" + ) + IF(NOT "${_zlib_soname}" STREQUAL "${_libname}") + SET(_zlib_symlink_cmd + COMMAND ${CMAKE_COMMAND} -E create_symlink ${_zlib_soname} ${RV_STAGE_LIB_DIR}/${_libname} + ) + ENDIF() -IF(RV_TARGET_WINDOWS) - # FFmpeg expect "zlib" in Release and Debug. - IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") ADD_CUSTOM_COMMAND( - TARGET ${_target} - POST_BUILD - COMMENT "Renaming the ZLIB import debug lib to the name FFmpeg is expecting (release name)" - COMMAND ${CMAKE_COMMAND} -E copy ${_implibpath} ${_lib_dir}/zlib.lib + COMMENT "Staging ${_target}: ${_zlib_soname} -> ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_zlib_soname} + COMMAND ${CMAKE_COMMAND} -E make_directory ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_zlib_realpath}" "${RV_STAGE_LIB_DIR}/${_zlib_soname}" + COMMAND ${CMAKE_INSTALL_NAME_TOOL} -id "@rpath/${_zlib_soname}" "${RV_STAGE_LIB_DIR}/${_zlib_soname}" ${_zlib_symlink_cmd} + DEPENDS ${_target} ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_zlib_soname} + ) + ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + ELSE() + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ZLIB::ZLIB USE_FLAG_FILE) ENDIF() - - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} OUTPUTS ${RV_STAGE_BIN_DIR}/${_libname}) -ELSEIF(RV_TARGET_DARWIN) - RV_STAGE_DEPENDENCY_LIBS( - TARGET - ${_target} - OUTPUTS - ${RV_STAGE_LIB_DIR}/${_libname} - PRE_COMMANDS - COMMAND - ${CMAKE_INSTALL_NAME_TOOL} - -id - "@rpath/${_libname}" - "${_lib_dir}/${_libname}" - ) -ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_libname}) ENDIF() -RV_ADD_IMPORTED_LIBRARY( - NAME - ZLIB::ZLIB - TYPE - SHARED - LOCATION - ${_libpath} - SONAME - ${_libname} - IMPLIB - ${_implibpath} - INCLUDE_DIRS - ${_include_dir} - DEPENDS - ${_target} - ADD_TO_DEPS_LIST -) - SET(RV_DEPS_ZLIB_INCLUDE_DIR ${_include_dir} CACHE STRING "Path to installed includes for ${_target}" diff --git a/cmake/macros/rv_create_std_deps_vars.cmake b/cmake/macros/rv_create_std_deps_vars.cmake index d24a8fe8e..10de1e323 100644 --- a/cmake/macros/rv_create_std_deps_vars.cmake +++ b/cmake/macros/rv_create_std_deps_vars.cmake @@ -303,6 +303,11 @@ MACRO(RV_SET_FOUND_PACKAGE_DIRS rv_deps_target find_package_name) "${_sfpd_unresolved_root_3}/include" ) + # Set both the regular variable (to override the one from RV_CREATE_STANDARD_DEPS_VARIABLES that would otherwise shadow the cache) and the cache variable (for + # persistence across reconfigures). + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + ) SET(${rv_deps_target}_ROOT_DIR "${_install_dir}" CACHE INTERNAL "" FORCE @@ -350,6 +355,85 @@ MACRO(RV_SET_FOUND_PKGCONFIG_DIRS rv_deps_target pc_prefix) "${_install_dir}/bin" ) + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + ) + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + CACHE INTERNAL "" FORCE + ) + + IF(NOT TARGET ${rv_deps_target}) + ADD_CUSTOM_TARGET(${rv_deps_target}) + ENDIF() +ENDMACRO() + +# +# RV_SET_FOUND_MODULE_DIRS +# +# Set directory variables from a package found via CMake MODULE mode (built-in Find modules like FindZLIB, FindJPEG, etc.). +# +# Unlike CONFIG mode packages that provide ${Package}_DIR, MODULE mode results derive locations from the imported target's properties and standard +# _INCLUDE_DIRS / _LIBRARIES variables. +# +# Must be a MACRO so the variable overrides propagate to the caller's scope. +# +MACRO(RV_SET_FOUND_MODULE_DIRS rv_deps_target find_package_name deps_list_targets) + SET(_sfmd_inc_dir + "" + ) + SET(_sfmd_lib_dir + "" + ) + + # Derive include dir: prefer _INCLUDE_DIRS, fall back to first target's INTERFACE_INCLUDE_DIRECTORIES + IF(${find_package_name}_INCLUDE_DIRS) + LIST(GET ${find_package_name}_INCLUDE_DIRS 0 _sfmd_inc_dir) + ELSE() + SET(_sfmd_dep_targets + ${deps_list_targets} + ) + LIST(LENGTH _sfmd_dep_targets _sfmd_dep_count) + IF(_sfmd_dep_count GREATER 0) + LIST(GET _sfmd_dep_targets 0 _sfmd_primary_tgt) + IF(TARGET ${_sfmd_primary_tgt}) + GET_TARGET_PROPERTY(_sfmd_inc_dir ${_sfmd_primary_tgt} INTERFACE_INCLUDE_DIRECTORIES) + ENDIF() + ENDIF() + ENDIF() + + # Derive lib dir from the first imported target's IMPORTED_LOCATION + SET(_sfmd_dep_targets + ${deps_list_targets} + ) + LIST(LENGTH _sfmd_dep_targets _sfmd_dep_count) + IF(_sfmd_dep_count GREATER 0) + LIST(GET _sfmd_dep_targets 0 _sfmd_primary_tgt) + RV_RESOLVE_IMPORTED_LOCATION(${_sfmd_primary_tgt} _sfmd_loc) + IF(_sfmd_loc) + GET_FILENAME_COMPONENT(_sfmd_lib_dir "${_sfmd_loc}" DIRECTORY) + ENDIF() + ENDIF() + + IF(_sfmd_inc_dir) + SET(_include_dir + "${_sfmd_inc_dir}" + ) + ENDIF() + IF(_sfmd_lib_dir) + SET(_lib_dir + "${_sfmd_lib_dir}" + ) + ENDIF() + + GET_FILENAME_COMPONENT(_install_dir "${_lib_dir}/.." ABSOLUTE) + SET(_bin_dir + "${_install_dir}/bin" + ) + + SET(${rv_deps_target}_ROOT_DIR + "${_install_dir}" + ) SET(${rv_deps_target}_ROOT_DIR "${_install_dir}" CACHE INTERNAL "" FORCE diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake index 63fbad839..95faf75e2 100644 --- a/cmake/macros/rv_find_dependency.cmake +++ b/cmake/macros/rv_find_dependency.cmake @@ -24,12 +24,13 @@ # PACKAGE # REQUIRED: e.g., Imath # [VERSION ] # Optional version (EXACT or MINIMUM per RV_DEPS_VERSION_MATCH) # [PKG_CONFIG_NAME ] # Optional pkg-config module name for fallback +# [ALLOW_MODULE] # Also try find_package(MODULE) for packages with built-in Find modules # [COMPONENTS ...] # Optional find_package COMPONENTS (e.g., Boost components) # [DEPS_LIST_TARGETS ...] # Targets to append to RV_DEPS_LIST # ) # cmake-format: on MACRO(RV_FIND_DEPENDENCY) - CMAKE_PARSE_ARGUMENTS(_RFD "" "TARGET;PACKAGE;VERSION;PKG_CONFIG_NAME" "DEPS_LIST_TARGETS;COMPONENTS" ${ARGN}) + CMAKE_PARSE_ARGUMENTS(_RFD "ALLOW_MODULE" "TARGET;PACKAGE;VERSION;PKG_CONFIG_NAME" "DEPS_LIST_TARGETS;COMPONENTS" ${ARGN}) SET(${_RFD_TARGET}_FOUND FALSE @@ -80,12 +81,44 @@ MACRO(RV_FIND_DEPENDENCY) ) ENDIF() + # Strategy 1.5: CMake MODULE mode (built-in Find modules) Some packages (e.g., zlib) don't ship CMake config files but have built-in Find modules + # (FindZLIB.cmake) that create proper imported targets. Opt-in via ALLOW_MODULE to avoid unexpected matches. + IF(NOT ${_RFD_PACKAGE}_FOUND + AND _RFD_ALLOW_MODULE + ) + IF(_RFD_VERSION) + IF(_rfd_match_mode STREQUAL "EXACT") + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} EXACT MODULE ${_rfd_components_args}) + ELSE() + FIND_PACKAGE(${_RFD_PACKAGE} ${_RFD_VERSION} MODULE ${_rfd_components_args}) + ENDIF() + ELSE() + FIND_PACKAGE(${_RFD_PACKAGE} MODULE ${_rfd_components_args}) + ENDIF() + + IF(${_RFD_PACKAGE}_FOUND) + SET(_RFD_FOUND_VIA + "module" + ) + ENDIF() + ENDIF() + # Strategy 2: pkg-config fallback IF(NOT ${_RFD_PACKAGE}_FOUND AND _RFD_PKG_CONFIG_NAME ) FIND_PACKAGE(PkgConfig QUIET) IF(PKG_CONFIG_FOUND) + # pkg_check_modules only searches PKG_CONFIG_PATH by default. Temporarily enable CMAKE_PREFIX_PATH integration so that + # -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/zlib also exposes /lib/pkgconfig/*.pc to pkg-config. Restored afterwards to avoid side-effects on + # unrelated pkg_check_modules calls. + SET(_rfd_saved_pc_prefix_path + "${PKG_CONFIG_USE_CMAKE_PREFIX_PATH}" + ) + SET(PKG_CONFIG_USE_CMAKE_PREFIX_PATH + ON + ) + IF(_RFD_VERSION) IF(_rfd_match_mode STREQUAL "EXACT") PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET "${_RFD_PKG_CONFIG_NAME}=${_RFD_VERSION}") @@ -96,6 +129,10 @@ MACRO(RV_FIND_DEPENDENCY) PKG_CHECK_MODULES(${_RFD_TARGET}_PC QUIET IMPORTED_TARGET ${_RFD_PKG_CONFIG_NAME}) ENDIF() + SET(PKG_CONFIG_USE_CMAKE_PREFIX_PATH + "${_rfd_saved_pc_prefix_path}" + ) + IF(${_RFD_TARGET}_PC_FOUND) SET(${_RFD_PACKAGE}_FOUND TRUE @@ -125,6 +162,16 @@ MACRO(RV_FIND_DEPENDENCY) # auxiliary interface targets (e.g. Imath::ImathConfig, OpenEXR::OpenEXRConfig) that carry include dirs and are linked transitively by the library # targets. IF(NOT "${_sfpd_unresolved_include_dir}" STREQUAL "${_include_dir}") + # Record the contaminating prefix so ExternalProject sub-builds can exclude it via CMAKE_IGNORE_PREFIX_PATH. This is how we propagate the symlink fix + # to sub-builds that run their own CMake and would otherwise rediscover packages at the unresolved (shared) prefix. + LIST(APPEND RV_DEPS_IGNORE_PREFIXES "${_sfpd_unresolved_root_3}") + LIST(REMOVE_DUPLICATES RV_DEPS_IGNORE_PREFIXES) + SET(RV_DEPS_IGNORE_PREFIXES + "${RV_DEPS_IGNORE_PREFIXES}" + CACHE INTERNAL "Contaminating prefixes detected during symlink resolution (for ExternalProject sub-builds)" FORCE + ) + MESSAGE(STATUS " Added ${_sfpd_unresolved_root_3} to RV_DEPS_IGNORE_PREFIXES (symlink resolved to ${_install_dir})") + GET_PROPERTY( _rfd_all_imported_targets DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" @@ -152,6 +199,9 @@ MACRO(RV_FIND_DEPENDENCY) ENDIF() ENDFOREACH() ENDIF() + ELSEIF(_RFD_FOUND_VIA STREQUAL "module") + MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via CMake Find module. Using installed package.") + RV_SET_FOUND_MODULE_DIRS(${_RFD_TARGET} ${_RFD_PACKAGE} "${_RFD_DEPS_LIST_TARGETS}") ELSEIF(_RFD_FOUND_VIA STREQUAL "pkgconfig") MESSAGE(STATUS "Found ${_RFD_PACKAGE} ${${_RFD_PACKAGE}_VERSION} via pkg-config. Using installed package.") RV_SET_FOUND_PKGCONFIG_DIRS(${_RFD_TARGET} ${_RFD_TARGET}_PC) From 791f25eb4edeb1347d25c808e7344cddb02fbf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Wed, 18 Mar 2026 23:16:28 -0400 Subject: [PATCH 08/15] build: fix OIIO sub-build Boost header contamination and Imath resolution MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix OIIO ExternalProject sub-build failing with undefined boost::filesystem symbols due to Homebrew include path contamination. When deps like Imath and fmt come from Homebrew, their transitive -isystem /opt/homebrew/include pulls in newer Boost headers (with _v3 namespaced symbols) that appear before RV's Boost 1.82 headers in the include order, causing ABI mismatches at link time. Fix by adding RV's Boost include directory as a non-system -I flag in the OIIO sub-build when contaminating prefixes are detected. Compilers always search -I before -isystem, ensuring the correct Boost headers are found. Replace fragile GET_TARGET_PROPERTY(IMPORTED_LOCATION) Imath resolution with direct *_DIR variables from RV_DEPS_*_ROOT_DIR, which work for both built-from-source and found (e.g. Homebrew) packages. Fix typo: -USE_FFMPEG=0 -> -DUSE_FFMPEG=0. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/ocio.cmake | 4 ++-- cmake/dependencies/oiio.cmake | 25 +++++++++++-------------- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/cmake/dependencies/ocio.cmake b/cmake/dependencies/ocio.cmake index 99c77312c..2aaf7419e 100644 --- a/cmake/dependencies/ocio.cmake +++ b/cmake/dependencies/ocio.cmake @@ -138,8 +138,8 @@ IF(NOT RV_TARGET_WINDOWS) ENDIF() LIST(APPEND _configure_options "-DOCIO_PYTHON_VERSION=${RV_DEPS_PYTHON_VERSION_SHORT}") -# Using Imath_ROOT because Imath_DIR does not seems to be enough on UNIX-based platform (at least Rocky linux). -LIST(APPEND _configure_options "-DImath_ROOT=${RV_DEPS_IMATH_ROOT_DIR}") +# Use explicit Imath_DIR for precise config resolution. Works for both built-from-source and found (e.g. Homebrew) packages. +LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath") LIST(APPEND _configure_options "-DZLIB_ROOT=${RV_DEPS_ZLIB_ROOT_DIR}") diff --git a/cmake/dependencies/oiio.cmake b/cmake/dependencies/oiio.cmake index c364604f7..055240335 100644 --- a/cmake/dependencies/oiio.cmake +++ b/cmake/dependencies/oiio.cmake @@ -52,20 +52,10 @@ LIST(APPEND _configure_options "-DUSE_OCIO=0") LIST(APPEND _configure_options "-DUSE_FREETYPE=0") LIST(APPEND _configure_options "-DUSE_GIF=OFF") -LIST(APPEND _configure_options "-DBoost_ROOT=${RV_DEPS_BOOST_ROOT_DIR}") +# Use explicit *_DIR variables pointing directly to config file directories for more precise package resolution. +LIST(APPEND _configure_options "-DBoost_DIR=${RV_DEPS_BOOST_ROOT_DIR}/lib/cmake/Boost-${RV_DEPS_BOOST_VERSION}") LIST(APPEND _configure_options "-DOpenEXR_ROOT=${RV_DEPS_OPENEXR_ROOT_DIR}") - -IF(NOT RV_TARGET_WINDOWS) - GET_TARGET_PROPERTY(_imath_library Imath::Imath IMPORTED_LOCATION) - GET_TARGET_PROPERTY(_imath_include_dir Imath::Imath INTERFACE_INCLUDE_DIRECTORIES) - LIST(APPEND _configure_options "-DImath_LIBRARY=${_imath_library}") - LIST(APPEND _configure_options "-DImath_INCLUDE_DIR=${_imath_include_dir}/..") - GET_FILENAME_COMPONENT(_imath_library_path ${_imath_library} DIRECTORY) - LIST(APPEND _configure_options "-DImath_DIR=${_imath_library_path}/cmake/Imath") -ELSE() - # Must point to the IMath-config.cmake file which is a 'FindIMath.cmake' type of file. - LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath") -ENDIF() +LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath") GET_TARGET_PROPERTY(_png_library PNG::PNG IMPORTED_LOCATION) GET_TARGET_PROPERTY(_png_include_dir PNG::PNG INTERFACE_INCLUDE_DIRECTORIES) @@ -94,7 +84,14 @@ LIST(APPEND _configure_options "-DOPENJPEG_INCLUDE_DIR=${_openjpeg_include_dir}" LIST(APPEND _configure_options "-DTIFF_ROOT=${RV_DEPS_TIFF_ROOT_DIR}") -LIST(APPEND _configure_options "-USE_FFMPEG=0") +LIST(APPEND _configure_options "-DUSE_FFMPEG=0") + +# When Boost is built from source but other deps come from a shared prefix (e.g. Homebrew), their transitive -isystem includes can pull in a newer system +# Boost's headers, causing ABI mismatches at link time. Adding Boost's include as a non-system -I flag ensures it takes precedence over any -isystem paths, +# since compilers (GCC/Clang) always search -I before -isystem. +IF(RV_DEPS_IGNORE_PREFIXES) + LIST(APPEND _configure_options "-DCMAKE_CXX_FLAGS=-I${RV_DEPS_BOOST_ROOT_DIR}/include") +ENDIF() IF(RV_TARGET_LINUX) MESSAGE(STATUS "Building OpenImageIO using system's freetype library.") From 233f5eabd1bac6882ea350230da524b3754dfbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Thu, 19 Mar 2026 10:03:12 -0400 Subject: [PATCH 09/15] build: fix plugin link race condition with ExternalProject dependencies MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add ADD_DEPENDENCIES(${arg_TARGET} dependencies) to all plugin type handlers in rv_stage.cmake (IMAGE_FORMAT, MOVIE_FORMAT, OIIO_PLUGIN, OUTPUT_PLUGIN, MU_PLUGIN, PYTHON_PLUGIN). CMake's ADD_DEPENDENCIES on IMPORTED targets is not transitive, so plugin targets that transitively link against ExternalProject outputs (e.g. libpython3.11.dylib via Python::Python) had no build-order guarantee that the ExternalProject had completed. This caused intermittent link failures in parallel builds when some deps resolve instantly via find_package instead of building from source, shifting the ninja schedule. The EXECUTABLE and EXECUTABLE_WITH_PLUGINS types already had this dependency. Plugins were missing it. Signed-off-by: Cédrik Fuoco --- cmake/macros/rv_stage.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmake/macros/rv_stage.cmake b/cmake/macros/rv_stage.cmake index 4e3212bc9..90902571a 100644 --- a/cmake/macros/rv_stage.cmake +++ b/cmake/macros/rv_stage.cmake @@ -375,6 +375,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(mu_plugins ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -409,6 +410,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(python_plugins ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -525,6 +527,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(image_formats ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -554,6 +557,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(movie_formats ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -584,6 +588,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(oiio_plugins ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) @@ -613,6 +618,7 @@ FUNCTION(rv_stage) ENDIF() ADD_DEPENDENCIES(output_plugins ${arg_TARGET}) + ADD_DEPENDENCIES(${arg_TARGET} dependencies) ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) From a62d3679fc938b3ad4840e59a59c39b9f6c2f63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Thu, 19 Mar 2026 14:24:04 -0400 Subject: [PATCH 10/15] build: PCRE2 10.44+ fixed the __builtin_mul_overflow / _pcre2_ckd_smul detection issue on MSVC. Although, I am not sure why this appeared just now MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- cmake/defaults/CYCOMMON.cmake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/defaults/CYCOMMON.cmake b/cmake/defaults/CYCOMMON.cmake index 70a711c48..53b45f90f 100644 --- a/cmake/defaults/CYCOMMON.cmake +++ b/cmake/defaults/CYCOMMON.cmake @@ -208,10 +208,10 @@ SET(RV_DEPS_OTIO_VERSION # pcre2 https://github.com/PCRE2Project/pcre2 SET(RV_DEPS_PCRE2_VERSION - "10.43" + "10.47" ) SET(RV_DEPS_PCRE2_DOWNLOAD_HASH - "e4c3f2a24eb5c15bec8360e50b3f0137" + "9a77e2cdc4410addf9a77363a89fe858" ) # png https://github.com/glennrp/libpng From c8c8d88ac839b4e5e9d7a7178ae9991c6ca97505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 20 Mar 2026 10:13:41 -0400 Subject: [PATCH 11/15] build: add OpenSSL find_package dispatcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Refactor openssl.cmake to use the find-first dispatcher pattern: - RV_FIND_DEPENDENCY with ALLOW_MODULE and PKG_CONFIG_NAME for CONFIG → MODULE (FindOpenSSL) → pkg-config resolution - Extract ExternalProject_Add build logic into build/openssl.cmake - Preserve RHEL8/CY2023 system OpenSSL 1.1.1 path - Use TARGET_LIBS for found-package staging - Linux OpenSSL/ subdirectory isolation for RHEL ABI compatibility - Shared naming logic for both found and built paths Signed-off-by: Cédrik Fuoco --- cmake/dependencies/build/openssl.cmake | 129 ++++++++++ cmake/dependencies/openssl.cmake | 310 ++++++++----------------- 2 files changed, 223 insertions(+), 216 deletions(-) create mode 100644 cmake/dependencies/build/openssl.cmake diff --git a/cmake/dependencies/build/openssl.cmake b/cmake/dependencies/build/openssl.cmake new file mode 100644 index 000000000..a7df4230c --- /dev/null +++ b/cmake/dependencies/build/openssl.cmake @@ -0,0 +1,129 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(RV_DEPS_WIN_PERL_ROOT + "" + CACHE STRING "Path to Windows perl root" +) + +STRING(REPLACE "." "_" _version_underscored ${_version}) + +IF(RV_TARGET_WINDOWS + AND (NOT RV_DEPS_WIN_PERL_ROOT + OR RV_DEPS_WIN_PERL_ROOT STREQUAL "") +) + MESSAGE( + FATAL_ERROR + "Unable to build without a RV_DEPS_WIN_PERL_ROOT. OpenSSL requires a Windows native perl interpreter to build (it recommends https://strawberryperl.com/). Example -DRV_DEPS_WIN_PERL_ROOT=c:/Strawberry/perl/bin" + ) +ENDIF() + +SET(_make_command_script + "${PROJECT_SOURCE_DIR}/src/build/make_openssl.py" +) +SET(_make_command + python3 "${_make_command_script}" +) + +LIST(APPEND _make_command "--source-dir") +LIST(APPEND _make_command ${_source_dir}) +LIST(APPEND _make_command "--output-dir") +LIST(APPEND _make_command ${_install_dir}) + +LIST(APPEND _make_command "--vfx_platform") +LIST(APPEND _make_command ${RV_VFX_CY_YEAR}) + +IF(RV_TARGET_WINDOWS) + LIST(APPEND _make_command "--perlroot") + LIST(APPEND _make_command ${RV_DEPS_WIN_PERL_ROOT}) +ENDIF() + +IF(APPLE) + IF(RV_TARGET_APPLE_X86_64) + SET(__openssl_arch__ + x86_64 + ) + ELSEIF(RV_TARGET_APPLE_ARM64) + SET(__openssl_arch__ + arm64 + ) + ENDIF() + LIST(APPEND _make_command --arch=-${__openssl_arch__}) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${_source_dir} + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_make_command} --configure + BUILD_COMMAND ${_make_command} --build + INSTALL_COMMAND ${_make_command} --install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_crypto_lib} ${_ssl_lib} + USES_TERMINAL_BUILD TRUE +) + +FILE(MAKE_DIRECTORY ${_include_dir}) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Renaming the openssl import libs to the name FFmpeg is expecting" + COMMAND ${CMAKE_COMMAND} -E copy ${_install_dir}/lib/libssl.lib ${_lib_dir}/ssl.lib + COMMAND ${CMAKE_COMMAND} -E copy ${_install_dir}/lib/libcrypto.lib ${_lib_dir}/crypto.lib + ) +ENDIF() + +ADD_LIBRARY(OpenSSL::Crypto SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenSSL::Crypto ${_target}) +SET_PROPERTY( + TARGET OpenSSL::Crypto + PROPERTY IMPORTED_LOCATION ${_crypto_lib} +) +SET_PROPERTY( + TARGET OpenSSL::Crypto + PROPERTY IMPORTED_SONAME ${_crypto_lib_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenSSL::Crypto + PROPERTY IMPORTED_IMPLIB ${_implibpath_crypto} + ) +ENDIF() +TARGET_INCLUDE_DIRECTORIES( + OpenSSL::Crypto + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST OpenSSL::Crypto) + +ADD_LIBRARY(OpenSSL::SSL SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenSSL::SSL ${_target}) +SET_PROPERTY( + TARGET OpenSSL::SSL + PROPERTY IMPORTED_LOCATION ${_ssl_lib} +) +SET_PROPERTY( + TARGET OpenSSL::SSL + PROPERTY IMPORTED_SONAME ${_ssl_lib_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenSSL::SSL + PROPERTY IMPORTED_IMPLIB ${_implibpath_ssl} + ) +ENDIF() +TARGET_INCLUDE_DIRECTORIES( + OpenSSL::SSL + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST OpenSSL::SSL) diff --git a/cmake/dependencies/openssl.cmake b/cmake/dependencies/openssl.cmake index 43ab27dfa..f57b61b30 100644 --- a/cmake/dependencies/openssl.cmake +++ b/cmake/dependencies/openssl.cmake @@ -4,18 +4,12 @@ # SPDX-License-Identifier: Apache-2.0 # -SET(_target - "RV_DEPS_OPENSSL" -) -SET(_version - ${RV_DEPS_OPENSSL_VERSION} -) +RV_CREATE_STANDARD_DEPS_VARIABLES("RV_DEPS_OPENSSL" "${RV_DEPS_OPENSSL_VERSION}" "" "") IF(RV_TARGET_IS_RHEL8 AND RV_VFX_PLATFORM STREQUAL CY2023 ) - # VFX2023: Rocky Linux 8 - + # VFX2023 on Rocky Linux 8: use system OpenSSL 1.1.1 FIND_PACKAGE(OpenSSL 1.1.1 REQUIRED) SET(RV_DEPS_OPENSSL_VERSION @@ -28,125 +22,38 @@ IF(RV_TARGET_IS_RHEL8 ) GET_FILENAME_COMPONENT(_lib_dir "${OPENSSL_SSL_LIBRARY}" DIRECTORY) - MESSAGE(STATUS "_include_dir ${_include_dir}") - MESSAGE(STATUS "_lib_dir ${_lib_dir}") - ELSE() - # VFX2023: Rocky Linux 9, Windows and MacOS VFX2024: Rocky Linux 8/9, Windows and MacOS - - SET(RV_DEPS_WIN_PERL_ROOT - "" - CACHE STRING "Path to Windows perl root" - ) - - SET(_target - "RV_DEPS_OPENSSL" + # All other platforms: find-first dispatcher. Tries CONFIG, then MODULE (FindOpenSSL.cmake), then pkg-config. On systems where OpenSSL is not on the default + # search path (e.g., keg-only on macOS, custom install on Linux/Windows), hint with CMAKE_PREFIX_PATH, OPENSSL_ROOT_DIR, or PKG_CONFIG_PATH. + RV_FIND_DEPENDENCY( + TARGET + ${_target} + PACKAGE + OpenSSL + VERSION + ${_version} + PKG_CONFIG_NAME + openssl + ALLOW_MODULE + DEPS_LIST_TARGETS + OpenSSL::Crypto + OpenSSL::SSL ) - STRING(REPLACE "." "_" _version_underscored ${_version}) - - IF(RV_TARGET_WINDOWS - AND (NOT RV_DEPS_WIN_PERL_ROOT - OR RV_DEPS_WIN_PERL_ROOT STREQUAL "") + SET(_download_url + "https://github.com/openssl/openssl/releases/download/openssl-${_version}/openssl-${_version}.tar.gz" ) - MESSAGE( - FATAL_ERROR - "Unable to build without a RV_DEPS_WIN_PERL_ROOT. OpenSSL requires a Windows native perl interpreter to build (it recommends https://strawberryperl.com/). Example -DRV_DEPS_WIN_PERL_ROOT=c:/Strawberry/perl/bin" - ) - ENDIF() - - SET(RV_DEPS_OPENSSL_INSTALL_DIR - ${RV_DEPS_BASE_DIR}/${_target}/install - ) - SET(_include_dir - ${RV_DEPS_OPENSSL_INSTALL_DIR}/include - ) - SET(_source_dir - ${RV_DEPS_BASE_DIR}/${_target}/src - ) - SET(_build_dir - ${RV_DEPS_BASE_DIR}/${_target}/build - ) - - IF(RHEL_VERBOSE) - IF(RV_VFX_PLATFORM STREQUAL "CY2023") - SET(_lib_dir - "${RV_DEPS_OPENSSL_INSTALL_DIR}/lib" - ) - ELSEIF(RV_VFX_PLATFORM STRGREATER_EQUAL "CY2024") - SET(_lib_dir - "${RV_DEPS_OPENSSL_INSTALL_DIR}/lib64" - ) - ENDIF() - ELSE() - SET(_lib_dir - ${RV_DEPS_OPENSSL_INSTALL_DIR}/lib - ) - ENDIF() - - SET(_bin_dir - ${RV_DEPS_OPENSSL_INSTALL_DIR}/bin - ) - IF(${_version} STREQUAL "1.1.1u") + STRING(REPLACE "." "_" _version_underscored ${_version}) SET(_download_url "https://github.com/openssl/openssl/releases/download/OpenSSL_${_version_underscored}/openssl-${_version}.tar.gz" ) - ELSE() - SET(_download_url - "https://github.com/openssl/openssl/releases/download/openssl-${_version}/openssl-${_version}.tar.gz" - ) ENDIF() - SET(_download_hash ${RV_DEPS_OPENSSL_HASH} ) - SET(_make_command_script - "${PROJECT_SOURCE_DIR}/src/build/make_openssl.py" - ) - SET(_make_command - python3 "${_make_command_script}" - ) - - LIST(APPEND _make_command "--source-dir") - LIST(APPEND _make_command ${_source_dir}) - LIST(APPEND _make_command "--output-dir") - LIST(APPEND _make_command ${RV_DEPS_OPENSSL_INSTALL_DIR}) - - LIST(APPEND _make_command "--vfx_platform") - - LIST(APPEND _make_command ${RV_VFX_CY_YEAR}) - - IF(RV_TARGET_WINDOWS) - LIST(APPEND _make_command "--perlroot") - LIST(APPEND _make_command ${RV_DEPS_WIN_PERL_ROOT}) - ENDIF() - - IF(APPLE) - # This is needed because if Rosetta is used to compile for x86_64 from ARM64, openssl build system detects it as "linux-x86_64" and it causes issues. - - IF(RV_TARGET_APPLE_X86_64) - SET(__openssl_arch__ - x86_64 - ) - ELSEIF(RV_TARGET_APPLE_ARM64) - SET(__openssl_arch__ - arm64 - ) - ENDIF() - - LIST(APPEND _make_command --arch=-${__openssl_arch__}) - ENDIF() - - # On most POSIX platforms, shared libraries are named `libcrypto.so.1.1` and `libssl.so.1.1`. - - # On Windows build with MSVC or using MingW, shared libraries are named `libcrypto-1_1.dll` and `libssl-1_1.dll` for 32-bit Windows, `libcrypto-1_1-x64.dll` - # and `libssl-1_1-x64.dll` for 64-bit x86_64 Windows, and `libcrypto-1_1-ia64.dll` and `libssl-1_1-ia64.dll` for IA64 Windows. With MSVC, the import libraries - # are named `libcrypto.lib` and `libssl.lib`, while with MingW, they are named `libcrypto.dll.a` and `libssl.dll.a`. - - # Ref: https://github.com/openssl/openssl/blob/398011848468c7e8e481b295f7904afc30934217/INSTALL.md?plain=1#L1847-L1858 - + # Shared naming logic (used by both build and found paths) SET(_dot_version ${RV_DEPS_OPENSSL_VERSION_DOT} ) @@ -158,12 +65,10 @@ ELSE() SET(_crypto_lib_name ${CMAKE_SHARED_LIBRARY_PREFIX}crypto${CMAKE_SHARED_LIBRARY_SUFFIX}${_dot_version} ) - SET(_ssl_lib_name ${CMAKE_SHARED_LIBRARY_PREFIX}ssl${CMAKE_SHARED_LIBRARY_SUFFIX}${_dot_version} ) ELSEIF(RV_TARGET_WINDOWS) - # As stated in the openssl documentation, the names are libcrypto-1_1-x64 and libssl-1_1-x64 when OpenSSL is build with MSVC. SET(_crypto_lib_name libcrypto-${_underscore_version}-x64${CMAKE_SHARED_LIBRARY_SUFFIX} ) @@ -182,7 +87,6 @@ ELSE() SET(_crypto_lib ${_lib_dir}/${_crypto_lib_name} ) - SET(_ssl_lib ${_lib_dir}/${_ssl_lib_name} ) @@ -191,108 +95,77 @@ ELSE() SET(_implibpath_crypto ${_lib_dir}/${CMAKE_IMPORT_LIBRARY_PREFIX}crypto${CMAKE_IMPORT_LIBRARY_SUFFIX} ) - SET(_implibpath_ssl ${_lib_dir}/${CMAKE_IMPORT_LIBRARY_PREFIX}ssl${CMAKE_IMPORT_LIBRARY_SUFFIX} ) ENDIF() - EXTERNALPROJECT_ADD( - ${_target} - DOWNLOAD_NAME ${_target}_${_version}.zip - DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} - DOWNLOAD_EXTRACT_TIMESTAMP TRUE - SOURCE_DIR ${_source_dir} - INSTALL_DIR ${RV_DEPS_OPENSSL_INSTALL_DIR} - URL ${_download_url} - URL_MD5 ${_download_hash} - CONFIGURE_COMMAND ${_make_command} --configure - BUILD_COMMAND ${_make_command} --build - INSTALL_COMMAND ${_make_command} --install - BUILD_IN_SOURCE TRUE - BUILD_ALWAYS FALSE - BUILD_BYPRODUCTS ${_crypto_lib} ${_ssl_lib} - USES_TERMINAL_BUILD TRUE - ) - - FILE(MAKE_DIRECTORY ${_include_dir}) - - ADD_LIBRARY(OpenSSL::Crypto SHARED IMPORTED GLOBAL) - ADD_DEPENDENCIES(OpenSSL::Crypto ${_target}) - SET_PROPERTY( - TARGET OpenSSL::Crypto - PROPERTY IMPORTED_LOCATION ${_crypto_lib} - ) - SET_PROPERTY( - TARGET OpenSSL::Crypto - PROPERTY IMPORTED_SONAME ${_crypto_lib_name} - ) - IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET OpenSSL::Crypto - PROPERTY IMPORTED_IMPLIB ${_implibpath_crypto} - ) - ENDIF() - TARGET_INCLUDE_DIRECTORIES( - OpenSSL::Crypto - INTERFACE ${_include_dir} - ) - LIST(APPEND RV_DEPS_LIST OpenSSL::Crypto) - - ADD_LIBRARY(OpenSSL::SSL SHARED IMPORTED GLOBAL) - ADD_DEPENDENCIES(OpenSSL::SSL ${_target}) - SET_PROPERTY( - TARGET OpenSSL::SSL - PROPERTY IMPORTED_LOCATION ${_ssl_lib} - ) - SET_PROPERTY( - TARGET OpenSSL::SSL - PROPERTY IMPORTED_SONAME ${_ssl_lib_name} - ) - IF(RV_TARGET_WINDOWS) - SET_PROPERTY( - TARGET OpenSSL::SSL - PROPERTY IMPORTED_IMPLIB ${_implibpath_ssl} - ) - ENDIF() - TARGET_INCLUDE_DIRECTORIES( - OpenSSL::SSL - INTERFACE ${_include_dir} - ) - LIST(APPEND RV_DEPS_LIST OpenSSL::SSL) - - SET(_openssl_stage_lib_dir - ${RV_STAGE_LIB_DIR} - ) - - IF(RV_TARGET_WINDOWS) - ADD_CUSTOM_COMMAND( - TARGET ${_target} - POST_BUILD - COMMENT "Renaming the openssl import libs to the name FFmpeg is expecting" - COMMAND ${CMAKE_COMMAND} -E copy ${RV_DEPS_OPENSSL_INSTALL_DIR}/lib/libssl.lib ${_lib_dir}/ssl.lib - COMMAND ${CMAKE_COMMAND} -E copy ${RV_DEPS_OPENSSL_INSTALL_DIR}/lib/libcrypto.lib ${_lib_dir}/crypto.lib - ) - # Copy import libs to stage lib dir and specific DLLs to stage bin dir - ADD_CUSTOM_COMMAND( - COMMENT "Staging ${_target} libs into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" - OUTPUT ${RV_STAGE_BIN_DIR}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR}/${_ssl_lib_name} - COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${_bin_dir}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR} - COMMAND ${CMAKE_COMMAND} -E copy ${_bin_dir}/${_ssl_lib_name} ${RV_STAGE_BIN_DIR} - DEPENDS ${_target} - ) - ADD_CUSTOM_TARGET( - ${_target}-stage-target ALL - DEPENDS ${RV_STAGE_BIN_DIR}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR}/${_ssl_lib_name} - ) - ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + IF(NOT ${_target}_FOUND) + INCLUDE(${CMAKE_CURRENT_LIST_DIR}/build/openssl.cmake) + + IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + COMMENT "Staging ${_target} libs into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + OUTPUT ${RV_STAGE_BIN_DIR}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR}/${_ssl_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${_bin_dir}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${_bin_dir}/${_ssl_lib_name} ${RV_STAGE_BIN_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR}/${_ssl_lib_name} + ) + ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + ELSE() + SET(_openssl_stage_lib_dir + ${RV_STAGE_LIB_DIR} + ) + IF(RV_TARGET_LINUX) + SET(_openssl_stage_lib_dir + ${_openssl_stage_lib_dir}/OpenSSL + ) + ENDIF() + + RV_STAGE_DEPENDENCY_LIBS( + TARGET + ${_target} + STAGE_LIB_DIR + ${_openssl_stage_lib_dir} + OUTPUTS + ${_openssl_stage_lib_dir}/${_crypto_lib_name} + ${_openssl_stage_lib_dir}/${_ssl_lib_name} + ) + ENDIF() ELSE() - - # Because RHEL8 has the same version of openssl library as we use but is not compatible with our library, we will copy openssl into its own seperate lib - # directory and conditionally add it to the LD_LIBRARY_PATH if the version we build does not match the system version. This will allow RHEL8 to use its own - # system version - # + FOREACH( + _ssl_target + OpenSSL::Crypto OpenSSL::SSL + ) + IF(NOT TARGET ${_ssl_target}) + STRING( + REGEX + REPLACE ".*::" "" _ssl_short ${_ssl_target} + ) + STRING(TOLOWER ${_ssl_short} _ssl_lower) + RV_ADD_IMPORTED_LIBRARY( + NAME + ${_ssl_target} + TYPE + SHARED + LOCATION + ${_lib_dir}/lib${_ssl_lower}${CMAKE_SHARED_LIBRARY_SUFFIX} + INCLUDE_DIRS + ${_include_dir} + DEPENDS + ${_target} + ) + ENDIF() + ENDFOREACH() + + SET(_openssl_stage_lib_dir + ${RV_STAGE_LIB_DIR} + ) IF(RV_TARGET_LINUX) SET(_openssl_stage_lib_dir ${_openssl_stage_lib_dir}/OpenSSL @@ -304,12 +177,17 @@ ELSE() ${_target} STAGE_LIB_DIR ${_openssl_stage_lib_dir} - OUTPUTS - ${_openssl_stage_lib_dir}/${_crypto_lib_name} - ${_openssl_stage_lib_dir}/${_ssl_lib_name} + TARGET_LIBS + OpenSSL::Crypto + OpenSSL::SSL + USE_FLAG_FILE ) ENDIF() + SET(RV_DEPS_OPENSSL_INSTALL_DIR + ${_install_dir} + ) + SET(RV_DEPS_OPENSSL_VERSION ${_version} CACHE INTERNAL "" FORCE From 5f25d372038937471ca8537bd6d1c526720c83c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 20 Mar 2026 10:14:03 -0400 Subject: [PATCH 12/15] build: auto-resolve staged outputs from TARGET_LIBS imported locations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace USE_FLAG_FILE with proper output tracking for TARGET_LIBS: the staging macro now resolves IMPORTED_LOCATION at configure time to derive output filenames, so Ninja detects deleted staged files and re-runs the staging command automatically. Falls back to flag file if location cannot be resolved. Also convert dav1d, ffmpeg, and ocio from LIB_DIR copy-directory staging to TARGET_LIBS for the same incremental-build benefit. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/boost.cmake | 2 +- cmake/dependencies/dav1d.cmake | 6 +- cmake/dependencies/ffmpeg.cmake | 16 +++-- cmake/dependencies/glew.cmake | 2 +- cmake/dependencies/ocio.cmake | 4 +- cmake/dependencies/openexr.cmake | 2 +- cmake/dependencies/openssl.cmake | 1 - cmake/dependencies/zlib.cmake | 2 +- cmake/macros/rv_stage_dependency_libs.cmake | 75 ++++++++++++++++++--- 9 files changed, 82 insertions(+), 28 deletions(-) diff --git a/cmake/dependencies/boost.cmake b/cmake/dependencies/boost.cmake index d9bf5ad8c..181eb3077 100644 --- a/cmake/dependencies/boost.cmake +++ b/cmake/dependencies/boost.cmake @@ -186,5 +186,5 @@ ELSE() ENDFOREACH() # Found path: actual filenames may differ (e.g. -mt suffix), use TARGET_LIBS to resolve at build time - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_boost_deps_list_targets} USE_FLAG_FILE) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_boost_deps_list_targets}) ENDIF() diff --git a/cmake/dependencies/dav1d.cmake b/cmake/dependencies/dav1d.cmake index e412a7270..4f979a2d6 100644 --- a/cmake/dependencies/dav1d.cmake +++ b/cmake/dependencies/dav1d.cmake @@ -94,11 +94,7 @@ ELSE() ENDIF() # --- Staging --- -IF(RV_TARGET_WINDOWS) - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} USE_FLAG_FILE) -ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} USE_FLAG_FILE) -ENDIF() +RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS dav1d::dav1d) # --- FFmpeg customization adding dav1d codec support --- SET_PROPERTY( diff --git a/cmake/dependencies/ffmpeg.cmake b/cmake/dependencies/ffmpeg.cmake index 7f949bdf6..a676a2e84 100644 --- a/cmake/dependencies/ffmpeg.cmake +++ b/cmake/dependencies/ffmpeg.cmake @@ -410,13 +410,15 @@ ADD_CUSTOM_TARGET( COMMAND ${CMAKE_COMMAND} -E remove_directory ${RV_DEPS_BASE_DIR}/cmake/dependencies/${_target}-prefix ) -# Note: On Windows, FFmpeg stores both import libs and DLLs in the install bin directory, so we copy _lib_dir (which is install/bin on Windows) to both stage -# dirs. -IF(RV_TARGET_WINDOWS) - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} EXTRA_LIB_DIRS ${RV_STAGE_BIN_DIR} USE_FLAG_FILE) -ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} USE_FLAG_FILE) -ENDIF() +SET(_ffmpeg_targets) +FOREACH( + _ffmpeg_lib + ${_ffmpeg_libs} +) + LIST(APPEND _ffmpeg_targets ffmpeg::${_ffmpeg_lib}) +ENDFOREACH() + +RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_ffmpeg_targets}) SET(RV_DEPS_FFMPEG_VERSION ${_version} diff --git a/cmake/dependencies/glew.cmake b/cmake/dependencies/glew.cmake index 501eccc2f..27693fb2b 100644 --- a/cmake/dependencies/glew.cmake +++ b/cmake/dependencies/glew.cmake @@ -48,5 +48,5 @@ ELSE() ENDIF() # Found path: use TARGET_LIBS to resolve actual library path at build time - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS GLEW::GLEW USE_FLAG_FILE) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS GLEW::GLEW) ENDIF() diff --git a/cmake/dependencies/ocio.cmake b/cmake/dependencies/ocio.cmake index 2aaf7419e..fe3b0558e 100644 --- a/cmake/dependencies/ocio.cmake +++ b/cmake/dependencies/ocio.cmake @@ -319,8 +319,6 @@ IF(RV_TARGET_WINDOWS) ENDIF() ENDIF() -RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} USE_FLAG_FILE) - RV_ADD_IMPORTED_LIBRARY( NAME OpenColorIO::OpenColorIO @@ -336,3 +334,5 @@ RV_ADD_IMPORTED_LIBRARY( ${_target} ADD_TO_DEPS_LIST ) + +RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS OpenColorIO::OpenColorIO) diff --git a/cmake/dependencies/openexr.cmake b/cmake/dependencies/openexr.cmake index 1dab72e3c..c7ec97d43 100644 --- a/cmake/dependencies/openexr.cmake +++ b/cmake/dependencies/openexr.cmake @@ -169,5 +169,5 @@ ELSE() LIST(APPEND _openexr_stage_targets OpenEXR::OpenEXRCore) ENDIF() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_openexr_stage_targets} USE_FLAG_FILE) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ${_openexr_stage_targets}) ENDIF() diff --git a/cmake/dependencies/openssl.cmake b/cmake/dependencies/openssl.cmake index f57b61b30..ff94a7c60 100644 --- a/cmake/dependencies/openssl.cmake +++ b/cmake/dependencies/openssl.cmake @@ -180,7 +180,6 @@ ELSE() TARGET_LIBS OpenSSL::Crypto OpenSSL::SSL - USE_FLAG_FILE ) ENDIF() diff --git a/cmake/dependencies/zlib.cmake b/cmake/dependencies/zlib.cmake index e7fe2c7a7..6cae31421 100644 --- a/cmake/dependencies/zlib.cmake +++ b/cmake/dependencies/zlib.cmake @@ -160,7 +160,7 @@ ELSE() ) ADD_DEPENDENCIES(dependencies ${_target}-stage-target) ELSE() - RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ZLIB::ZLIB USE_FLAG_FILE) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} TARGET_LIBS ZLIB::ZLIB) ENDIF() ENDIF() diff --git a/cmake/macros/rv_stage_dependency_libs.cmake b/cmake/macros/rv_stage_dependency_libs.cmake index 6d31c938c..c99128a8a 100644 --- a/cmake/macros/rv_stage_dependency_libs.cmake +++ b/cmake/macros/rv_stage_dependency_libs.cmake @@ -20,8 +20,10 @@ # [STAGE_LIB_DIR ] # Override RV_STAGE_LIB_DIR (e.g., for OpenSSL/Linux) # [EXTRA_LIB_DIRS ...] # Additional lib dirs to copy to # [FILES [file2...]] # Individual files to copy_if_different to STAGE_LIB_DIR (alternative to LIB_DIR) -# [TARGET_LIBS [t2...]] # Imported targets to stage via $ generatos (requires USE_FLAG_FILE). -# # Resolves actual library paths at build time — handles any naming convention. +# [TARGET_LIBS [t2...]] # Imported targets to stage via $ generators. +# # At configure time, resolves IMPORTED_LOCATION to derive output filenames +# # for proper incremental tracking (re-stages if files are deleted). +# # Falls back to USE_FLAG_FILE if location cannot be resolved. # # On Windows: DLL→STAGE_BIN_DIR, import lib→STAGE_LIB_DIR. # [DEPENDS [dep2...]] # Dependencies (default: ${TARGET}) # [PRE_COMMANDS COMMAND [COMMAND ...]] # Commands to run before copy; each must be prefixed with COMMAND keyword @@ -71,13 +73,6 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ENDIF() ENDIF() - # Validate TARGET_LIBS requires USE_FLAG_FILE (generators can't be used in OUTPUTS) - IF(_ARG_TARGET_LIBS - AND NOT _ARG_USE_FLAG_FILE - ) - MESSAGE(FATAL_ERROR "RV_STAGE_DEPENDENCY_LIBS: TARGET_LIBS requires USE_FLAG_FILE (generator expressions cannot be used in OUTPUTS)") - ENDIF() - # Default LIB_DIR to caller's _lib_dir if not explicitly passed IF(NOT _ARG_LIB_DIR AND NOT _ARG_FILES @@ -111,6 +106,68 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ) ENDIF() + # When TARGET_LIBS is used without explicit USE_FLAG_FILE or OUTPUTS, resolve IMPORTED_LOCATION at configure time to derive output filenames for proper + # incremental tracking. This way Ninja detects deleted staged files and re-runs the staging command automatically. + IF(_ARG_TARGET_LIBS + AND NOT _ARG_USE_FLAG_FILE + AND NOT _ARG_OUTPUTS + ) + SET(_rsdl_resolved_outputs) + SET(_rsdl_failed + FALSE + ) + STRING(TOUPPER "${CMAKE_BUILD_TYPE}" _rsdl_config_upper) + + FOREACH( + _rsdl_tgt + ${_ARG_TARGET_LIBS} + ) + RV_RESOLVE_IMPORTED_LOCATION(${_rsdl_tgt} _rsdl_loc) + + IF(_rsdl_loc) + GET_FILENAME_COMPONENT(_rsdl_fname "${_rsdl_loc}" NAME) + IF(RV_TARGET_WINDOWS) + LIST(APPEND _rsdl_resolved_outputs "${RV_STAGE_BIN_DIR}/${_rsdl_fname}") + SET(_rsdl_implib + "" + ) + FOREACH( + _rsdl_prop + IMPORTED_IMPLIB IMPORTED_IMPLIB_${_rsdl_config_upper} IMPORTED_IMPLIB_RELEASE IMPORTED_IMPLIB_NOCONFIG + ) + IF(NOT _rsdl_implib) + GET_TARGET_PROPERTY(_rsdl_implib ${_rsdl_tgt} ${_rsdl_prop}) + ENDIF() + ENDFOREACH() + IF(_rsdl_implib) + GET_FILENAME_COMPONENT(_rsdl_impfname "${_rsdl_implib}" NAME) + LIST(APPEND _rsdl_resolved_outputs "${_ARG_STAGE_LIB_DIR}/${_rsdl_impfname}") + ENDIF() + ELSE() + LIST(APPEND _rsdl_resolved_outputs "${_ARG_STAGE_LIB_DIR}/${_rsdl_fname}") + ENDIF() + ELSE() + SET(_rsdl_failed + TRUE + ) + MESSAGE(AUTHOR_WARNING "RV_STAGE_DEPENDENCY_LIBS: Cannot resolve IMPORTED_LOCATION for ${_rsdl_tgt}; falling back to flag file") + BREAK() + ENDIF() + ENDFOREACH() + + IF(NOT _rsdl_failed + AND _rsdl_resolved_outputs + ) + SET(_ARG_OUTPUTS + ${_rsdl_resolved_outputs} + ) + ELSE() + SET(_ARG_USE_FLAG_FILE + TRUE + ) + ENDIF() + ENDIF() + # Build the command list SET(_commands) From 0738c55868f8a5a026438536a2c3eaf38ebd441c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 20 Mar 2026 16:24:40 -0400 Subject: [PATCH 13/15] build: fix OCIO Windows staging and OIIO/OCIO Imath resolution on RHEL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix two build regressions introduced by the find_package dispatcher work: 1. OCIO Windows staging: RV_MAKE_STANDARD_LIB_NAME generates "OpenColorIO.dll" but OCIO builds "OpenColorIO_2.3.dll" (version- suffixed). Fix _libpath to match the actual DLL so TARGET_LIBS staging resolves the correct file. 2. OIIO/OCIO Imath_DIR: hardcoded lib/cmake/Imath is wrong on RHEL/Rocky Linux where GNUInstallDirs sets LIBDIR to lib64. Use RV_DEPS_IMATH_CMAKE_DIR (set in imath.cmake from _lib_dir) which correctly resolves to lib64/cmake/Imath on RHEL. The broken Imath_DIR caused find_dependency(Imath) inside OpenEXRConfig.cmake to fail, surfacing as "OpenEXR not found" in the OIIO sub-build. Signed-off-by: Cédrik Fuoco --- cmake/dependencies/ocio.cmake | 13 ++++++++++--- cmake/dependencies/oiio.cmake | 5 +++-- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmake/dependencies/ocio.cmake b/cmake/dependencies/ocio.cmake index fe3b0558e..333c76c00 100644 --- a/cmake/dependencies/ocio.cmake +++ b/cmake/dependencies/ocio.cmake @@ -52,6 +52,12 @@ IF(RV_TARGET_WINDOWS) ${_bin_dir}/${_ocio_win_sharedlibname} ) LIST(APPEND _byproducts ${_ocio_win_sharedlib_path}) + + # Fix _libpath to match the actual version-suffixed DLL name that OCIO produces. RV_MAKE_STANDARD_LIB_NAME generates "OpenColorIO.dll" but OCIO builds + # "OpenColorIO_2.3.dll". + SET(_libpath + ${_ocio_win_sharedlib_path} + ) ENDIF() IF(RV_TARGET_WINDOWS) @@ -138,8 +144,9 @@ IF(NOT RV_TARGET_WINDOWS) ENDIF() LIST(APPEND _configure_options "-DOCIO_PYTHON_VERSION=${RV_DEPS_PYTHON_VERSION_SHORT}") -# Use explicit Imath_DIR for precise config resolution. Works for both built-from-source and found (e.g. Homebrew) packages. -LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath") +# Use explicit Imath_DIR for precise config resolution. Works for both built-from-source and found (e.g. Homebrew) packages. Use RV_DEPS_IMATH_CMAKE_DIR which +# accounts for lib vs lib64 (RHEL) rather than hardcoding lib/. +LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_CMAKE_DIR}") LIST(APPEND _configure_options "-DZLIB_ROOT=${RV_DEPS_ZLIB_ROOT_DIR}") @@ -202,7 +209,7 @@ ELSE() # Windows "-DZLIB_LIBRARY=${_zlib_library}" "-DZLIB_INCLUDE_DIR=${_zlib_include_dir}" "-Dexpat_ROOT=${RV_DEPS_EXPAT_ROOT_DIR}" - "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath" + "-DImath_DIR=${RV_DEPS_IMATH_CMAKE_DIR}" "-DPython_ROOT=${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install" # Mandatory param: OCIO CMake code finds Python. "-DPython_LIBRARY=${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/bin/python${PYTHON_VERSION_SHORT_NO_DOT}.lib" # with this param diff --git a/cmake/dependencies/oiio.cmake b/cmake/dependencies/oiio.cmake index 055240335..1c03af7c8 100644 --- a/cmake/dependencies/oiio.cmake +++ b/cmake/dependencies/oiio.cmake @@ -52,10 +52,11 @@ LIST(APPEND _configure_options "-DUSE_OCIO=0") LIST(APPEND _configure_options "-DUSE_FREETYPE=0") LIST(APPEND _configure_options "-DUSE_GIF=OFF") -# Use explicit *_DIR variables pointing directly to config file directories for more precise package resolution. +# Use explicit *_DIR variables pointing directly to config file directories for more precise package resolution. Use RV_DEPS_*_CMAKE_DIR which accounts for lib +# vs lib64 (RHEL) rather than hardcoding lib/. LIST(APPEND _configure_options "-DBoost_DIR=${RV_DEPS_BOOST_ROOT_DIR}/lib/cmake/Boost-${RV_DEPS_BOOST_VERSION}") LIST(APPEND _configure_options "-DOpenEXR_ROOT=${RV_DEPS_OPENEXR_ROOT_DIR}") -LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_ROOT_DIR}/lib/cmake/Imath") +LIST(APPEND _configure_options "-DImath_DIR=${RV_DEPS_IMATH_CMAKE_DIR}") GET_TARGET_PROPERTY(_png_library PNG::PNG IMPORTED_LOCATION) GET_TARGET_PROPERTY(_png_include_dir PNG::PNG INTERFACE_INCLUDE_DIRECTORIES) From c112235076b874d729583c229e3ea5688a6436e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 20 Mar 2026 21:03:13 -0400 Subject: [PATCH 14/15] build: defer Darwin install_name_tool fixup to install time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all install_name_tool modifications and codesigning of staged libraries from build time to install time. This prevents Homebrew library signatures from being invalidated during the build, which causes SIGKILL on arm64 macOS and EPERM from install_name_tool on macOS 26+. Build-time changes: - Remove install_name_tool -id and codesign from TARGET_LIBS staging - Remove install_name_tool -change POST_BUILD on executables/plugins - Replace install_name_tool -add_rpath POST_BUILD with linker flags (TARGET_LINK_OPTIONS) to avoid post-link binary modification - Remove RV_RESOLVE_DARWIN_INSTALL_NAME infrastructure (no longer needed) - Remove install_name_tool -id from zlib found/built staging paths - Add SONAME symlink creation for staged libraries whose install name differs from their filename (e.g. libFoo.2.3.dylib -> libFoo.2.3.2.dylib) Install-time changes (remove_absolute_rpath.py): - Add library self-ID fixup (install_name_tool -id @rpath/) - Broaden reference matching to convert all non-system absolute paths to @rpath (not just paths under source root) - Add is_system_library() to preserve /usr/lib/ and /System/ refs - Fix pre-existing bug: file._str.find() -> file.find() Install-time changes (pre_install_darwin.cmake): - Add ad-hoc codesign pass after rpath fixup for OpenRV - Commercial RV replaces these via garasign in a separate pipeline stage Signed-off-by: Cédrik Fuoco --- cmake/dependencies/zlib.cmake | 20 ++------ cmake/install/pre_install_darwin.cmake | 26 +++++++++- cmake/macros/rv_create_std_deps_vars.cmake | 34 ------------- cmake/macros/rv_find_dependency.cmake | 1 - cmake/macros/rv_stage.cmake | 56 ++------------------- cmake/macros/rv_stage_dependency_libs.cmake | 35 +++---------- cmake/scripts/create_soname_symlink.cmake | 56 +++++++++++++++++++++ src/build/remove_absolute_rpath.py | 46 ++++++++++++++++- 8 files changed, 142 insertions(+), 132 deletions(-) create mode 100644 cmake/scripts/create_soname_symlink.cmake diff --git a/cmake/dependencies/zlib.cmake b/cmake/dependencies/zlib.cmake index 6cae31421..4685c65b3 100644 --- a/cmake/dependencies/zlib.cmake +++ b/cmake/dependencies/zlib.cmake @@ -77,18 +77,7 @@ IF(NOT ${_target}_FOUND) IF(RV_TARGET_WINDOWS) RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} BIN_DIR ${_bin_dir} OUTPUTS ${RV_STAGE_BIN_DIR}/${_libname}) ELSEIF(RV_TARGET_DARWIN) - RV_STAGE_DEPENDENCY_LIBS( - TARGET - ${_target} - OUTPUTS - ${RV_STAGE_LIB_DIR}/${_libname} - PRE_COMMANDS - COMMAND - ${CMAKE_INSTALL_NAME_TOOL} - -id - "@rpath/${_libname}" - "${_lib_dir}/${_libname}" - ) + RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_libname}) ELSE() RV_STAGE_DEPENDENCY_LIBS(TARGET ${_target} OUTPUTS ${RV_STAGE_LIB_DIR}/${_libname}) ENDIF() @@ -111,8 +100,8 @@ ELSE() ENDIF() IF(RV_TARGET_DARWIN) - # Found zlib may carry a versioned install name (e.g. libz.1.dylib from Homebrew, libz.1.3.1.dylib from MacPorts). Stage with the actual SONAME, rewrite the - # install name to @rpath, and create an unversioned symlink when needed. + # Found zlib may carry a versioned install name (e.g. libz.1.dylib from Homebrew, libz.1.3.1.dylib from MacPorts). Stage with the actual SONAME and create an + # unversioned symlink when needed. The install name rewrite to @rpath is deferred to install time (remove_absolute_rpath.py). # # Resolve the actual library path, then fall back to ZLIB_LIBRARIES (always set by FindZLIB.cmake). RV_RESOLVE_IMPORTED_LOCATION(ZLIB::ZLIB _zlib_realpath) @@ -150,8 +139,7 @@ ELSE() COMMENT "Staging ${_target}: ${_zlib_soname} -> ${RV_STAGE_LIB_DIR}" OUTPUT ${RV_STAGE_LIB_DIR}/${_zlib_soname} COMMAND ${CMAKE_COMMAND} -E make_directory ${RV_STAGE_LIB_DIR} - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_zlib_realpath}" "${RV_STAGE_LIB_DIR}/${_zlib_soname}" - COMMAND ${CMAKE_INSTALL_NAME_TOOL} -id "@rpath/${_zlib_soname}" "${RV_STAGE_LIB_DIR}/${_zlib_soname}" ${_zlib_symlink_cmd} + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${_zlib_realpath}" "${RV_STAGE_LIB_DIR}/${_zlib_soname}" ${_zlib_symlink_cmd} DEPENDS ${_target} ) ADD_CUSTOM_TARGET( diff --git a/cmake/install/pre_install_darwin.cmake b/cmake/install/pre_install_darwin.cmake index fe22f0970..19e63a26e 100644 --- a/cmake/install/pre_install_darwin.cmake +++ b/cmake/install/pre_install_darwin.cmake @@ -91,9 +91,33 @@ MACRO(post_install_platform) ${FILES_TO_FIX_RPATH} ) - MESSAGE(STATUS "python3 ${CMAKE_CURRENT_LIST_DIR}/../../src/build/remove_absolute_rpath.py --files-list ${_output_file_to_fix_} --root ${CMAKE_SOURCE_DIR}") + MESSAGE(STATUS "Fixing rpaths: python3 ${CMAKE_CURRENT_LIST_DIR}/../../src/build/remove_absolute_rpath.py --files-list ${_output_file_to_fix_} --root ${CMAKE_SOURCE_DIR}") EXECUTE_PROCESS( COMMAND python3 ${CMAKE_CURRENT_LIST_DIR}/../../src/build/remove_absolute_rpath.py --files-list ${_output_file_to_fix_} --root ${CMAKE_SOURCE_DIR} COMMAND_ERROR_IS_FATAL ANY ) + + # Ad-hoc codesign all Mach-O binaries. install_name_tool modifications above invalidate any existing signatures. + MESSAGE(STATUS "Ad-hoc codesigning installed binaries...") + FILE(STRINGS ${_output_file_to_fix_} _files_to_sign) + FOREACH(_file_to_sign ${_files_to_sign}) + STRING(STRIP "${_file_to_sign}" _file_to_sign) + IF(_file_to_sign AND EXISTS "${_file_to_sign}") + EXECUTE_PROCESS( + COMMAND file -bh "${_file_to_sign}" + OUTPUT_VARIABLE _file_type + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + IF(_file_type MATCHES "^Mach-O") + EXECUTE_PROCESS( + COMMAND codesign --force --sign - "${_file_to_sign}" + RESULT_VARIABLE _codesign_result + ) + IF(NOT _codesign_result EQUAL 0) + MESSAGE(WARNING "Failed to codesign ${_file_to_sign}") + ENDIF() + ENDIF() + ENDIF() + ENDFOREACH() + MESSAGE(STATUS "Ad-hoc codesigning complete.") ENDMACRO() diff --git a/cmake/macros/rv_create_std_deps_vars.cmake b/cmake/macros/rv_create_std_deps_vars.cmake index 10de1e323..5d2c31a3d 100644 --- a/cmake/macros/rv_create_std_deps_vars.cmake +++ b/cmake/macros/rv_create_std_deps_vars.cmake @@ -197,40 +197,6 @@ MACRO(RV_RESOLVE_IMPORTED_LOCATION _rril_target _rril_out_var) ENDIF() ENDMACRO() -# -# RV_RESOLVE_DARWIN_INSTALL_NAME — Get a macOS dylib's actual install name (LC_ID_DYLIB) -# -# On macOS, the install name recorded in a binary by the linker comes from the library's LC_ID_DYLIB, which may differ from the file path (e.g. Homebrew symlink -# paths). This macro resolves the real install name via otool -D and caches the result as a target property (RV_DARWIN_INSTALL_NAME) for later use in rpath -# fixup. -# -# For built-from-source deps where the library doesn't exist at configure time, this is a no-op. -# -MACRO(RV_RESOLVE_DARWIN_INSTALL_NAME _rdain_target) - IF(RV_TARGET_DARWIN - AND TARGET ${_rdain_target} - ) - RV_RESOLVE_IMPORTED_LOCATION(${_rdain_target} _rdain_loc) - IF(_rdain_loc - AND EXISTS "${_rdain_loc}" - ) - EXECUTE_PROCESS( - COMMAND otool -D "${_rdain_loc}" - OUTPUT_VARIABLE _rdain_otool_out - OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET - ) - # otool -D output: first line is file path, second line is the install name - STRING(REGEX MATCH "[^\n]+$" _rdain_install_name "${_rdain_otool_out}") - IF(_rdain_install_name) - SET_PROPERTY( - TARGET ${_rdain_target} - PROPERTY RV_DARWIN_INSTALL_NAME "${_rdain_install_name}" - ) - ENDIF() - ENDIF() - ENDIF() -ENDMACRO() - # # RV_MAKE_TARGETS_GLOBAL — Promote imported targets to GLOBAL visibility # diff --git a/cmake/macros/rv_find_dependency.cmake b/cmake/macros/rv_find_dependency.cmake index 95faf75e2..c42182be6 100644 --- a/cmake/macros/rv_find_dependency.cmake +++ b/cmake/macros/rv_find_dependency.cmake @@ -212,7 +212,6 @@ MACRO(RV_FIND_DEPENDENCY) ${_RFD_DEPS_LIST_TARGETS} ) LIST(APPEND RV_DEPS_LIST ${_rfd_dep}) - RV_RESOLVE_DARWIN_INSTALL_NAME(${_rfd_dep}) ENDFOREACH() STRING(TOUPPER ${_RFD_PACKAGE} _RFD_PKG_UPPER) diff --git a/cmake/macros/rv_stage.cmake b/cmake/macros/rv_stage.cmake index 90902571a..15a168ffc 100644 --- a/cmake/macros/rv_stage.cmake +++ b/cmake/macros/rv_stage.cmake @@ -82,57 +82,11 @@ FUNCTION(rv_stage) IF(TARGET ${arg_TARGET}) GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) IF(_native_target_type STREQUAL "EXECUTABLE") - ADD_CUSTOM_COMMAND( - COMMENT "Fixing ${arg_TARGET}'s RPATHs" TARGET ${arg_TARGET} POST_BUILD - COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks" "$" - COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../lib" "$" - ) - ENDIF() - - IF(_native_target_type STREQUAL "EXECUTABLE" - OR _native_target_type STREQUAL "SHARED_LIBRARY" - ) - # Batch all -change arguments into a single install_name_tool invocation per target. Multiple separate invocations each re-sign the binary, which can - # trigger macOS code signing lockouts ("Operation not permitted") on arm64. - SET(_change_args) - FOREACH( - dep - ${RV_DEPS_LIST} - ) - IF(TARGET ${dep}) - GET_PROPERTY( - dep_file_path - TARGET ${dep} - PROPERTY LOCATION - ) - # For found packages (e.g. Homebrew), the install name recorded by the linker may differ from the target's LOCATION (different symlink paths). Use - # the cached install name from RV_RESOLVE_DARWIN_INSTALL_NAME if available; this is a no-op for built-from-source deps where the library doesn't - # exist at configure time. - GET_PROPERTY( - _dep_install_name - TARGET ${dep} - PROPERTY RV_DARWIN_INSTALL_NAME - ) - IF(_dep_install_name) - SET(dep_change_path - "${_dep_install_name}" - ) - GET_FILENAME_COMPONENT(dep_file_name "${_dep_install_name}" NAME) - ELSE() - SET(dep_change_path - "${dep_file_path}" - ) - GET_FILENAME_COMPONENT(dep_file_name ${dep_file_path} NAME) - ENDIF() - LIST(APPEND _change_args -change "${dep_change_path}" "@rpath/${dep_file_name}") - ENDIF() - ENDFOREACH() - IF(_change_args) - ADD_CUSTOM_COMMAND( - COMMENT "Fixing dependency rpaths in ${arg_TARGET}" TARGET ${arg_TARGET} POST_BUILD - COMMAND ${CMAKE_INSTALL_NAME_TOOL} ${_change_args} "$" - ) - ENDIF() + # Add rpaths via linker flags instead of POST_BUILD install_name_tool. + # CMAKE_SKIP_RPATH is ON so CMake's rpath machinery is disabled; + # direct linker flags bypass it. This avoids modifying the auto-signed + # binary after linking (which fails on arm64 macOS 26+). + TARGET_LINK_OPTIONS(${arg_TARGET} PRIVATE "LINKER:-rpath,@executable_path/../Frameworks" "LINKER:-rpath,@executable_path/../lib") ENDIF() ENDIF() ENDIF() diff --git a/cmake/macros/rv_stage_dependency_libs.cmake b/cmake/macros/rv_stage_dependency_libs.cmake index c99128a8a..0f3022878 100644 --- a/cmake/macros/rv_stage_dependency_libs.cmake +++ b/cmake/macros/rv_stage_dependency_libs.cmake @@ -282,41 +282,22 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) $ ${_ARG_STAGE_LIB_DIR}/ ) - ENDIF() - # On macOS, fix the staged copy's install name to @rpath so it can be resolved via rpath at runtime. For found packages (e.g. Homebrew) the install name - # is an absolute path; for built-from-source it's typically already @rpath but -id is idempotent. - IF(RV_TARGET_DARWIN) - GET_PROPERTY( - _rsdl_install_name - TARGET ${_tgt} - PROPERTY RV_DARWIN_INSTALL_NAME - ) - IF(_rsdl_install_name) - GET_FILENAME_COMPONENT(_rsdl_install_fname "${_rsdl_install_name}" NAME) - LIST( - APPEND - _commands - COMMAND - ${CMAKE_INSTALL_NAME_TOOL} - -id - "@rpath/${_rsdl_install_fname}" - "${_ARG_STAGE_LIB_DIR}/$" - ) - # Re-sign with ad-hoc after modifying the install name. Homebrew libraries carry a Homebrew signature that install_name_tool invalidates; on arm64 - # macOS loading a library with an invalid signature causes SIGKILL. + # On macOS, create a SONAME symlink if the library's install name differs from its filename (e.g. libFoo.2.3.dylib -> libFoo.2.3.2.dylib). The linker + # records the install name in binaries that link against this library, so dyld needs a file matching that name at runtime. + IF(RV_TARGET_DARWIN) LIST( APPEND _commands COMMAND - codesign - --force - --sign - - - "${_ARG_STAGE_LIB_DIR}/$" + ${CMAKE_COMMAND} + -DLIB_FILE=${_ARG_STAGE_LIB_DIR}/$ + -P + ${PROJECT_SOURCE_DIR}/cmake/scripts/create_soname_symlink.cmake ) ENDIF() ENDIF() + ENDFOREACH() ENDIF() diff --git a/cmake/scripts/create_soname_symlink.cmake b/cmake/scripts/create_soname_symlink.cmake new file mode 100644 index 000000000..6f7bf1b93 --- /dev/null +++ b/cmake/scripts/create_soname_symlink.cmake @@ -0,0 +1,56 @@ +# +# Copyright (C) 2026 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# create_soname_symlink.cmake — Create a SONAME symlink for a staged macOS dylib +# +# On macOS, a shared library's install name (LC_ID_DYLIB) may differ from its filename +# (e.g. libOpenColorIO.2.3.dylib vs libOpenColorIO.2.3.2.dylib). The linker records the +# install name in binaries that link against the library, so at runtime dyld looks for +# a file matching the install name. This script creates a symlink from the install name +# basename to the actual file if they differ. +# +# Usage: cmake -DLIB_FILE=/path/to/staged/libFoo.1.2.3.dylib -P create_soname_symlink.cmake +# + +IF(NOT DEFINED LIB_FILE) + MESSAGE(FATAL_ERROR "LIB_FILE is required") +ENDIF() + +IF(NOT EXISTS "${LIB_FILE}") + MESSAGE(FATAL_ERROR "LIB_FILE does not exist: ${LIB_FILE}") +ENDIF() + +EXECUTE_PROCESS( + COMMAND otool -D "${LIB_FILE}" + OUTPUT_VARIABLE _otool_out + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _otool_rc +) + +IF(NOT _otool_rc EQUAL 0) + RETURN() +ENDIF() + +# otool -D output: first line is the file path, second line is the install name +STRING(REGEX MATCH "[^\n]+$" _install_name "${_otool_out}") + +IF(NOT _install_name) + RETURN() +ENDIF() + +GET_FILENAME_COMPONENT(_install_basename "${_install_name}" NAME) +GET_FILENAME_COMPONENT(_file_basename "${LIB_FILE}" NAME) +GET_FILENAME_COMPONENT(_file_dir "${LIB_FILE}" DIRECTORY) + +IF(NOT "${_install_basename}" STREQUAL "${_file_basename}") + SET(_symlink_path "${_file_dir}/${_install_basename}") + IF(NOT EXISTS "${_symlink_path}") + MESSAGE(STATUS " Creating SONAME symlink: ${_install_basename} -> ${_file_basename}") + FILE(CREATE_LINK "${_file_basename}" "${_symlink_path}" SYMBOLIC) + ENDIF() +ENDIF() diff --git a/src/build/remove_absolute_rpath.py b/src/build/remove_absolute_rpath.py index a34e7062c..379924310 100755 --- a/src/build/remove_absolute_rpath.py +++ b/src/build/remove_absolute_rpath.py @@ -103,6 +103,42 @@ def change_shared_library_path(object_file_path, old_library_path): return new_library_path +def get_install_name(object_file_path): + """Get a dylib's own install name (LC_ID_DYLIB) via otool -D. + Returns None for executables or if no install name is found.""" + try: + otool_output = subprocess.check_output( + ["otool", "-D", object_file_path], stderr=subprocess.DEVNULL + ).decode().splitlines() + except subprocess.CalledProcessError: + return None + # otool -D output: first line is the file path, second line is the install name + if len(otool_output) >= 2: + return otool_output[1].strip() + return None + + +def change_install_name_id(object_file_path, old_id): + new_id = f"@rpath/{os.path.basename(old_id)}" + command = [ + "install_name_tool", + "-id", + new_id, + object_file_path, + ] + subprocess.run(command).check_returncode() + return new_id + + +# System library prefixes that must keep their absolute paths. +# These are Apple-provided frameworks/dylibs that are always present at these paths. +SYSTEM_LIBRARY_PREFIXES = ("/usr/lib/", "/System/") + + +def is_system_library(library_path): + return any(library_path.startswith(p) for p in SYSTEM_LIBRARY_PREFIXES) + + def fix_rpath(target, root): for file in get_object_files(target): logging.info(f"Fixing rpaths for {file}") @@ -111,7 +147,7 @@ def fix_rpath(target, root): # become invalid due to our rpath fixing logic. # IMPORTANT: # Numpy was the only issue found, but it could happend again if new dependencies are added. - is_numpy = file._str.find("numpy") + is_numpy = file.find("numpy") if is_numpy > -1: logging.info(f"Skipping {file} because numpy") return @@ -128,13 +164,19 @@ def fix_rpath(target, root): logging.info(output) + # Fix library self-ID: for dylibs with absolute install names, rewrite to @rpath/ + install_name = get_install_name(file) + if install_name and not install_name.startswith("@") and not is_system_library(install_name): + new_id = change_install_name_id(file, install_name) + logging.info(f"\tinstall name: {install_name} (Changed ID to {new_id})") + logging.info(f"Fixing shared library paths for {file}") for library_path in get_shared_library_paths(file): output = f"\tlibrary path: {library_path}" shared_library_name = os.path.basename(library_path) - if library_path.startswith(root) or library_path == shared_library_name: + if (library_path.startswith("/") and not is_system_library(library_path)) or library_path == shared_library_name: new_library_path = change_shared_library_path(file, library_path) output += f" (Changed to {new_library_path})" From 928974729d49a784bb0587ab34d804aa20b5feaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Fri, 20 Mar 2026 21:40:28 -0400 Subject: [PATCH 15/15] build: create SONAME symlinks for staged libraries on Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The TARGET_LIBS staging path only created SONAME symlinks on macOS (via otool -D). On Linux, versioned libraries like libOpenColorIO.so.2.3.2 were staged without the SONAME symlink (libOpenColorIO.so.2.3), causing all LoadingSharedLibrariesTest tests to fail with 'cannot open shared object file'. Extend create_soname_symlink.cmake to support Linux by using readelf -d to extract the ELF SONAME, and invoke the script on Linux in addition to macOS. Platform is passed via -DRV_TARGET_* flags to the cmake -P invocation. Signed-off-by: Cédrik Fuoco --- cmake/macros/rv_stage_dependency_libs.cmake | 8 +- cmake/scripts/create_soname_symlink.cmake | 81 +++++++++++++++------ 2 files changed, 63 insertions(+), 26 deletions(-) diff --git a/cmake/macros/rv_stage_dependency_libs.cmake b/cmake/macros/rv_stage_dependency_libs.cmake index 0f3022878..185383030 100644 --- a/cmake/macros/rv_stage_dependency_libs.cmake +++ b/cmake/macros/rv_stage_dependency_libs.cmake @@ -283,15 +283,17 @@ FUNCTION(RV_STAGE_DEPENDENCY_LIBS) ${_ARG_STAGE_LIB_DIR}/ ) - # On macOS, create a SONAME symlink if the library's install name differs from its filename (e.g. libFoo.2.3.dylib -> libFoo.2.3.2.dylib). The linker - # records the install name in binaries that link against this library, so dyld needs a file matching that name at runtime. - IF(RV_TARGET_DARWIN) + # Create a SONAME symlink if the library's recorded name differs from its filename (e.g. libFoo.2.3.dylib -> libFoo.2.3.2.dylib on macOS, + # libFoo.so.2.3 -> libFoo.so.2.3.2 on Linux). The linker records this name in dependent binaries, so the runtime loader needs a file matching it. + IF(RV_TARGET_DARWIN OR RV_TARGET_LINUX) LIST( APPEND _commands COMMAND ${CMAKE_COMMAND} -DLIB_FILE=${_ARG_STAGE_LIB_DIR}/$ + -DRV_TARGET_DARWIN=${RV_TARGET_DARWIN} + -DRV_TARGET_LINUX=${RV_TARGET_LINUX} -P ${PROJECT_SOURCE_DIR}/cmake/scripts/create_soname_symlink.cmake ) diff --git a/cmake/scripts/create_soname_symlink.cmake b/cmake/scripts/create_soname_symlink.cmake index 6f7bf1b93..af5ae9239 100644 --- a/cmake/scripts/create_soname_symlink.cmake +++ b/cmake/scripts/create_soname_symlink.cmake @@ -5,15 +5,17 @@ # # -# create_soname_symlink.cmake — Create a SONAME symlink for a staged macOS dylib +# create_soname_symlink.cmake — Create a SONAME symlink for a staged shared library # # On macOS, a shared library's install name (LC_ID_DYLIB) may differ from its filename -# (e.g. libOpenColorIO.2.3.dylib vs libOpenColorIO.2.3.2.dylib). The linker records the -# install name in binaries that link against the library, so at runtime dyld looks for -# a file matching the install name. This script creates a symlink from the install name -# basename to the actual file if they differ. +# (e.g. libOpenColorIO.2.3.dylib vs libOpenColorIO.2.3.2.dylib). On Linux, the ELF SONAME +# may differ similarly (e.g. libOpenColorIO.so.2.3 vs libOpenColorIO.so.2.3.2). The linker +# records this name in dependent binaries, so the runtime loader needs a file matching it. +# This script creates a symlink from the recorded name to the actual file if they differ. # -# Usage: cmake -DLIB_FILE=/path/to/staged/libFoo.1.2.3.dylib -P create_soname_symlink.cmake +# Usage: +# cmake -DLIB_FILE=/path/to/staged/libFoo.1.2.3.dylib -DRV_TARGET_DARWIN=TRUE -P create_soname_symlink.cmake +# cmake -DLIB_FILE=/path/to/staged/libFoo.so.1.2.3 -DRV_TARGET_LINUX=TRUE -P create_soname_symlink.cmake # IF(NOT DEFINED LIB_FILE) @@ -24,33 +26,66 @@ IF(NOT EXISTS "${LIB_FILE}") MESSAGE(FATAL_ERROR "LIB_FILE does not exist: ${LIB_FILE}") ENDIF() -EXECUTE_PROCESS( - COMMAND otool -D "${LIB_FILE}" - OUTPUT_VARIABLE _otool_out - OUTPUT_STRIP_TRAILING_WHITESPACE - ERROR_QUIET - RESULT_VARIABLE _otool_rc -) +# Extract the library's recorded name using platform-specific tooling. +SET(_soname_basename "") -IF(NOT _otool_rc EQUAL 0) - RETURN() -ENDIF() +IF(RV_TARGET_DARWIN) + EXECUTE_PROCESS( + COMMAND otool -D "${LIB_FILE}" + OUTPUT_VARIABLE _otool_out + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _otool_rc + ) + + IF(NOT _otool_rc EQUAL 0) + RETURN() + ENDIF() + + # otool -D output: first line is the file path, second line is the install name + STRING(REGEX MATCH "[^\n]+$" _install_name "${_otool_out}") + + IF(NOT _install_name) + RETURN() + ENDIF() + + GET_FILENAME_COMPONENT(_soname_basename "${_install_name}" NAME) + +ELSEIF(RV_TARGET_LINUX) + EXECUTE_PROCESS( + COMMAND readelf -d "${LIB_FILE}" + OUTPUT_VARIABLE _readelf_out + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _readelf_rc + ) + + IF(NOT _readelf_rc EQUAL 0) + RETURN() + ENDIF() + + # readelf -d output contains a line like: + # 0x000000000000000e (SONAME) Library soname: [libOpenColorIO.so.2.3] + STRING(REGEX MATCH "\\(SONAME\\)[^\n]*\\[([^\n]+)\\]" _soname_match "${_readelf_out}") + + IF(NOT CMAKE_MATCH_1) + RETURN() + ENDIF() -# otool -D output: first line is the file path, second line is the install name -STRING(REGEX MATCH "[^\n]+$" _install_name "${_otool_out}") + SET(_soname_basename "${CMAKE_MATCH_1}") -IF(NOT _install_name) +ELSE() + # Unsupported platform — nothing to do. RETURN() ENDIF() -GET_FILENAME_COMPONENT(_install_basename "${_install_name}" NAME) GET_FILENAME_COMPONENT(_file_basename "${LIB_FILE}" NAME) GET_FILENAME_COMPONENT(_file_dir "${LIB_FILE}" DIRECTORY) -IF(NOT "${_install_basename}" STREQUAL "${_file_basename}") - SET(_symlink_path "${_file_dir}/${_install_basename}") +IF(NOT "${_soname_basename}" STREQUAL "${_file_basename}") + SET(_symlink_path "${_file_dir}/${_soname_basename}") IF(NOT EXISTS "${_symlink_path}") - MESSAGE(STATUS " Creating SONAME symlink: ${_install_basename} -> ${_file_basename}") + MESSAGE(STATUS " Creating SONAME symlink: ${_soname_basename} -> ${_file_basename}") FILE(CREATE_LINK "${_file_basename}" "${_symlink_path}" SYMBOLIC) ENDIF() ENDIF()