Skip to content

Commit

Permalink
Merge pull request #150 from rake-compiler/flavorjones-expose-cross-r…
Browse files Browse the repository at this point in the history
…ubies

feat: introduce `RakeCompilerDock.set_ruby_cc_version`
  • Loading branch information
flavorjones authored Jan 20, 2025
2 parents a173ba7 + 2205d50 commit 6812c27
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 1 deletion.
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,44 @@ Other environment variables can be set or passed through to the container like t
RakeCompilerDock.sh "rake cross native gem OPENSSL_VERSION=#{ENV['OPENSSL_VERSION']}"
```

### Choosing specific Ruby versions to support

If you only want to precompile for certain Ruby versions, you can specify those versions by overwriting the `RUBY_CC_VERSION` environment variable.

For example, if you wanted to only support Ruby 3.4 and 3.3, you might set this variable to:

``` ruby
ENV["RUBY_CC_VERSION"] = "3.4.1:3.3.7"
```

In practice, though, hardcoding these versions is brittle because the patch versions in the container may very from release to release.

A more robust way to do this is to use `RakeCompilerDock.ruby_cc_version` which accepts an array of Ruby minor versions or patch version requirements.

``` ruby
RakeCompilerDock.ruby_cc_version("3.3", "3.4")
# => "3.4.1:3.3.7"

RakeCompilerDock.ruby_cc_version("~> 3.4.0", "~> 3.3.0")
# => "3.4.1:3.3.7"

