From a4eaf8a2b4cc163ab3c1a3cd46045ef7dc13295b Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:41:27 -0400 Subject: [PATCH 1/6] feat: install gem tools --- internal/librarian/ruby/install.go | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/internal/librarian/ruby/install.go b/internal/librarian/ruby/install.go index fdb4fb8748c..02a6caaac0f 100644 --- a/internal/librarian/ruby/install.go +++ b/internal/librarian/ruby/install.go @@ -15,14 +15,35 @@ package ruby import ( + "errors" "fmt" + "os/exec" "path/filepath" + "context" + "github.com/googleapis/librarian/internal/cache" + "github.com/googleapis/librarian/internal/config" + "github.com/googleapis/librarian/internal/tool/gem" ) const toolsDir = "ruby_tools" +var ( + errNoGems = errors.New("no gem tools specified") + errMissingExecutable = errors.New("is not installed or not in PATH, which is required for Ruby tool installation") +) + +func Install(ctx context.Context, tools *config.Tools) error { + if err := verify(tools); err != nil { + return err + } + if err := gem.Install(ctx, tools.Gem); err != nil { + return err + } + return nil +} + // InstallDir gets the directory where tools should be installed. func InstallDir() (string, error) { dir, err := cache.BinDirectory() @@ -44,3 +65,16 @@ func binDir() (string, error) { } return filepath.Join(installDir, "bin"), nil } + +func verify(tools *config.Tools) error { + if tools == nil || len(tools.Gem) == 0 { + return errNoGems + } + + for _, cmd := range []string{"gem"} { + if _, err := exec.LookPath(cmd); err != nil { + return fmt.Errorf("%s %w: %w", cmd, errMissingExecutable, err) + } + } + return nil +} From 5685b6dae6d67487f57f457cfb51637e6d2183d1 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:45:33 -0400 Subject: [PATCH 2/6] add unit tests --- internal/librarian/ruby/install.go | 1 + internal/librarian/ruby/install_test.go | 26 +++++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/internal/librarian/ruby/install.go b/internal/librarian/ruby/install.go index 02a6caaac0f..c0a01ff4264 100644 --- a/internal/librarian/ruby/install.go +++ b/internal/librarian/ruby/install.go @@ -34,6 +34,7 @@ var ( errMissingExecutable = errors.New("is not installed or not in PATH, which is required for Ruby tool installation") ) +// Install installs Ruby gem dependencies. func Install(ctx context.Context, tools *config.Tools) error { if err := verify(tools); err != nil { return err diff --git a/internal/librarian/ruby/install_test.go b/internal/librarian/ruby/install_test.go index 809b94f17f6..098c70f8183 100644 --- a/internal/librarian/ruby/install_test.go +++ b/internal/librarian/ruby/install_test.go @@ -15,8 +15,11 @@ package ruby import ( + "os" "path/filepath" "testing" + + "github.com/googleapis/librarian/internal/config" ) func TestInstallDir(t *testing.T) { @@ -44,3 +47,26 @@ func TestBinDir(t *testing.T) { t.Errorf("binDir() = %q, want %q", got, want) } } + +func TestVerify(t *testing.T) { + stubDir := t.TempDir() + gemStubPath := filepath.Join(stubDir, "gem") + // Create a simple shell script stub for "gem". + stubContent := "#!/bin/sh\nexit 0\n" + if err := os.WriteFile(gemStubPath, []byte(stubContent), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("PATH", stubDir+string(filepath.ListSeparator)+os.Getenv("PATH")) + tools := &config.Tools{ + Gem: []*config.GemTool{ + { + Name: "a-gem-tool", + Version: "1.0", + }, + }, + } + + if err := verify(tools); err != nil { + t.Errorf("verify() returned unexpected error: %v", err) + } +} From 3441208b17ec74168790660d2417d9a51425aa13 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:47:24 -0400 Subject: [PATCH 3/6] add unit tests --- internal/librarian/ruby/install_test.go | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/internal/librarian/ruby/install_test.go b/internal/librarian/ruby/install_test.go index 098c70f8183..6f3b6088dc5 100644 --- a/internal/librarian/ruby/install_test.go +++ b/internal/librarian/ruby/install_test.go @@ -15,6 +15,7 @@ package ruby import ( + "errors" "os" "path/filepath" "testing" @@ -70,3 +71,48 @@ func TestVerify(t *testing.T) { t.Errorf("verify() returned unexpected error: %v", err) } } + +func TestVerify_Error(t *testing.T) { + for _, test := range []struct { + name string + tools *config.Tools + setup func(t *testing.T) + wantErr error + }{ + { + name: "nil tools", + tools: nil, + wantErr: errNoGems, + }, + { + name: "empty tools", + tools: &config.Tools{}, + wantErr: errNoGems, + }, + { + name: "missing gem in path", + tools: &config.Tools{ + Gem: []*config.GemTool{ + { + Name: "a-gem-tool", + Version: "1.0", + }, + }, + }, + setup: func(t *testing.T) { + t.Setenv("PATH", t.TempDir()) + }, + wantErr: errMissingExecutable, + }, + } { + t.Run(test.name, func(t *testing.T) { + if test.setup != nil { + test.setup(t) + } + err := verify(test.tools) + if !errors.Is(err, test.wantErr) { + t.Fatalf("verify() error = %v, wantErr = %v", err, test.wantErr) + } + }) + } +} From e638ae678b3210162c67941a30f25f98b6bd487a Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:51:57 -0400 Subject: [PATCH 4/6] add unit tests --- internal/librarian/ruby/install_test.go | 31 +++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/internal/librarian/ruby/install_test.go b/internal/librarian/ruby/install_test.go index 6f3b6088dc5..0de64d75d37 100644 --- a/internal/librarian/ruby/install_test.go +++ b/internal/librarian/ruby/install_test.go @@ -23,6 +23,37 @@ import ( "github.com/googleapis/librarian/internal/config" ) +func TestInstall(t *testing.T) { + stubDir := t.TempDir() + gemStubPath := filepath.Join(stubDir, "gem") + recordFile := filepath.Join(t.TempDir(), "calls.txt") + stubContent := "#!/bin/sh\necho \"$*\" >> \"" + recordFile + "\"\nexit 0\n" + if err := os.WriteFile(gemStubPath, []byte(stubContent), 0o755); err != nil { + t.Fatal(err) + } + t.Setenv("PATH", stubDir+string(filepath.ListSeparator)+os.Getenv("PATH")) + tools := &config.Tools{ + Gem: []*config.GemTool{ + { + Name: "gapic-generator", + Version: "1.2.3", + }, + }, + } + if err := Install(t.Context(), tools); err != nil { + t.Fatalf("Install() returned unexpected error: %v", err) + } + data, err := os.ReadFile(recordFile) + if err != nil { + t.Fatalf("failed to read call records: %v", err) + } + got := string(data) + want := "install gapic-generator -v 1.2.3 --no-document\n" + if got != want { + t.Errorf("gem called with = %q, want %q", got, want) + } +} + func TestInstallDir(t *testing.T) { binDir := t.TempDir() t.Setenv("LIBRARIAN_BIN", binDir) From 99929121b693b9e9ac912ae96cac9a6ec0cf0330 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:53:02 -0400 Subject: [PATCH 5/6] add librarian install --- internal/librarian/librarian.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/internal/librarian/librarian.go b/internal/librarian/librarian.go index 08167e07c3e..13acc7bcc51 100644 --- a/internal/librarian/librarian.go +++ b/internal/librarian/librarian.go @@ -30,6 +30,7 @@ import ( "github.com/googleapis/librarian/internal/librarian/nodejs" "github.com/googleapis/librarian/internal/librarian/php" "github.com/googleapis/librarian/internal/librarian/python" + "github.com/googleapis/librarian/internal/librarian/ruby" "github.com/googleapis/librarian/internal/librarian/rust" "github.com/googleapis/librarian/internal/tool/protoc" "github.com/googleapis/librarian/internal/yaml" @@ -123,6 +124,8 @@ Examples: return php.Install(ctx, tools) case config.LanguagePython: return python.Install(ctx) + case config.LanguageRuby: + return ruby.Install(ctx, tools) case config.LanguageRust: return rust.Install(ctx, tools) default: From ba5458cfdab669ad1e4ac0d0b8ed7238c577b734 Mon Sep 17 00:00:00 2001 From: Joe Wang <106995533+JoeWang1127@users.noreply.github.com> Date: Fri, 10 Jul 2026 14:38:21 -0400 Subject: [PATCH 6/6] review --- internal/librarian/ruby/install_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/librarian/ruby/install_test.go b/internal/librarian/ruby/install_test.go index 0de64d75d37..ccb99a4041e 100644 --- a/internal/librarian/ruby/install_test.go +++ b/internal/librarian/ruby/install_test.go @@ -15,6 +15,7 @@ package ruby import ( + "bytes" "errors" "os" "path/filepath" @@ -47,10 +48,9 @@ func TestInstall(t *testing.T) { if err != nil { t.Fatalf("failed to read call records: %v", err) } - got := string(data) - want := "install gapic-generator -v 1.2.3 --no-document\n" - if got != want { - t.Errorf("gem called with = %q, want %q", got, want) + want := []byte("install gapic-generator -v 1.2.3 --no-document\n") + if !bytes.Equal(data, want) { + t.Errorf("gem called with = %q, want %q", data, want) } }