From 7f4ff2a3ebad7d0efd5f662130889c890ea0a8ac Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 13 Jul 2023 22:41:01 -0700 Subject: [PATCH] Make cross-compilation for native gems work mini_portile2 provides `configure` script with the `--host` parameter, and the script figures out the target compilers to use. With CMake, we don't have that benefit so we need to set the C and C++ compilers ourselves. We use a simple heuristic to find the target compilers: 1. For macOS, we use `clang` and `clang++. Otherwise we use `gcc` and `gcc++`. 2. We search the PATH for `-compiler`. Use that if we find it. Otherwise default to the base compilers. To make CMake work with cross-compilation, a number of variables have to be set: - CMAKE_SYSTEM_PROCESSOR - CMAKE_SYSTEM_NAME - CMAKE_C_COMPILER - CMAKE_CXX_COMPILER --- Rakefile | 2 +- ext/re2/extconf.rb | 67 +++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/Rakefile b/Rakefile index 89280b7..daee260 100644 --- a/Rakefile +++ b/Rakefile @@ -62,7 +62,7 @@ namespace 'gem' do RakeCompilerDock.sh <<~SCRIPT, platform: platform, verbose: true gem install bundler --no-document && bundle && - CMAKE=#{cmake} bundle exec rake gem:#{platform}:builder MAKE='nice make -j`nproc`' + bundle exec rake gem:#{platform}:builder CMAKE=#{cmake} SCRIPT end diff --git a/ext/re2/extconf.rb b/ext/re2/extconf.rb index 6968fdf..eb0e1aa 100644 --- a/ext/re2/extconf.rb +++ b/ext/re2/extconf.rb @@ -65,6 +65,55 @@ def do_help exit!(0) end +def darwin? + RbConfig::CONFIG["target_os"].include?("darwin") +end + +def target_host + # We use 'host' to set compiler prefix for cross-compiling. Prefer host_alias over host. And + # prefer i686 (what external dev tools use) to i386 (what ruby's configure.ac emits). + host = RbConfig::CONFIG["host_alias"].empty? ? RbConfig::CONFIG["host"] : RbConfig::CONFIG["host_alias"] + host.gsub(/i386/, "i686") +end + +def find_compiler(compilers) + compilers.find { |binary| find_executable(binary) } +end + +# configure automatically searches for the right compiler based on the +# `--host` parameter. However, we don't have that feature with +# cmake. Search for the right compiler for the target architecture using +# some basic heruistics. +def find_c_and_cxx_compilers(host) + if darwin? + c_compiler = 'clang' + cxx_compiler = 'clang++' + else + c_compiler = 'gcc' + cxx_compiler = 'g++' + end + + c_platform_compiler = "#{host}-#{c_compiler}" + cxx_platform_compiler = "#{host}-#{cxx_compiler}" + c_compiler = find_compiler([c_platform_compiler, c_compiler]) + cxx_compiler = find_compiler([cxx_platform_compiler, cxx_compiler]) + + [c_compiler, cxx_compiler] +end + +def cmake_compile_flags(host) + system_name = darwin? ? 'Darwin' : 'Linux' + c_compiler, cxx_compiler = find_c_and_cxx_compilers(host) + + # needed to ensure cross-compilation with CMake targets the right CPU and compilers + [ + "-DCMAKE_SYSTEM_PROCESSOR=#{RbConfig::CONFIG['target_cpu']}", + "-DCMAKE_SYSTEM_NAME=#{system_name}", + "-DCMAKE_C_COMPILER=#{c_compiler}", + "-DCMAKE_CXX_COMPILER=#{cxx_compiler}" + ] +end + # # main # @@ -179,7 +228,11 @@ def process_recipe(name, version) message("Using mini_portile version #{MiniPortile::VERSION}\n") MiniPortileCMake.new(name, version).tap do |recipe| + # We use 'host' to set compiler prefix for cross-compiling. Prefer host_alias over host. And + # prefer i686 (what external dev tools use) to i386 (what ruby's configure.ac emits). + recipe.host = target_host recipe.target = File.join(PACKAGE_ROOT_DIR, "ports") + recipe.configure_options += [ # abseil needs a C++14 compiler '-DCMAKE_CXX_STANDARD=17', @@ -188,6 +241,7 @@ def process_recipe(name, version) # ensures pkg-config and installed libraries will be in lib, not lib64 '-DCMAKE_INSTALL_LIBDIR=lib' ] + recipe.configure_options += cmake_compile_flags(recipe.host) yield recipe @@ -268,13 +322,12 @@ def build_with_vendored_libraries ENV['PKG_CONFIG_PATH'] = pkg_config_paths pc_file = File.join(re2_recipe.path, 'lib', 'pkgconfig', 're2.pc') - if pkg_config(pc_file) - # See https://bugs.ruby-lang.org/issues/18490, broken in Ruby 3.1 but fixed in Ruby 3.2. - flags = xpopen(['pkg-config', '--libs', '--static', pc_file], err: %i[child out], &:read) - flags.split.each { |flag| append_ldflags(flag) } if $?.success? - else - raise 'Please install the `pkg-config` utility!' - end + + raise 'Please install the `pkg-config` utility!' unless pkg_config('re2') + + # See https://bugs.ruby-lang.org/issues/18490, broken in Ruby 3.1 but fixed in Ruby 3.2. + flags = xpopen(['pkg-config', '--libs', '--static', pc_file], err: %i[child out], &:read) + flags.split.each { |flag| append_ldflags(flag) } if $?.success? build_extension end