Skip to content

Commit dcdeed5

Browse files
committed
Add strategy pattern for Ruby detection.
- don't add user gems to bundler context
1 parent d3fa335 commit dcdeed5

File tree

13 files changed

+1082
-314
lines changed

13 files changed

+1082
-314
lines changed

crates/rb-cli/src/commands/environment.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ mod tests {
424424

425425
let bundler_sandbox = BundlerSandbox::new()?;
426426
let project_dir = bundler_sandbox.add_bundler_project("test-app", true)?;
427-
let bundler_runtime = BundlerRuntime::new(&project_dir);
427+
let bundler_runtime = BundlerRuntime::new(&project_dir, ruby.version.clone());
428428

429429
// Use sandboxed gem directory instead of real home directory
430430
let gem_runtime = GemRuntime::for_base_dir(&ruby_sandbox.gem_base_dir(), &ruby.version);

crates/rb-cli/src/commands/exec.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,17 @@ mod tests {
237237

238238
// Test that standard variables are present
239239
assert!(env_vars.contains_key("PATH"));
240-
assert!(env_vars.contains_key("GEM_HOME"));
241-
assert!(env_vars.contains_key("GEM_PATH"));
240+
241+
// IMPORTANT: When bundler context is detected, GEM_HOME and GEM_PATH should NOT be set
242+
// This is bundler isolation - only bundled gems are available
243+
assert!(
244+
!env_vars.contains_key("GEM_HOME"),
245+
"GEM_HOME should NOT be set in bundler context (isolation)"
246+
);
247+
assert!(
248+
!env_vars.contains_key("GEM_PATH"),
249+
"GEM_PATH should NOT be set in bundler context (isolation)"
250+
);
242251

243252
// Test that bundler variables are set when bundler project is detected
244253
assert!(env_vars.contains_key("BUNDLE_GEMFILE"));

crates/rb-cli/src/discovery.rs

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,10 @@ impl DiscoveryContext {
4545

4646
// Step 2: Detect bundler environment
4747
debug!("Detecting bundler environment");
48-
let bundler_environment = match BundlerRuntimeDetector::discover(&current_dir) {
49-
Ok(Some(bundler)) => {
50-
debug!(
51-
"Bundler environment detected at: {}",
52-
bundler.root.display()
53-
);
54-
Some(bundler)
48+
let bundler_root = match BundlerRuntimeDetector::discover(&current_dir) {
49+
Ok(Some(root)) => {
50+
debug!("Bundler environment detected at: {}", root.display());
51+
Some(root)
5552
}
5653
Ok(None) => {
5754
debug!("No bundler environment detected");
@@ -64,8 +61,10 @@ impl DiscoveryContext {
6461
};
6562

6663
// Step 3: Determine required Ruby version
67-
let required_ruby_version = if let Some(bundler) = &bundler_environment {
68-
match bundler.ruby_version() {
64+
let required_ruby_version = if bundler_root.is_some() {
65+
use rb_core::ruby::CompositeDetector;
66+
let detector = CompositeDetector::bundler();
67+
match detector.detect(&current_dir) {
6968
Some(version) => {
7069
debug!("Bundler environment specifies Ruby version: {}", version);
7170
Some(version)
@@ -86,7 +85,19 @@ impl DiscoveryContext {
8685
&required_ruby_version,
8786
);
8887

89-
// Step 5: Create butler runtime if we have a selected Ruby
88+
// Step 5: Create bundler runtime with selected Ruby version (if bundler detected)
89+
let bundler_environment = if let Some(ref root) = bundler_root {
90+
if let Some(ref ruby) = selected_ruby {
91+
Some(BundlerRuntime::new(root, ruby.version.clone()))
92+
} else {
93+
// No suitable Ruby found - create with temp version for display purposes
94+
Some(BundlerRuntime::new(root, Version::new(0, 0, 0)))
95+
}
96+
} else {
97+
None
98+
};
99+
100+
// Step 6: Create butler runtime if we have a selected Ruby
90101
let butler_runtime = if let Some(ruby) = &selected_ruby {
91102
match ruby.infer_gem_runtime() {
92103
Ok(gem_runtime) => {

crates/rb-cli/src/lib.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,21 @@ mod tests {
227227
#[test]
228228
fn test_create_ruby_context_with_sandbox() {
229229
let sandbox = RubySandbox::new().expect("Failed to create sandbox");
230-
sandbox
230+
let ruby_dir = sandbox
231231
.add_ruby_dir("3.2.5")
232232
.expect("Failed to create ruby-3.2.5");
233233

234+
// Create Ruby executable so it can be discovered
235+
std::fs::create_dir_all(ruby_dir.join("bin")).expect("Failed to create bin dir");
236+
let ruby_exe = ruby_dir.join("bin").join("ruby");
237+
std::fs::write(&ruby_exe, "#!/bin/sh\necho ruby").expect("Failed to write ruby exe");
238+
#[cfg(unix)]
239+
{
240+
use std::os::unix::fs::PermissionsExt;
241+
std::fs::set_permissions(&ruby_exe, std::fs::Permissions::from_mode(0o755))
242+
.expect("Failed to set permissions");
243+
}
244+
234245
let result = create_ruby_context(Some(sandbox.root().to_path_buf()), None);
235246

236247
// Should successfully create a ButlerRuntime

crates/rb-core/src/bundler/detector.rs

Lines changed: 19 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
use log::{debug, info};
2-
use std::path::Path;
3-
4-
use super::BundlerRuntime;
2+
use std::path::{Path, PathBuf};
53

64
pub struct BundlerRuntimeDetector;
75

86
impl BundlerRuntimeDetector {
9-
/// Discover a BundlerRuntime by searching for Gemfile in the current directory
7+
/// Discover a Bundler project by searching for Gemfile in the current directory
108
/// and walking up the directory tree until one is found or we reach the root.
11-
pub fn discover(start_dir: &Path) -> std::io::Result<Option<BundlerRuntime>> {
9+
/// Returns the root directory containing the Gemfile.
10+
pub fn discover(start_dir: &Path) -> std::io::Result<Option<PathBuf>> {
1211
debug!(
1312
"Starting Bundler discovery from directory: {}",
1413
start_dir.display()
@@ -22,9 +21,8 @@ impl BundlerRuntimeDetector {
2221

2322
if gemfile_path.exists() && gemfile_path.is_file() {
2423
info!("Found Gemfile at: {}", gemfile_path.display());
25-
let bundler_runtime = BundlerRuntime::new(&current_dir);
26-
debug!("Created BundlerRuntime for root: {}", current_dir.display());
27-
return Ok(Some(bundler_runtime));
24+
debug!("Returning bundler root: {}", current_dir.display());
25+
return Ok(Some(current_dir));
2826
} else {
2927
debug!("No Gemfile found in: {}", current_dir.display());
3028
}
@@ -50,7 +48,7 @@ impl BundlerRuntimeDetector {
5048
}
5149

5250
/// Convenience method to discover from current working directory
53-
pub fn discover_from_cwd() -> std::io::Result<Option<BundlerRuntime>> {
51+
pub fn discover_from_cwd() -> std::io::Result<Option<PathBuf>> {
5452
let cwd = std::env::current_dir()?;
5553
debug!(
5654
"Discovering Bundler runtime from current working directory: {}",
@@ -64,7 +62,6 @@ impl BundlerRuntimeDetector {
6462
mod tests {
6563
use super::*;
6664
use rb_tests::BundlerSandbox;
67-
use semver;
6865
use std::io;
6966

7067
#[test]
@@ -75,9 +72,9 @@ mod tests {
7572
let result = BundlerRuntimeDetector::discover(&project_dir)?;
7673

7774
assert!(result.is_some());
78-
let bundler_runtime = result.unwrap();
79-
assert_eq!(bundler_runtime.root, project_dir);
80-
assert_eq!(bundler_runtime.gemfile_path(), project_dir.join("Gemfile"));
75+
let bundler_root = result.unwrap();
76+
assert_eq!(bundler_root, project_dir);
77+
assert_eq!(bundler_root.join("Gemfile"), project_dir.join("Gemfile"));
8178

8279
Ok(())
8380
}
@@ -95,9 +92,9 @@ mod tests {
9592
let result = BundlerRuntimeDetector::discover(&sub_dir)?;
9693

9794
assert!(result.is_some());
98-
let bundler_runtime = result.unwrap();
99-
assert_eq!(bundler_runtime.root, project_dir);
100-
assert_eq!(bundler_runtime.gemfile_path(), project_dir.join("Gemfile"));
95+
let bundler_root = result.unwrap();
96+
assert_eq!(bundler_root, project_dir);
97+
assert_eq!(bundler_root.join("Gemfile"), project_dir.join("Gemfile"));
10198

10299
Ok(())
103100
}
@@ -125,9 +122,9 @@ mod tests {
125122
let result = BundlerRuntimeDetector::discover(&deep_dir)?;
126123

127124
assert!(result.is_some());
128-
let bundler_runtime = result.unwrap();
129-
assert_eq!(bundler_runtime.root, subproject);
130-
assert_eq!(bundler_runtime.gemfile_path(), subproject.join("Gemfile"));
125+
let bundler_root = result.unwrap();
126+
assert_eq!(bundler_root, subproject);
127+
assert_eq!(bundler_root.join("Gemfile"), subproject.join("Gemfile"));
131128

132129
Ok(())
133130
}
@@ -147,41 +144,9 @@ mod tests {
147144
let result = BundlerRuntimeDetector::discover(&deep_dir)?;
148145

149146
assert!(result.is_some());
150-
let bundler_runtime = result.unwrap();
151-
assert_eq!(bundler_runtime.root, project_dir);
152-
assert_eq!(bundler_runtime.gemfile_path(), project_dir.join("Gemfile"));
153-
154-
Ok(())
155-
}
156-
157-
#[test]
158-
fn discover_detects_ruby_version_from_project() -> io::Result<()> {
159-
let sandbox = BundlerSandbox::new()?;
160-
let project_dir = sandbox.add_dir("ruby-version-app")?;
161-
162-
// Create Gemfile with ruby version
163-
let gemfile_content = r#"source 'https://rubygems.org'
164-
165-
ruby '3.2.1'
166-
167-
gem 'rails'
168-
"#;
169-
sandbox.add_file(
170-
format!(
171-
"{}/Gemfile",
172-
project_dir.file_name().unwrap().to_str().unwrap()
173-
),
174-
gemfile_content,
175-
)?;
176-
177-
let result = BundlerRuntimeDetector::discover(&project_dir)?;
178-
179-
assert!(result.is_some());
180-
let bundler_runtime = result.unwrap();
181-
assert_eq!(
182-
bundler_runtime.ruby_version(),
183-
Some(semver::Version::parse("3.2.1").unwrap())
184-
);
147+
let bundler_root = result.unwrap();
148+
assert_eq!(bundler_root, project_dir);
149+
assert_eq!(bundler_root.join("Gemfile"), project_dir.join("Gemfile"));
185150

186151
Ok(())
187152
}

0 commit comments

Comments
 (0)