Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
{
"caches": {
"apt": {
"directory": "/var/cache/apt",
"type": "locked"
},
"apt-lists": {
"directory": "/var/lib/apt/lists",
"type": "locked"
}
},
"deploy": {
"base": {
"step": "packages:apt:runtime"
},
"inputs": [
{
"include": [
"/mise/shims",
"/mise/installs",
"/usr/local/bin/mise",
"/etc/mise/config.toml",
"/root/.local/state/mise"
],
"step": "packages:mise"
},
{
"include": [
"/usr/local/bundle"
],
"step": "install"
},
{
"include": [
"/app"
],
"step": "build"
}
],
"startCommand": "bundle exec rackup config.ru -p ${PORT:-3000}",
"variables": {
"BUNDLE_GEMFILE": "/app/Gemfile",
"GEM_HOME": "/usr/local/bundle",
"GEM_PATH": "/usr/local/bundle",
"MALLOC_ARENA_MAX": "2"
}
},
"steps": [
{
"caches": [
"apt",
"apt-lists"
],
"commands": [
{
"cmd": "sh -c 'apt-get update \u0026\u0026 apt-get install -y cargo libyaml-dev rustc'",
"customName": "install apt packages: cargo libyaml-dev rustc"
}
],
"inputs": [
{
"image": "ghcr.io/railwayapp/railpack-builder:latest"
}
],
"name": "packages:apt:build"
},
{
"assets": {
"mise.toml": "[mise.toml]"
},
"commands": [
{
"path": "/mise/shims"
},
{
"customName": "create mise config",
"name": "mise.toml",
"path": "/etc/mise/config.toml"
},
{
"cmd": "sh -c 'mise trust -a \u0026\u0026 mise install'",
"customName": "install mise packages: ruby"
}
],
"inputs": [
{
"step": "packages:apt:build"
}
],
"name": "packages:mise",
"variables": {
"MISE_CACHE_DIR": "/mise/cache",
"MISE_CONFIG_DIR": "/mise",
"MISE_DATA_DIR": "/mise",
"MISE_INSTALLS_DIR": "/mise/installs",
"MISE_SHIMS_DIR": "/mise/shims"
}
},
{
"commands": [
{
"cmd": "gem install -N bundler:2.4.22"
},
{
"dest": "Gemfile",
"src": "Gemfile"
},
{
"dest": "Gemfile.lock",
"src": "Gemfile.lock"
},
{
"cmd": "bundle install"
},
{
"path": "/usr/local/bundle"
}
],
"inputs": [
{
"step": "packages:mise"
}
],
"name": "install",
"variables": {
"BUNDLE_GEMFILE": "/app/Gemfile",
"GEM_HOME": "/usr/local/bundle",
"GEM_PATH": "/usr/local/bundle",
"MALLOC_ARENA_MAX": "2"
}
},
{
"commands": [
{
"dest": ".",
"src": "."
}
],
"inputs": [
{
"step": "install"
}
],
"name": "build",
"variables": {
"BUNDLE_GEMFILE": "/app/Gemfile",
"GEM_HOME": "/usr/local/bundle",
"GEM_PATH": "/usr/local/bundle",
"MALLOC_ARENA_MAX": "2"
}
},
{
"caches": [
"apt",
"apt-lists"
],
"commands": [
{
"cmd": "sh -c 'apt-get update \u0026\u0026 apt-get install -y libyaml-dev'",
"customName": "install apt packages: libyaml-dev"
}
],
"inputs": [
{
"image": "ghcr.io/railwayapp/railpack-runtime:latest"
}
],
"name": "packages:apt:runtime"
}
]
}
50 changes: 21 additions & 29 deletions core/providers/ruby/ruby.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,8 +245,9 @@ func (p *RubyProvider) InstallMisePackages(ctx *generate.GenerateContext, miseSt
miseStep.Version(ruby, utils.ExtractSemverVersion(string(versionFile)), ".ruby-version")
}

if gemfileVersion := parseVersionFromGemfile(ctx); gemfileVersion != "" {
miseStep.Version(ruby, gemfileVersion, "Gemfile")
if constraint := extractRubyConstraintFromGemfile(ctx); constraint != "" {
Comment thread
gauravtiwari marked this conversation as resolved.
miseStep.Resolver.SetVersionAvailable(ruby, isStableRubyVersion)
miseStep.Version(ruby, constraint, "Gemfile")
}

miseStep.AddSupportingAptPackage("libyaml-dev")
Expand All @@ -261,6 +262,11 @@ func (p *RubyProvider) InstallMisePackages(ctx *generate.GenerateContext, miseSt
}
}

func isStableRubyVersion(version string) bool {
return !(strings.Contains(version, "-dev") || strings.Contains(version, "-preview") ||
strings.Contains(version, "-rc") || strings.Contains(version, "dev"))
}

