Skip to content

Commit 5883357

Browse files
zfralishmattkram
andauthored
fix!: Remove anaconda symlink to avoid shadowing in conda's base environment (#239)
Co-authored-by: mattkram <mkramer@anaconda.com>
1 parent 9df43af commit 5883357

7 files changed

Lines changed: 41 additions & 71 deletions

File tree

src/anaconda_cli.rs

Lines changed: 4 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
use std::process::Command;
2-
31
use crate::context::CommandContext;
42
use crate::paths;
53
use crate::tools;
64

75
pub async fn run_bootstrap(ctx: &mut CommandContext) -> Result<(), String> {
8-
let anaconda_bin = paths::bin_path("anaconda");
9-
10-
if anaconda_bin.exists() {
6+
if paths::tool_prefix("anaconda-cli").exists() {
117
eprintln!("anaconda-cli is already installed");
128
return Ok(());
139
}
@@ -26,36 +22,7 @@ pub fn run_subcommand(
2622
subcommand: &str,
2723
args: &[String],
2824
) -> Result<(), String> {
29-
let anaconda_bin = paths::bin_path("anaconda");
30-
31-
if !anaconda_bin.exists() {
32-
let msg = format!(
33-
"anaconda not found at {}. Run `ana bootstrap` first.",
34-
anaconda_bin.display()
35-
);
36-
tracing::error!("{}", msg);
37-
return Err(msg);
38-
}
39-
40-
run_anaconda_command(&anaconda_bin, subcommand, args)
41-
}
42-
43-
fn run_anaconda_command(
44-
anaconda_bin: &std::path::Path,
45-
subcommand: &str,
46-
args: &[String],
47-
) -> Result<(), String> {
48-
let status = Command::new(anaconda_bin)
49-
.arg(subcommand)
50-
.args(args)
51-
.status()
52-
.map_err(|e| format!("Failed to run anaconda: {}", e))?;
53-
54-
if status.success() {
55-
Ok(())
56-
} else {
57-
let msg = format!("anaconda exited with code {}", status.code().unwrap_or(1));
58-
tracing::error!("{}", msg);
59-
Err(msg)
60-
}
25+
let mut full_args = vec![subcommand.to_string()];
26+
full_args.extend(args.iter().cloned());
27+
tools::run_tool_binary("anaconda-cli", "anaconda", &full_args).map_err(|e| format!("{:?}", e))
6128
}

src/cli.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ pub fn parse() -> (Action, LogLevel) {
520520

521521
let cli = match Cli::from_arg_matches(&matches) {
522522
Ok(c) => c,
523-
Err(e) => return handle_parse_error(e.into()),
523+
Err(e) => return handle_parse_error(e),
524524
};
525525

526526
let level: LogLevel = cli.verbose.into();

src/mcp/run.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::ui::status;
66
/// Run the `anaconda mcp` command with the given arguments.
77
/// Auto-installs anaconda-cli if not present.
88
pub async fn run(ctx: &mut CommandContext, args: &[String]) -> miette::Result<()> {
9-
if !paths::bin_path("anaconda").exists() {
9+
if !paths::tool_prefix("anaconda-cli").exists() {
1010
status::info("Installing anaconda-cli...");
1111
tools::install::install_tool(ctx, "anaconda-cli").await?;
1212
status::blank_line();

src/telemetry/otel.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ mod tests {
113113
fn test_serializable_value_from_otel() {
114114
let string_val = Value::String("test".into());
115115
let int_val = Value::I64(42);
116-
let float_val = Value::F64(3.14);
116+
let float_val = Value::F64(2.72);
117117
let bool_val = Value::Bool(true);
118118

119119
assert_eq!(
@@ -123,7 +123,7 @@ mod tests {
123123
assert_eq!(SerializableValue::from(int_val), SerializableValue::Int(42));
124124
assert_eq!(
125125
SerializableValue::from(float_val),
126-
SerializableValue::Float(3.14)
126+
SerializableValue::Float(2.72)
127127
);
128128
assert_eq!(
129129
SerializableValue::from(bool_val),
@@ -136,7 +136,7 @@ mod tests {
136136
let values = vec![
137137
SerializableValue::String("hello".to_string()),
138138
SerializableValue::Int(42),
139-
SerializableValue::Float(3.14),
139+
SerializableValue::Float(2.72),
140140
SerializableValue::Bool(true),
141141
];
142142

src/telemetry/spool.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ mod tests {
125125
let tmp_files: Vec<_> = fs::read_dir(&pending_dir)
126126
.unwrap()
127127
.filter_map(|e| e.ok())
128-
.filter(|e| e.path().extension().map_or(false, |ext| ext == "tmp"))
128+
.filter(|e| e.path().extension().is_some_and(|ext| ext == "tmp"))
129129
.collect();
130130

131131
assert!(tmp_files.is_empty(), "No .tmp files should remain");
@@ -238,7 +238,7 @@ mod tests {
238238
},
239239
TelemetryEvent::Histogram {
240240
name: "histogram1".to_string(),
241-
value: 3.14,
241+
value: 2.72,
242242
attributes: HashMap::new(),
243243
},
244244
];

src/tools/specs.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,9 @@ const TOOLS: &[Tool] = &[
1616
Tool {
1717
name: "anaconda-cli",
1818
lockfile: include_str!("../../tool-specs/anaconda-cli/pixi.lock"),
19-
binaries: if cfg![unix] {
20-
&[&["bin", "anaconda"]]
21-
} else {
22-
&[&["Scripts", "anaconda"]]
23-
},
19+
// No symlink - anaconda-cli is only accessed via ana subcommands (e.g., ana mcp)
20+
// to avoid shadowing users' existing anaconda command from anaconda-auth
21+
binaries: &[],
2422
experimental: None,
2523
},
2624
#[cfg(unix)]

tests/test_cli.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -267,27 +267,20 @@ def test_bootstrap_installs_anaconda_cli(
267267
assert tool_dir.exists(), f"Tool directory not found: {tool_dir}"
268268
assert tool_dir.is_dir()
269269

270-
def test_bootstrap_creates_symlinked_binary(
270+
def test_bootstrap_does_not_create_symlink(
271271
self, run_ana: AnaRunner, fake_home: Path
272272
) -> None:
273-
"""Test that bootstrap creates a symlinked anaconda binary in ~/.ana/bin."""
273+
"""Test that bootstrap does NOT create an anaconda symlink in ~/.ana/bin.
274+
275+
anaconda-cli is only accessible via ana subcommands to avoid shadowing
276+
the user's existing anaconda command from anaconda-auth.
277+
"""
274278
result = run_ana("bootstrap")
275279
assert result.returncode == 0
276280

277-
# Verify the symlinked binary exists
281+
# Verify NO symlink is created
278282
bin_path = fake_home / ".ana" / "bin" / ANACONDA_BIN
279-
assert bin_path.exists(), f"Binary not found: {bin_path}"
280-
tools_dir = fake_home / ".ana" / "tools"
281-
if IS_WINDOWS:
282-
shim_cfg = tools_dir / "shims.cfg"
283-
assert (
284-
"anaconda=anaconda-cli\\Scripts\\anaconda.exe\r\n"
285-
in shim_cfg.read_text(newline="")
286-
)
287-
else:
288-
src_file = tools_dir / "anaconda-cli" / "bin" / "anaconda"
289-
assert bin_path.is_symlink(), f"Binary is not a symlink: {bin_path}"
290-
assert bin_path.samefile(src_file)
283+
assert not bin_path.exists(), f"Symlink should not be created: {bin_path}"
291284

292285
def test_bootstrap_already_installed(
293286
self, run_ana: AnaRunner, fake_home: Path
@@ -297,9 +290,9 @@ def test_bootstrap_already_installed(
297290
first_result = run_ana("bootstrap")
298291
assert first_result.returncode == 0
299292

300-
# Verify installation exists
301-
bin_path = fake_home / ".ana" / "bin" / ANACONDA_BIN
302-
assert bin_path.exists()
293+
# Verify tool prefix exists
294+
tool_dir = fake_home / ".ana" / "tools" / "anaconda-cli"
295+
assert tool_dir.exists()
303296

304297
# Second run should indicate already installed
305298
second_result = run_ana("bootstrap")
@@ -309,13 +302,25 @@ def test_bootstrap_already_installed(
309302
def test_bootstrap_anaconda_binary_runs(
310303
self, run_ana: AnaRunner, fake_home: Path
311304
) -> None:
312-
"""Test that the installed anaconda binary runs and outputs help."""
305+
"""Test that the installed anaconda binary runs from the tool prefix."""
313306
result = run_ana("bootstrap")
314307
assert result.returncode == 0
315308

316-
# Run the installed anaconda binary
317-
bin_path = fake_home / ".ana" / "bin" / ANACONDA_BIN
318-
assert bin_path.exists()
309+
# Run the anaconda binary directly from the tool prefix
310+
if IS_WINDOWS:
311+
bin_path = (
312+
fake_home
313+
/ ".ana"
314+
/ "tools"
315+
/ "anaconda-cli"
316+
/ "Scripts"
317+
/ "anaconda.exe"
318+
)
319+
else:
320+
bin_path = (
321+
fake_home / ".ana" / "tools" / "anaconda-cli" / "bin" / "anaconda"
322+
)
323+
assert bin_path.exists(), f"Binary not found: {bin_path}"
319324

320325
proc = subprocess.run(
321326
[str(bin_path), "--help"],

0 commit comments

Comments
 (0)