Skip to content

Commit

Permalink
Make cross-compilation for native gems work
Browse files Browse the repository at this point in the history
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 `<host>-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
  • Loading branch information
stanhu committed Jul 14, 2023
1 parent 26e68b7 commit f0e2f35
Show file tree
Hide file tree
Showing 2 changed files with 59 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
65 changes: 58 additions & 7 deletions ext/re2/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down Expand Up @@ -179,7 +228,9 @@ def process_recipe(name, version)
message("Using mini_portile version #{MiniPortile::VERSION}\n")

MiniPortileCMake.new(name, version).tap do |recipe|
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',
Expand All @@ -188,6 +239,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

Expand Down Expand Up @@ -268,13 +320,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
Expand Down

0 comments on commit f0e2f35

Please sign in to comment.