From 53c9a300b9529758420cd8c01c3406b5b4097226 Mon Sep 17 00:00:00 2001 From: Nathan Willson Date: Thu, 30 Jan 2025 23:01:09 +0900 Subject: [PATCH] wip: ruby provider --- core/providers/provider.go | 6 +- core/providers/ruby/ruby.go | 95 ++++++++++++++++++++++++++++++ core/providers/ruby/ruby_test.go | 54 +++++++++++++++++ examples/ruby-sinatra/Gemfile | 2 + examples/ruby-sinatra/Gemfile.lock | 14 +++++ examples/ruby-sinatra/Procfile | 1 + examples/ruby-sinatra/app.rb | 5 ++ 7 files changed, 175 insertions(+), 2 deletions(-) create mode 100644 core/providers/ruby/ruby.go create mode 100644 core/providers/ruby/ruby_test.go create mode 100644 examples/ruby-sinatra/Gemfile create mode 100644 examples/ruby-sinatra/Gemfile.lock create mode 100644 examples/ruby-sinatra/Procfile create mode 100644 examples/ruby-sinatra/app.rb diff --git a/core/providers/provider.go b/core/providers/provider.go index 13749d2..3cef939 100644 --- a/core/providers/provider.go +++ b/core/providers/provider.go @@ -6,6 +6,7 @@ import ( "github.com/railwayapp/railpack/core/providers/node" "github.com/railwayapp/railpack/core/providers/php" "github.com/railwayapp/railpack/core/providers/python" + "github.com/railwayapp/railpack/core/providers/ruby" "github.com/railwayapp/railpack/core/providers/staticfile" ) @@ -17,10 +18,11 @@ type Provider interface { func GetLanguageProviders() []Provider { return []Provider{ - &php.PhpProvider{}, + &golang.GoProvider{}, &node.NodeProvider{}, + &php.PhpProvider{}, &python.PythonProvider{}, - &golang.GoProvider{}, + &ruby.RubyProvider{}, &staticfile.StaticfileProvider{}, } } diff --git a/core/providers/ruby/ruby.go b/core/providers/ruby/ruby.go new file mode 100644 index 0000000..fb14c7c --- /dev/null +++ b/core/providers/ruby/ruby.go @@ -0,0 +1,95 @@ +package ruby + +import ( + "fmt" + "strings" + + "github.com/railwayapp/railpack/core/generate" + "github.com/railwayapp/railpack/core/plan" +) + +const ( + DEFAULT_RUBY_VERSION = "latest" +) + +type RubyProvider struct{} + +func (p *RubyProvider) Name() string { + return "ruby" +} + +func (p *RubyProvider) Detect(ctx *generate.GenerateContext) (bool, error) { + hasRuby := ctx.App.HasMatch("Gemfile") + + return hasRuby, nil +} + +func (p *RubyProvider) Plan(ctx *generate.GenerateContext) error { + packages := p.packages(ctx) + + _ = p.install(ctx, packages) + + ctx.Start.Command = fmt.Sprintf("ruby %s", "app.rb") + + return nil +} + +func (p *RubyProvider) packages(ctx *generate.GenerateContext) *generate.MiseStepBuilder { + packages := ctx.GetMiseStepBuilder() + + ruby := packages.Default("ruby", DEFAULT_RUBY_VERSION) + + if envVersion, varName := ctx.Env.GetConfigVariable("RUBY_VERSION"); envVersion != "" { + packages.Version(ruby, envVersion, varName) + } + + if version := p.gemfileRubyVersion(ctx); version != "" { + packages.Version(ruby, version, "Gemfile.lock") + } + + return packages +} + +func (p *RubyProvider) install(ctx *generate.GenerateContext, packages *generate.MiseStepBuilder) *generate.CommandStepBuilder { + install := ctx.NewCommandStep("install") + install.AddCommands([]plan.Command{ + // make sure gem is updated + plan.NewExecCommand("gem update --system --no-document"), + // install bundler + plan.NewExecCommand("gem install -N bundler"), + plan.NewCopyCommand("Gemfile"), + plan.NewCopyCommand("Gemfile.lock"), + plan.NewExecCommand("bundle install"), + }) + + install.DependsOn = []string{packages.DisplayName} + + return install +} + +// scan for a Gemfile.lock and return the Ruby version +func (p *RubyProvider) gemfileRubyVersion(ctx *generate.GenerateContext) string { + if gemfileLock, err := ctx.App.ReadFile("Gemfile.lock"); err == nil { + foundRubyVersion := false + lines := strings.Split(string(gemfileLock), "\n") + for _, line := range lines { + // Look for the "RUBY VERSION" line + if strings.HasPrefix(line, "RUBY VERSION") { + foundRubyVersion = true + continue + } + + // The Ruby version follows "RUBY VERSION" + if foundRubyVersion { + fields := strings.Fields(line) + if len(fields) >= 2 && fields[0] == "ruby" { + fmt.Println("Ruby Version:", fields[1]) + return fields[1] + } + } + } + // packages.Version(ruby, string(versionFile), "Gemfile.lock") + } + + return "" +} diff --git a/core/providers/ruby/ruby_test.go b/core/providers/ruby/ruby_test.go new file mode 100644 index 0000000..db6aaa0 --- /dev/null +++ b/core/providers/ruby/ruby_test.go @@ -0,0 +1,54 @@ +package ruby + +import ( + "testing" + + testingUtils "github.com/railwayapp/railpack/core/testing" + "github.com/stretchr/testify/require" +) + +func TestDetect(t *testing.T) { + tests := []struct { + name string + path string + want bool + }{ + { + name: "sinatra", + path: "../../../examples/ruby-sinatra", + want: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := testingUtils.CreateGenerateContext(t, tt.path) + provider := RubyProvider{} + got, err := provider.Detect(ctx) + require.NoError(t, err) + require.Equal(t, tt.want, got) + }) + } +} +func TestGemfileRubyVersion(t *testing.T) { + tests := []struct { + name string + path string + want string + }{ + { + name: "sinatra", + path: "../../../examples/ruby-sinatra", + want: "3.2", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ctx := testingUtils.CreateGenerateContext(t, tt.path) + provider := RubyProvider{} + version := provider.gemfileRubyVersion(ctx) + require.Equal(t, tt.want, version) + }) + } +} diff --git a/examples/ruby-sinatra/Gemfile b/examples/ruby-sinatra/Gemfile new file mode 100644 index 0000000..e5fe2ae --- /dev/null +++ b/examples/ruby-sinatra/Gemfile @@ -0,0 +1,2 @@ + +source 'https://rubygems.org' diff --git a/examples/ruby-sinatra/Gemfile.lock b/examples/ruby-sinatra/Gemfile.lock new file mode 100644 index 0000000..80b94cf --- /dev/null +++ b/examples/ruby-sinatra/Gemfile.lock @@ -0,0 +1,14 @@ +GEM + remote: https://rubygems.org/ + specs: + +PLATFORMS + arm64-darwin-21 + +DEPENDENCIES + +RUBY VERSION + ruby 3.2 + +BUNDLED WITH + 2.3.7 diff --git a/examples/ruby-sinatra/Procfile b/examples/ruby-sinatra/Procfile new file mode 100644 index 0000000..82cb606 --- /dev/null +++ b/examples/ruby-sinatra/Procfile @@ -0,0 +1 @@ +web: ruby app.rb \ No newline at end of file diff --git a/examples/ruby-sinatra/app.rb b/examples/ruby-sinatra/app.rb new file mode 100644 index 0000000..34b7933 --- /dev/null +++ b/examples/ruby-sinatra/app.rb @@ -0,0 +1,5 @@ +require 'sinatra' + +get '/' do + 'Hello world!' +end \ No newline at end of file