func (p *RubyProvider) getRubyVersion(ctx *generate.GenerateContext) string {
miseStepBuilder := ctx.GetMiseStepBuilder()
pkg := miseStepBuilder.Resolver.Get("ruby")
Expand Down Expand Up @@ -320,35 +326,9 @@ func (p *RubyProvider) addMetadata(ctx *generate.GenerateContext) {
}

var (
gemfileVersionRegex = regexp.MustCompile(`ruby (?:'|")(.*)(?:'|")[^>]"`)
gemfileLockVersionRegex = regexp.MustCompile(`ruby ((?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*))[^>]`)
gemfileVersionRegex = regexp.MustCompile(`ruby\s+(?:'|")([^'"]+)(?:'|")`)
)

func parseVersionFromGemfile(ctx *generate.GenerateContext) string {
gemfile, err := ctx.App.ReadFile("Gemfile")
if err != nil {
return ""
}

matches := gemfileVersionRegex.FindStringSubmatch(string(gemfile))

if len(matches) > 2 {
return matches[2]
}

gemfileLock, err := ctx.App.ReadFile("Gemfile.lock")
if err != nil {
return ""
}

matches = gemfileLockVersionRegex.FindStringSubmatch(string(gemfileLock))
if len(matches) > 1 {
return matches[1]
}

return ""
}

func parseBundlerVersionFromGemfile(ctx *generate.GenerateContext) string {
gemfileLock, err := ctx.App.ReadFile("Gemfile.lock")
if err != nil {
Expand Down Expand Up @@ -393,3 +373,15 @@ func parseLocalPathsFromGemfile(ctx *generate.GenerateContext) []string {

return paths
}

func extractRubyConstraintFromGemfile(ctx *generate.GenerateContext) string {
gemfile, err := ctx.App.ReadFile("Gemfile")
if err != nil {
return ""
}
matches := gemfileVersionRegex.FindStringSubmatch(string(gemfile))
if len(matches) > 1 {
return matches[1]
}
return ""
}
81 changes: 81 additions & 0 deletions core/providers/ruby/ruby_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ruby
import (
"testing"

"github.com/Masterminds/semver/v3"
"github.com/stretchr/testify/require"

testingUtils "github.com/railwayapp/railpack/core/testing"
Expand Down Expand Up @@ -36,3 +37,83 @@ func TestDetect(t *testing.T) {
})
}
}

func TestRubyVersionDetection_Examples(t *testing.T) {
cases := []struct {
desc string
path string
expect string
}{
{
desc: ".ruby-version present (ruby-3)",
path: "../../../examples/ruby-3",
expect: "3.2.1",
},
{
desc: "Gemfile.lock with version (ruby-vanilla)",
path: "../../../examples/ruby-vanilla",
expect: "3.4.2",
},
{
desc: "Gemfile with version constraint < 3.5.0 (ruby-no-version)",
path: "../../../examples/ruby-no-version",
expect: "3.4.4",
},
{
desc: "Gemfile with single version constraint (ruby-single-version)",
path: "../../../examples/ruby-single-version",
expect: "3.4.2",
},
}

for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, c.path)
miseStep := ctx.GetMiseStepBuilder()
provider := RubyProvider{}
provider.InstallMisePackages(ctx, miseStep)

resolvedPackages, err := miseStep.Resolver.ResolvePackages()
require.NoError(t, err)

resolvedPkg, exists := resolvedPackages["ruby"]
if !exists {
t.Fatalf("ruby package not found in resolved packages")
}

if resolvedPkg.ResolvedVersion == nil {
t.Fatalf("ruby package version not resolved")
}

require.Equal(t, c.expect, *resolvedPkg.ResolvedVersion)
})
}
}

func TestRubyVersionConstraintResolution(t *testing.T) {
t.Run("constraint_resolution_works", func(t *testing.T) {
ctx := testingUtils.CreateGenerateContext(t, "../../../examples/ruby-no-version")

miseStep := ctx.GetMiseStepBuilder()
provider := RubyProvider{}
provider.InstallMisePackages(ctx, miseStep)

resolvedPackages, err := miseStep.Resolver.ResolvePackages()
require.NoError(t, err)

resolvedPkg, exists := resolvedPackages["ruby"]
require.True(t, exists, "ruby package should be found in resolved packages")
require.NotNil(t, resolvedPkg.ResolvedVersion, "ruby package version should be resolved")

actualVersion := *resolvedPkg.ResolvedVersion

v, err := semver.NewVersion(actualVersion)
require.NoError(t, err)
require.True(t, v.Major() == 3 && v.Minor() >= 2 && v.Minor() < 5,
"Resolved version %s should be >= 3.2.0 and < 3.5.0", actualVersion)

require.True(t, v.Minor() >= 4, "Should resolve to a recent version, got %s", actualVersion)

require.Equal(t, "3.4.4", actualVersion, "Should resolve to the highest stable version under 3.5.0")
})
}
4 changes: 4 additions & 0 deletions core/resolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,7 @@ func (r *Resolver) SetVersionAvailable(ref PackageRef, isVersionAvailable func(v
func (r *Resolver) SetSkipMiseInstall(ref PackageRef, skipMiseInstall bool) {
r.packages[ref.Name].SkipMiseInstall = skipMiseInstall
}

func (r *Resolver) GetMise() *mise.Mise {
return r.mise
}
4 changes: 2 additions & 2 deletions examples/ruby-no-version/Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

source 'https://rubygems.org'
ruby '>= 3.2.0', '< 3.5.0'

gem 'puma', '~> 5.6'
gem 'sinatra'
gem "puma", "~> 5.6"
5 changes: 5 additions & 0 deletions examples/ruby-single-version/Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
source 'https://rubygems.org'
ruby '3.4.2'

gem 'puma', '~> 5.6'
gem 'sinatra'
20 changes: 20 additions & 0 deletions examples/ruby-single-version/Gemfile.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
GEM
remote: https://rubygems.org/
specs:
puma (5.6.7)
nio4r (~> 2.0)
sinatra (3.0.5)
mustermann (~> 3.0)
rack (~> 3.0)
rack-protection (= 3.0.5)
tilt (~> 2.0)

PLATFORMS
x86_64-linux

DEPENDENCIES
puma (~> 5.6)
sinatra

BUNDLED WITH
2.4.22
5 changes: 5 additions & 0 deletions examples/ruby-single-version/app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require 'sinatra'

get '/' do
"Hello from Ruby #{RUBY_VERSION}!"
end
2 changes: 2 additions & 0 deletions examples/ruby-single-version/config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require './app'
run Sinatra::Application
Loading
Loading