RakeCompilerDock.ruby_cc_version("~> 3.3")
# => "3.4.1:3.3.7"
```

So you can either set the environment variable directly:

``` ruby
ENV["RUBY_CC_VERSION"] = RakeCompilerDock.ruby_cc_version("~> 3.1")
```

or do the same thing using the `set_ruby_cc_version` convenience method:

``` ruby
RakeCompilerDock.set_ruby_cc_version("~> 3.1") # equivalent to the above assignment
```



## More information

Expand Down
83 changes: 82 additions & 1 deletion lib/rake_compiler_dock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,86 @@ def exec(*args, &block)
Starter.exec(*args, &block)
end

module_function :exec, :sh, :image_name
# Retrieve the cross-rubies that are available in the docker image. This can be used to construct
# a custom `RUBY_CC_VERSION` string that is valid.
#
# Returns a Hash<minor_version => corresponding_patch_version>
#
# For example:
#
# RakeCompilerDock.cross_rubies
# # => {
# # "3.4" => "3.4.1",
# # "3.3" => "3.3.5",
# # "3.2" => "3.2.6",
# # "3.1" => "3.1.6",
# # "3.0" => "3.0.7",
# # "2.7" => "2.7.8",
# # "2.6" => "2.6.10",
# # "2.5" => "2.5.9",
# # "2.4" => "2.4.10",
# # }
#
def cross_rubies
{
"3.4" => "3.4.1",
"3.3" => "3.3.7",
"3.2" => "3.2.6",
"3.1" => "3.1.6",
"3.0" => "3.0.7",
"2.7" => "2.7.8",
"2.6" => "2.6.10",
"2.5" => "2.5.9",
"2.4" => "2.4.10",
}
end

# Returns a valid RUBY_CC_VERSION string for the given requirements,
# where each `requirement` may be:
#
# - a String that matches the minor version exactly
# - a String that can be used as a Gem::Requirement constructor argument
# - a Gem::Requirement object
#
# Note that the returned string will contain versions sorted in descending order.
#
# For example:
# RakeCompilerDock.ruby_cc_version("2.7", "3.4")
# # => "3.4.1:2.7.8"
#
# RakeCompilerDock.ruby_cc_version("~> 3.2")
# # => "3.4.1:3.3.7:3.2.6"
#
# RakeCompilerDock.ruby_cc_version(Gem::Requirement.new("~> 3.2"))
# # => "3.4.1:3.3.7:3.2.6"
#
def ruby_cc_version(*requirements)
cross = cross_rubies
output = []

if requirements.empty?
output += cross.values
else
requirements.each do |requirement|
if cross[requirement]
output << cross[requirement]
else
requirement = Gem::Requirement.new(requirement) unless requirement.is_a?(Gem::Requirement)
versions = cross.values.find_all { |v| requirement.satisfied_by?(Gem::Version.new(v)) }
raise("No matching ruby version for requirement: #{requirement.inspect}") if versions.empty?
output += versions
end
end
end

output.uniq.sort.reverse.join(":")
end

# Set the environment variable `RUBY_CC_VERSION` to the value returned by `ruby_cc_version`,
# for the given requirements.
def set_ruby_cc_version(*requirements)
ENV["RUBY_CC_VERSION"] = ruby_cc_version(*requirements)
end

module_function :exec, :sh, :image_name, :cross_rubies, :ruby_cc_version, :set_ruby_cc_version
end
2 changes: 2 additions & 0 deletions test/test_environment_variables.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def test_RUBY_CC_VERSION
df = File.read(File.expand_path("../../Dockerfile.mri.erb", __FILE__))
df =~ /^ENV RUBY_CC_VERSION=(.*)$/
assert_equal $1, rcd_env['RUBY_CC_VERSION']

assert_equal RakeCompilerDock.ruby_cc_version, rcd_env['RUBY_CC_VERSION']
end

def test_RAKE_EXTENSION_TASK_NO_NATIVE
Expand Down
82 changes: 82 additions & 0 deletions test/test_versions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require 'rake_compiler_dock'
require 'rbconfig'
require 'test/unit'

class TestVersions < Test::Unit::TestCase
def test_cross_rubies
cross = RakeCompilerDock.cross_rubies
assert_operator(cross, :is_a?, Hash)
cross.each do |minor, patch|
assert_match(/^\d+\.\d+$/, minor)
assert_match(/^\d+\.\d+\.\d+$/, patch)
end
end

def test_ruby_cc_versions_no_args
cross = RakeCompilerDock.cross_rubies
expected = cross.values.sort.reverse.join(":")

assert_equal(expected, RakeCompilerDock.ruby_cc_version)
end

def test_ruby_cc_versions_strings
cross = RakeCompilerDock.cross_rubies

expected = cross["3.4"]
assert_equal(expected, RakeCompilerDock.ruby_cc_version("3.4"))

expected = [cross["3.4"], cross["3.2"]].join(":")
assert_equal(expected, RakeCompilerDock.ruby_cc_version("3.4", "3.2"))

expected = [cross["3.4"], cross["3.2"]].join(":")
assert_equal(expected, RakeCompilerDock.ruby_cc_version("3.2", "3.4"))

assert_raises do
RakeCompilerDock.ruby_cc_version("9.8")
end

assert_raises do
RakeCompilerDock.ruby_cc_version("foo")
end
end

def test_ruby_cc_versions_requirements
cross = RakeCompilerDock.cross_rubies

expected = cross["3.4"]
assert_equal(expected, RakeCompilerDock.ruby_cc_version("~> 3.4"))
assert_equal(expected, RakeCompilerDock.ruby_cc_version(Gem::Requirement.new("~> 3.4")))

expected = [cross["3.4"], cross["3.3"], cross["3.2"]].join(":")
assert_equal(expected, RakeCompilerDock.ruby_cc_version("~> 3.2"))
assert_equal(expected, RakeCompilerDock.ruby_cc_version(Gem::Requirement.new("~> 3.2")))

expected = [cross["3.4"], cross["3.2"]].join(":")
assert_equal(expected, RakeCompilerDock.ruby_cc_version("~> 3.2.0", "~> 3.4.0"))
assert_equal(expected, RakeCompilerDock.ruby_cc_version(Gem::Requirement.new("~> 3.2.0"), Gem::Requirement.new("~> 3.4.0")))

expected = [cross["3.4"], cross["3.3"], cross["3.2"]].join(":")
assert_equal(expected, RakeCompilerDock.ruby_cc_version(">= 3.2"))
assert_equal(expected, RakeCompilerDock.ruby_cc_version(Gem::Requirement.new(">= 3.2")))

assert_raises do
RakeCompilerDock.ruby_cc_version(Gem::Requirement.new("> 9.8"))
end
end

def test_set_ruby_cc_versions
original_ruby_cc_versions = ENV["RUBY_CC_VERSION"]
cross = RakeCompilerDock.cross_rubies

RakeCompilerDock.set_ruby_cc_version(Gem::Requirement.new("~> 3.2.0"), Gem::Requirement.new("~> 3.4.0"))
assert_equal([cross["3.4"], cross["3.2"]].join(":"), ENV["RUBY_CC_VERSION"])

RakeCompilerDock.set_ruby_cc_version("~> 3.2.0", "~> 3.4.0")
assert_equal([cross["3.4"], cross["3.2"]].join(":"), ENV["RUBY_CC_VERSION"])

RakeCompilerDock.set_ruby_cc_version("~> 3.1")
assert_equal([cross["3.4"], cross["3.3"], cross["3.2"], cross["3.1"]].join(":"), ENV["RUBY_CC_VERSION"])
ensure
ENV["RUBY_CC_VERSION"] = original_ruby_cc_versions
end
end

0 comments on commit 6812c27

Please sign in to comment.