1- cmake_minimum_required (VERSION 3.26)
1+ # =============================================================================
2+ # CMakeLists.txt - Building a Ruby Extension with Rice
3+ # =============================================================================
4+ #
5+ # This CMake configuration demonstrates how to build a Ruby native extension
6+ # using the Rice library (https://github.com/ruby-rice/rice). Rice provides
7+ # a clean C++ interface for creating Ruby bindings.
8+ #
9+ # This file serves as an educational example and can be adapted for your
10+ # own Rice-based Ruby extensions.
11+ #
12+ # =============================================================================
13+
14+ # -----------------------------------------------------------------------------
15+ # CMake Version and C++ Standard
16+ # -----------------------------------------------------------------------------
17+ # CMake 3.26+ is required for modern FetchContent features and improved
18+ # Ruby detection. Rice requires C++17 for features like std::string_view,
19+ # structured bindings, and if-constexpr.
20+
21+ cmake_minimum_required (VERSION 3.26)
222
323set (CMAKE_CXX_STANDARD 17)
424set (CMAKE_CXX_STANDARD_REQUIRED ON )
525
6- # Setup ruby extension
26+ # -----------------------------------------------------------------------------
27+ # Project Definition
28+ # -----------------------------------------------------------------------------
29+ # Define the project name and specify that we only need a C++ compiler.
30+ # The project name is used throughout via ${CMAKE_PROJECT_NAME}.
31+
732project (BitmapPlusPlus LANGUAGES CXX)
8- add_library (${CMAKE_PROJECT_NAME} MODULE)
933
10- # Link to Rice git repo
34+ # -----------------------------------------------------------------------------
35+ # Create the Extension Library
36+ # -----------------------------------------------------------------------------
37+ # IMPORTANT: Use MODULE instead of SHARED for Ruby extensions!
38+ #
39+ # - MODULE: Creates a loadable plugin that cannot be linked against.
40+ # This is correct for Ruby extensions loaded via require/dlopen.
41+ # - SHARED: Creates a shared library that can be linked against.
42+ # On macOS, this creates a .dylib which Ruby cannot load.
43+ #
44+ # Using SHARED on macOS will result in: "LoadError: cannot load such file"
45+ # because Ruby expects a .bundle file, not a .dylib.
46+
47+ add_library (${CMAKE_PROJECT_NAME} MODULE)
48+
49+ # -----------------------------------------------------------------------------
50+ # Fetch Rice from GitHub
51+ # -----------------------------------------------------------------------------
52+ # Rice is a header-only library, so we use FetchContent to download it
53+ # automatically. This eliminates the need for users to manually install Rice.
54+ #
55+ # FetchContent downloads the repository at configure time and makes it
56+ # available as if it were part of your project.
57+ #
58+ # Note: For production gems, you may want to pin to a specific release tag
59+ # instead of 'dev' for reproducible builds.
60+
1161include (FetchContent)
1262FetchContent_Declare(
1363 rice
@@ -16,37 +66,109 @@ FetchContent_Declare(
1666)
1767FetchContent_MakeAvailable(rice)
1868
19- # Use FindRuby.cmake from Rice repo for now while upstream is being updated to support
20- # the new Ruby targets
69+ # -----------------------------------------------------------------------------
70+ # Configure Ruby Detection
71+ # -----------------------------------------------------------------------------
72+ # Rice provides an enhanced FindRuby.cmake that creates proper CMake targets
73+ # (Ruby::Ruby, Ruby::Module) instead of just setting variables. We prepend
74+ # Rice's module path so CMake finds this improved version.
75+ #
76+ # The upstream CMake FindRuby.cmake is being updated to support these targets,
77+ # but until that lands, we use Rice's version.
78+
2179list (PREPEND CMAKE_MODULE_PATH "${rice_SOURCE_DIR} " )
2280
23- # Find Ruby
81+ # -----------------------------------------------------------------------------
82+ # Find Ruby Installation
83+ # -----------------------------------------------------------------------------
84+ # find_package(Ruby) locates the Ruby installation and sets up:
85+ # - Ruby::Ruby - Target for embedding Ruby (links to libruby)
86+ # - Ruby::Module - Target for extensions (links to Ruby headers only)
87+ #
88+ # For extensions, always link to Ruby::Module, not Ruby::Ruby!
89+ # Extensions are loaded into an already-running Ruby process, so they
90+ # should not link against libruby (which could cause symbol conflicts).
91+
2492find_package (Ruby REQUIRED)
93+
2594target_link_libraries (${CMAKE_PROJECT_NAME} PRIVATE
2695 Ruby::Module
2796)
2897
29- # Rice headers
98+ # -----------------------------------------------------------------------------
99+ # Link to Rice
100+ # -----------------------------------------------------------------------------
101+ # The rice::rice target provides:
102+ # - Include paths to Rice headers
103+ # - Required compiler flags for Rice
104+ #
105+ # Rice is header-only, so this doesn't add any link-time dependencies.
106+
30107target_link_libraries (${CMAKE_PROJECT_NAME} PRIVATE
31108 rice::rice
32109)
33110
34- # BitmapPlusPlus.hpp headers
111+ # -----------------------------------------------------------------------------
112+ # Include Directories for Project Headers
113+ # -----------------------------------------------------------------------------
114+ # Add the current directory (ext/) to the include path so we can find
115+ # our project's header files (BitmapPlusPlus.hpp, etc.)
116+
35117target_include_directories (${CMAKE_PROJECT_NAME} PRIVATE .)
36118
37- # Define project properties
119+ # -----------------------------------------------------------------------------
120+ # Configure Extension Output
121+ # -----------------------------------------------------------------------------
122+ # Ruby extensions have specific naming requirements that vary by platform:
123+ #
124+ # Extension Suffix (from Ruby configuration):
125+ # - Linux: .so (e.g., bitmap_plus_plus_ruby.so)
126+ # - macOS: .bundle (e.g., bitmap_plus_plus_ruby.bundle)
127+ # - Windows: .so (e.g., bitmap_plus_plus_ruby.so)
128+ #
129+ # Note: Windows uses .so (not .dll) for Ruby extensions by convention.
130+ #
131+ # PREFIX "": Ruby extensions have no 'lib' prefix (unlike regular shared libs)
132+ #
133+ # Visibility Settings:
134+ # - CXX_VISIBILITY_PRESET hidden: Hide all symbols by default
135+ # - VISIBILITY_INLINES_HIDDEN ON: Hide inline function symbols
136+ # - WINDOWS_EXPORT_ALL_SYMBOLS OFF: Don't auto-export on Windows
137+ #
138+ # These settings ensure only the Init_* function is exported, reducing
139+ # binary size and avoiding symbol conflicts with other extensions.
140+ #
141+ # Output Directories:
142+ # - RUNTIME_OUTPUT_DIRECTORY: Where Windows puts .dll/.so files
143+ # - LIBRARY_OUTPUT_DIRECTORY: Where Unix puts .so/.bundle files
144+ #
145+ # On Windows, we place the extension in a Ruby version-specific subdirectory
146+ # (e.g., lib/3.3/) to support multiple Ruby versions simultaneously.
147+
38148get_target_property (RUBY_EXT_SUFFIX Ruby::Ruby INTERFACE_RUBY_EXTENSION_SUFFIX)
149+
39150set_target_properties (${CMAKE_PROJECT_NAME} PROPERTIES
40151 PREFIX ""
41152 SUFFIX "${RUBY_EXT_SUFFIX} "
42153 OUTPUT_NAME "bitmap_plus_plus_ruby"
43154 CXX_VISIBILITY_PRESET hidden
44155 VISIBILITY_INLINES_HIDDEN ON
45156 WINDOWS_EXPORT_ALL_SYMBOLS OFF
46- RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR} /../lib/${Ruby_VERSION_MAJOR} .${Ruby_VERSION_MINOR} " # Windows
47- LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR} /../lib" ) # Not Windows
157+ RUNTIME_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR} /../lib/${Ruby_VERSION_MAJOR} .${Ruby_VERSION_MINOR} "
158+ LIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR} /../lib"
159+ )
160+
161+ # -----------------------------------------------------------------------------
162+ # Source Files
163+ # -----------------------------------------------------------------------------
164+ # List all source files for the extension. The naming convention used here is:
165+ # - ProjectName-rb.hpp: Header declaring the Init function
166+ # - ProjectName-rb.cpp: Implementation with Rice bindings
167+ #
168+ # The Init_<name> function in the .cpp file is the entry point Ruby calls
169+ # when the extension is loaded via 'require'.
48170
49- # Source code
50171target_sources (${CMAKE_PROJECT_NAME} PRIVATE
51- "${CMAKE_PROJECT_NAME} -rb.hpp"
52- "${CMAKE_PROJECT_NAME} -rb.cpp" )
172+ "${CMAKE_PROJECT_NAME} -rb.hpp"
173+ "${CMAKE_PROJECT_NAME} -rb.cpp"
174+ )
0 commit comments