From 09abef62ae278b52a769cd3c1efe12d1b01e7177 Mon Sep 17 00:00:00 2001 From: Charlie Savage Date: Sat, 20 Dec 2025 21:53:02 -0800 Subject: [PATCH] FindRuby: Add support for interpreter and development components --- Help/release/dev/FindRuby-components.rst | 4 + Modules/FindRuby.cmake | 155 +++++++++++++++++++++-- Tests/FindRuby/Test/CMakeLists.txt | 31 ++++- 3 files changed, 173 insertions(+), 17 deletions(-) create mode 100644 Help/release/dev/FindRuby-components.rst diff --git a/Help/release/dev/FindRuby-components.rst b/Help/release/dev/FindRuby-components.rst new file mode 100644 index 0000000000..b88498a122 --- /dev/null +++ b/Help/release/dev/FindRuby-components.rst @@ -0,0 +1,4 @@ +FindRuby-components +------------------- + +* The :module:`FindRuby` module now provides imported targets. diff --git a/Modules/FindRuby.cmake b/Modules/FindRuby.cmake index 1cbeb69f25..a26bf9da73 100644 --- a/Modules/FindRuby.cmake +++ b/Modules/FindRuby.cmake @@ -9,12 +9,50 @@ Finds Ruby installation and the locations of its include files and libraries: .. code-block:: cmake - find_package(Ruby [] [...]) + find_package(Ruby [] [COMPONENTS ...] [...]) Ruby is a general-purpose programming language. This module supports Ruby -1.8 through 3.4. Virtual environments, such as RVM or RBENV, are also +2.0 through 4.0. Virtual environments, such as RVM or RBENV, are also supported. +Components +^^^^^^^^^^ + +.. versionadded:: 4.3 + +This module supports the following components: + +``Interpreter`` + The Ruby interpreter executable. + +``Development`` + Headers and libraries needed to build Ruby extensions or embed Ruby. + +If no components are specified, both ``Interpreter`` and ``Development`` +are searched for. + +Imported Targets +^^^^^^^^^^^^^^^^ + +.. versionadded:: 4.3 + +This module defines the following :prop_tgt:`IMPORTED` targets: + +``Ruby::Interpreter`` + Ruby interpreter. Target defined if component ``Interpreter`` is found. + +``Ruby::Ruby`` + Ruby library for embedding Ruby in C/C++ applications. + Target defined if component ``Development`` is found. + +``Ruby::Module`` + Ruby library for building Ruby extension modules. + Target defined if component ``Development`` is found. + Use this target when creating native extensions that will be + loaded into Ruby via ``require``. On most platforms, extension + modules do not link directly to libruby. Includes appropriate + symbol visibility settings. + Result Variables ^^^^^^^^^^^^^^^^ @@ -427,9 +465,6 @@ if (Ruby_VERSION_MAJOR) set(_Ruby_VERSION_SHORT_NODOT "${Ruby_VERSION_MAJOR}${Ruby_VERSION_MINOR}") endif () -# FIXME: Currently we require both the interpreter and development components to be found -# in order to use either. See issue #20474. - # Save CMAKE_FIND_FRAMEWORK set(_Ruby_CMAKE_FIND_FRAMEWORK_ORIGINAL ${CMAKE_FIND_FRAMEWORK}) @@ -450,11 +485,6 @@ find_library(Ruby_LIBRARY NAMES "${_Ruby_SO_NAME}" HINTS ${_Ruby_POSSIBLE_LIB_DIR}) -set(_Ruby_REQUIRED_VARS Ruby_EXECUTABLE Ruby_INCLUDE_DIR Ruby_LIBRARY) -if (_Ruby_VERSION_SHORT_NODOT GREATER 18) - list(APPEND _Ruby_REQUIRED_VARS Ruby_CONFIG_INCLUDE_DIR) -endif () - # Restore CMAKE_FIND_FRAMEWORK set(CMAKE_FIND_FRAMEWORK ${_Ruby_CMAKE_FIND_FRAMEWORK_ORIGINAL}) @@ -475,12 +505,111 @@ message(DEBUG "Ruby_ARCH_DIR: ${Ruby_ARCH_DIR}") message(DEBUG "Ruby_ARCHHDR_DIR: ${Ruby_ARCHHDR_DIR}") message(DEBUG "--------------------") +# Components +# +# If the caller does not request components, preserve legacy behavior +if (NOT Ruby_FIND_COMPONENTS) + set(Ruby_FIND_COMPONENTS Interpreter Development) +endif () + +set(_Ruby_WANT_INTERPRETER FALSE) +set(_Ruby_WANT_DEVELOPMENT FALSE) +set(_Ruby_REQUIRED_VARS "") + +foreach (component IN LISTS Ruby_FIND_COMPONENTS) + if (component STREQUAL "Interpreter") + set(_Ruby_WANT_INTERPRETER TRUE) + list(APPEND _Ruby_REQUIRED_VARS Ruby_EXECUTABLE) + elseif (component STREQUAL "Development") + set(_Ruby_WANT_DEVELOPMENT TRUE) + list(APPEND _Ruby_REQUIRED_VARS Ruby_INCLUDE_DIR Ruby_CONFIG_INCLUDE_DIR) + if (WIN32) + list(APPEND _Ruby_REQUIRED_VARS Ruby_LIBRARY) + endif () + else () + message(FATAL_ERROR + "FindRuby: Unsupported component '${component}'. Supported components are: Interpreter, Development") + endif () +endforeach () + +# Set component found flags +if (Ruby_EXECUTABLE) + set(Ruby_Interpreter_FOUND TRUE) +else () + set(Ruby_Interpreter_FOUND FALSE) +endif () + +if (Ruby_INCLUDE_DIR AND Ruby_CONFIG_INCLUDE_DIR AND (Ruby_LIBRARY OR NOT WIN32)) + set(Ruby_Development_FOUND TRUE) +else () + set(Ruby_Development_FOUND FALSE) +endif () + include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Ruby REQUIRED_VARS ${_Ruby_REQUIRED_VARS} - VERSION_VAR Ruby_VERSION) +find_package_handle_standard_args(Ruby + REQUIRED_VARS ${_Ruby_REQUIRED_VARS} + VERSION_VAR Ruby_VERSION + HANDLE_COMPONENTS) if (Ruby_FOUND) - set(Ruby_LIBRARIES ${Ruby_LIBRARY}) + if (NOT TARGET Ruby::Interpreter) + add_executable(Ruby::Interpreter IMPORTED GLOBAL) + set_target_properties(Ruby::Interpreter PROPERTIES + IMPORTED_LOCATION "${Ruby_EXECUTABLE}" + ) + endif () + + if (Ruby_Development_FOUND) + set(Ruby_LIBRARIES ${Ruby_LIBRARY}) + + if (Ruby_LIBRARY AND NOT TARGET Ruby::Ruby) + add_library(Ruby::Ruby UNKNOWN IMPORTED) + set_target_properties(Ruby::Ruby PROPERTIES + IMPORTED_LOCATION "${Ruby_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${Ruby_INCLUDE_DIRS}" + # Custom property for extension suffix (with dot), e.g. ".so", ".bundle" + INTERFACE_RUBY_EXTENSION_SUFFIX ".${_Ruby_DLEXT}" + ) + endif () + + # Ruby::Module - For building Ruby extension modules + if (NOT TARGET Ruby::Module) + if (WIN32) + add_library(Ruby::Module UNKNOWN IMPORTED) + set_target_properties(Ruby::Module PROPERTIES + IMPORTED_LOCATION "${Ruby_LIBRARY}" + ) + else () + add_library(Ruby::Module INTERFACE IMPORTED) + endif () + + set_target_properties(Ruby::Module PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${Ruby_INCLUDE_DIRS}" + # Custom property for extension suffix (with dot), e.g. ".so", ".bundle" + INTERFACE_RUBY_EXTENSION_SUFFIX ".${_Ruby_DLEXT}" + INTERFACE_C_VISIBILITY_PRESET hidden + INTERFACE_CXX_VISIBILITY_PRESET hidden + INTERFACE_VISIBILITY_INLINES_HIDDEN ON + ) + + # macOS: allow unresolved Ruby API symbols; resolved when Ruby loads the bundle. + if (APPLE) + target_link_options(Ruby::Module INTERFACE + "LINKER:-undefined,dynamic_lookup" + ) + endif () + + # Linux (and other ELF platforms): + # Normally undefined Ruby API symbols are allowed in shared objects and resolved at dlopen(). + # But if the toolchain/preset adds -Wl,--no-undefined, linking will fail. + # This counteracts that. + if (UNIX AND NOT APPLE) + target_link_options(Ruby::Module INTERFACE + "LINKER:--unresolved-symbols=ignore-all" + ) + endif () + endif () + endif () endif () mark_as_advanced( diff --git a/Tests/FindRuby/Test/CMakeLists.txt b/Tests/FindRuby/Test/CMakeLists.txt index fb5ae37c1d..d2f166c76b 100644 --- a/Tests/FindRuby/Test/CMakeLists.txt +++ b/Tests/FindRuby/Test/CMakeLists.txt @@ -9,6 +9,14 @@ if (NOT Ruby_FOUND) message (FATAL_ERROR "Failed to find Ruby >=1.9.9") endif() +if (NOT Ruby_Interpreter_FOUND) + message (FATAL_ERROR "Failed to find Ruby 'Interpreter' component") +endif() + +if (NOT Ruby_Development_FOUND) + message (FATAL_ERROR "Failed to find Ruby 'Development' component") +endif() + foreach(var_CMP0185 RUBY_EXECUTABLE RUBY_INCLUDE_DIRS @@ -20,8 +28,23 @@ foreach(var_CMP0185 endif() endforeach() -add_executable(ruby_version ruby_version.c) -target_include_directories(ruby_version PRIVATE ${Ruby_INCLUDE_DIRS}) -target_link_libraries(ruby_version PRIVATE ${Ruby_LIBRARIES}) +if(NOT TARGET Ruby::Interpreter) + message(SEND_ERROR "Ruby::Interpreter not found") +endif() -add_test(NAME ruby_version COMMAND ruby_version) +if (NOT TARGET Ruby::Ruby) + message(SEND_ERROR "Ruby::Ruby not found") +endif() + +if (NOT TARGET Ruby::Module) + message(SEND_ERROR "Ruby::Module not found") +endif() + +add_executable(ruby_version_var ruby_version.c) +target_include_directories(ruby_version_var PRIVATE ${Ruby_INCLUDE_DIRS}) +target_link_libraries(ruby_version_var PRIVATE ${Ruby_LIBRARIES}) +add_test(NAME ruby_version_var COMMAND ruby_version_var) + +add_executable(ruby_version_tgt ruby_version.c) +target_link_libraries(ruby_version_tgt Ruby::Ruby) +add_test(NAME ruby_version_tgt COMMAND ruby_version_tgt)