Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cmake] Automatically add required cross-compilation variables #130

Merged
merged 2 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions lib/mini_portile2/mini_portile.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,45 @@ class MiniPortile
attr_accessor :host, :files, :patch_files, :target, :logger, :source_directory

def self.windows?
RbConfig::CONFIG['target_os'] =~ /mswin|mingw/
target_os =~ /mswin|mingw/
end

# GNU MinGW compiled Ruby?
def self.mingw?
RbConfig::CONFIG['target_os'] =~ /mingw/
target_os =~ /mingw/
end

# MS Visual-C compiled Ruby?
def self.mswin?
RbConfig::CONFIG['target_os'] =~ /mswin/
target_os =~ /mswin/
end

def self.darwin?
target_os =~ /darwin/
end

def self.freebsd?
target_os =~ /freebsd/
end

def self.openbsd?
target_os =~ /openbsd/
end

def self.linux?
target_os =~ /linux/
end

def self.solaris?
target_os =~ /solaris/
end

def self.target_os
RbConfig::CONFIG['target_os']
end

def self.target_cpu
RbConfig::CONFIG['target_cpu']
end

def initialize(name, version, **kwargs)
Expand Down
84 changes: 77 additions & 7 deletions lib/mini_portile2/mini_portile_cmake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
require 'open3'

class MiniPortileCMake < MiniPortile
attr_accessor :system_name

def configure_prefix
"-DCMAKE_INSTALL_PREFIX=#{File.expand_path(port_path)}"
end
Expand All @@ -12,13 +14,10 @@ def initialize(name, version, **kwargs)
end

def configure_defaults
if MiniPortile.mswin? && generator_available?('NMake')
['-G', 'NMake Makefiles']
elsif MiniPortile.mingw? && generator_available?('MSYS')
['-G', 'MSYS Makefiles']
else
[]
end
[
generator_defaults,
cmake_compile_flags,
].flatten
end

def configure
Expand Down Expand Up @@ -52,6 +51,77 @@ def cmake_cmd

private

def generator_defaults
if MiniPortile.mswin? && generator_available?('NMake')
['-G', 'NMake Makefiles']
elsif MiniPortile.mingw? && generator_available?('MSYS')
['-G', 'MSYS Makefiles']
else
[]
end
end

def cmake_compile_flags
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_NAME=#{cmake_system_name}",
"-DCMAKE_SYSTEM_PROCESSOR=#{MiniPortile.target_cpu}",
flavorjones marked this conversation as resolved.
Show resolved Hide resolved
"-DCMAKE_C_COMPILER=#{c_compiler}",
"-DCMAKE_CXX_COMPILER=#{cxx_compiler}"
]
end

def find_compiler(compilers)
compilers.find { |binary| which(binary) }
end

# configure automatically searches for the right compiler based on the
# `--host` parameter. However, CMake doesn't have an equivalent feature.
# Search for the right compiler for the target architecture using
# some basic heruistics.
def find_c_and_cxx_compilers(host)
c_compiler = ENV["CC"]
cxx_compiler = ENV["CXX"]

if MiniPortile.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

# Full list: https://gitlab.kitware.com/cmake/cmake/-/blob/v3.26.4/Modules/CMakeDetermineSystem.cmake?ref_type=tags#L12-31
def cmake_system_name
return system_name if system_name

if MiniPortile.linux?
'Linux'
elsif MiniPortile.darwin?
'Darwin'
elsif MiniPortile.windows?
'Windows'
elsif MiniPortile.freebsd?
'FreeBSD'
elsif MiniPortile.openbsd?
'OpenBSD'
elsif MiniPortile.solaris?
'SunOS'
else
raise "Unable to set CMAKE_SYSTEM_NAME for #{MiniPortile.target_os}"
end
end

def generator_available?(generator_type)
stdout_str, status = Open3.capture2("#{cmake_cmd} --help")

Expand Down
155 changes: 136 additions & 19 deletions test/test_cmake.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ def before_all
create_tar(@tar_path, @assets_path, "test-cmake-1.0")
start_webrick(File.dirname(@tar_path))

@recipe = MiniPortileCMake.new("test-cmake", "1.0").tap do |recipe|
recipe.files << "http://localhost:#{HTTP_PORT}/#{ERB::Util.url_encode(File.basename(@tar_path))}"
recipe.patch_files << File.join(@assets_path, "patch 1.diff")
git_dir = File.join(@assets_path, "git")
with_custom_git_dir(git_dir) do
recipe.cook
end
@recipe = init_recipe

git_dir = File.join(@assets_path, "git")
with_custom_git_dir(git_dir) do
recipe.cook
end
end

Expand Down Expand Up @@ -57,6 +55,13 @@ def test_install
binary = File.join(recipe.path, "bin", exe_name)
assert File.exist?(binary), binary
end

def init_recipe
MiniPortileCMake.new("test-cmake", "1.0").tap do |recipe|
recipe.files << "http://localhost:#{HTTP_PORT}/#{ERB::Util.url_encode(File.basename(@tar_path))}"
recipe.patch_files << File.join(@assets_path, "patch 1.diff")
end
end
end

class TestCMakeConfig < TestCMake
Expand All @@ -77,26 +82,103 @@ def test_make_command_configuration
end
end

def test_configure_defaults_with_macos
recipe = init_recipe
recipe.host = 'some-host'

with_env({ "CC" => nil, "CXX" => nil }) do
MiniPortile.stub(:darwin?, true) do
with_stubbed_target(os: 'darwin22', cpu: 'arm64') do
with_compilers(recipe, host_prefix: true, c_compiler: 'clang', cxx_compiler: 'clang++') do
Open3.stub(:capture2, cmake_help_mock('Unix')) do
assert_equal(
[
"-DCMAKE_SYSTEM_NAME=Darwin",
"-DCMAKE_SYSTEM_PROCESSOR=arm64",
"-DCMAKE_C_COMPILER=some-host-clang",
"-DCMAKE_CXX_COMPILER=some-host-clang++"
],
recipe.configure_defaults)
end
end
end
end
end
end

def test_configure_defaults_with_manual_system_name
recipe = init_recipe
recipe.system_name = 'Custom'

MiniPortile.stub(:darwin?, false) do
with_stubbed_target do
with_compilers(recipe) do
Open3.stub(:capture2, cmake_help_mock('Unix')) do
assert_equal(
[
"-DCMAKE_SYSTEM_NAME=Custom",
"-DCMAKE_SYSTEM_PROCESSOR=x86_64",
"-DCMAKE_C_COMPILER=gcc",
"-DCMAKE_CXX_COMPILER=g++"
],
recipe.configure_defaults)
end
end
end
end
end

def test_configure_defaults_with_unix_makefiles
Open3.stub(:capture2, cmake_help_mock('Unix')) do
MiniPortile.stub(:mingw?, true) do
assert_equal([], @recipe.configure_defaults)
recipe = init_recipe

MiniPortile.stub(:linux?, true) do
MiniPortile.stub(:darwin?, false) do
with_stubbed_target do
with_compilers(recipe) do
Open3.stub(:capture2, cmake_help_mock('Unix')) do
MiniPortile.stub(:mingw?, true) do
assert_equal(default_x86_compile_flags,
recipe.configure_defaults)
end
end
end
end
end
end
end

def test_configure_defaults_with_msys_makefiles
Open3.stub(:capture2, cmake_help_mock('MSYS')) do
MiniPortile.stub(:mingw?, true) do
assert_equal(['-G', 'MSYS Makefiles'], @recipe.configure_defaults)
recipe = init_recipe

MiniPortile.stub(:linux?, true) do
MiniPortile.stub(:darwin?, false) do
with_stubbed_target do
with_compilers(recipe) do
Open3.stub(:capture2, cmake_help_mock('MSYS')) do
MiniPortile.stub(:mingw?, true) do
assert_equal(['-G', 'MSYS Makefiles'] + default_x86_compile_flags, recipe.configure_defaults)
end
end
end
end
end
end
end

def test_configure_defaults_with_nmake_makefiles
Open3.stub(:capture2, cmake_help_mock('NMake')) do
MiniPortile.stub(:mswin?, true) do
assert_equal(['-G', 'NMake Makefiles'], @recipe.configure_defaults)
recipe = init_recipe

MiniPortile.stub(:linux?, true) do
MiniPortile.stub(:darwin?, false) do
with_stubbed_target do
with_compilers(recipe) do
Open3.stub(:capture2, cmake_help_mock('NMake')) do
MiniPortile.stub(:mswin?, true) do
assert_equal(['-G', 'NMake Makefiles'] + default_x86_compile_flags, recipe.configure_defaults)
end
end
end
end
end
end
end
Expand All @@ -114,12 +196,47 @@ def test_cmake_command_configuration

private

def with_stubbed_target(os: 'linux', cpu: 'x86_64')
MiniPortile.stub(:target_os, os) do
MiniPortile.stub(:target_cpu, cpu) do
yield
end
end
end

def with_compilers(recipe, host_prefix: false, c_compiler: 'gcc', cxx_compiler: 'g++')
mock = MiniTest::Mock.new

if host_prefix
mock.expect(:call, true, ["#{recipe.host}-#{c_compiler}"])
mock.expect(:call, true, ["#{recipe.host}-#{cxx_compiler}"])
else
mock.expect(:call, false, ["#{recipe.host}-#{c_compiler}"])
mock.expect(:call, true, [c_compiler])
mock.expect(:call, false, ["#{recipe.host}-#{cxx_compiler}"])
mock.expect(:call, true, [cxx_compiler])
end

recipe.stub(:which, mock) do
yield
end
end

def default_x86_compile_flags
[
"-DCMAKE_SYSTEM_NAME=Linux",
"-DCMAKE_SYSTEM_PROCESSOR=x86_64",
"-DCMAKE_C_COMPILER=gcc",
"-DCMAKE_CXX_COMPILER=g++"
]
end

def cmake_help_mock(generator_type)
open3_mock = MiniTest::Mock.new
cmake_script = <<~SCRIPT
echo "The following generators are available on this platform (* marks default):"
echo "* #{generator_type} Makefiles = Generates standard #{generator_type.upcase} makefiles."
SCRIPT
echo "The following generators are available on this platform (* marks default):"
echo "* #{generator_type} Makefiles = Generates standard #{generator_type.upcase} makefiles."
SCRIPT

exit_status = MiniTest::Mock.new
exit_status.expect(:success?, true)
Expand Down