Skip to content

Commit 80a3a0c

Browse files
committed
fix: install base tools, runtimes, and filter redundant deps for Basilica
- Install base build tools (git, curl, build-essential, python3, pip, pytest) on every Basilica container before running install commands - Add python/pip symlinks for Ubuntu compatibility - Run runtime_install as separate step (not filtered by filter_install_command) - Add yarn, pnpm to Node runtime install via npm install -g - Filter out redundant apt-get install of runtimes already handled - Filter out corepack enable (yarn/pnpm already installed globally) - Add runtime_install field to WorkspaceConfig (skip in serde) Tested on 50 tasks: 60% p2p pass rate (86% projected with pytest fix)
1 parent 25b2e94 commit 80a3a0c

File tree

3 files changed

+82
-10
lines changed

3 files changed

+82
-10
lines changed

src/executor.rs

Lines changed: 68 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,53 @@ fn filter_install_command(cmd: &str) -> String {
8383
"apt-get", "apt ", "dpkg", "yum ", "dnf ", "pacman ", "apk ",
8484
];
8585

86+
// Runtime packages that are already installed by runtime_install_command.
87+
// Strip these apt-get install commands to avoid "held broken packages" conflicts.
88+
let redundant_runtime_packages = [
89+
"golang", "nodejs", "npm", "node", "default-jdk", "openjdk",
90+
];
91+
8692
let parts: Vec<&str> = cmd.split("&&").collect();
8793
let processed: Vec<String> = parts
8894
.iter()
89-
.map(|p| {
95+
.filter_map(|p| {
9096
let trimmed = p.trim();
97+
if trimmed.is_empty() {
98+
return None;
99+
}
100+
let lower = trimmed.to_lowercase();
101+
// Skip corepack enable (yarn/pnpm already installed globally by runtime)
102+
if lower.starts_with("corepack enable") || lower.starts_with("sudo corepack") {
103+
return None;
104+
}
105+
// Skip redundant apt-get install of runtimes already handled
106+
if (lower.contains("apt-get install") || lower.contains("apt install"))
107+
&& redundant_runtime_packages.iter().any(|pkg| lower.contains(pkg))
108+
// Keep if it also installs other packages (heuristic: >3 packages)
109+
&& !lower.contains("build-essential")
110+
{
111+
// Check if this ONLY installs runtime packages
112+
let non_runtime = trimmed.split_whitespace()
113+
.filter(|w| !w.starts_with('-') && !redundant_runtime_packages.iter().any(|pkg| w.to_lowercase().contains(pkg)))
114+
.filter(|w| !["sudo", "apt-get", "apt", "install", "update", "&&", "-y", "-qq"].contains(w))
115+
.count();
116+
if non_runtime == 0 {
117+
return None; // pure runtime install, skip it
118+
}
119+
}
120+
// Also skip apt-get update if it's standalone (already done by runtime install)
121+
if lower == "sudo apt-get update" || lower == "apt-get update"
122+
|| lower == "sudo apt-get update -qq" || lower == "apt-get update -qq"
123+
{
124+
return None;
125+
}
126+
91127
if trimmed.starts_with("sudo ") {
92-
fix_pip_pep668(trimmed)
128+
Some(fix_pip_pep668(trimmed))
93129
} else if root_prefixes.iter().any(|prefix| trimmed.starts_with(prefix)) {
94-
fix_pip_pep668(&format!("sudo {}", trimmed))
130+
Some(fix_pip_pep668(&format!("sudo {}", trimmed)))
95131
} else {
96-
fix_pip_pep668(trimmed)
132+
Some(fix_pip_pep668(trimmed))
97133
}
98134
})
99135
.collect();
@@ -717,6 +753,34 @@ async fn run_task_on_basilica(
717753
}
718754

719755
result.status = TaskStatus::InstallingDeps;
756+
757+
// Install base build tools + ensure python/pip/pytest are on PATH
758+
let base_tools = format!(
759+
"sudo apt-get update -qq && \
760+
sudo apt-get install -y -qq git curl build-essential python3 python3-pip python3-venv unzip > /dev/null 2>&1 && \
761+
sudo ln -sf $(command -v python3) /usr/local/bin/python 2>/dev/null; \
762+
sudo ln -sf $(command -v pip3) /usr/local/bin/pip 2>/dev/null; \
763+
sudo python3 -m pip install --break-system-packages pytest 2>/dev/null; true"
764+
);
765+
let (_, _, exit) = ssh_exec(host, port, user, &base_tools, timeout, ssh_key).await?;
766+
if exit != 0 {
767+
warn!("[{}] Base tools install failed (exit {})", task.id, exit);
768+
}
769+
770+
// Run runtime install (not filtered - installs Go/Node/Rust/Java from official sources)
771+
if let Some(ref runtime_cmd) = task.workspace.runtime_install {
772+
info!("[{}] Installing runtime on container: {}", task.id, &runtime_cmd[..runtime_cmd.len().min(120)]);
773+
let cmd = format!("cd {work_dir}/repo && {runtime_cmd}");
774+
let (_, stderr, exit) = ssh_exec(host, port, user, &cmd, timeout, ssh_key).await?;
775+
if exit != 0 {
776+
warn!(
777+
"[{}] Runtime install failed on container (exit {}): {}",
778+
task.id, exit, &stderr[..stderr.len().min(500)]
779+
);
780+
}
781+
}
782+
783+
// Run project install commands (filtered for sudo/pip)
720784
if let Some(ref install_cmds) = task.workspace.install {
721785
for cmd in install_cmds {
722786
let effective_cmd = filter_install_command(cmd);

src/task/mod.rs

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ pub struct WorkspaceConfig {
4040
pub patch: Option<String>,
4141
#[serde(default)]
4242
pub prompt: Option<String>,
43+
/// Runtime install command generated from install_config version fields.
44+
/// Executed before project install commands, without filter_install_command.
45+
#[serde(skip)]
46+
pub runtime_install: Option<String>,
4347
}
4448

4549
#[derive(Debug, Clone)]
@@ -352,12 +356,12 @@ pub fn parse_task(task_dir: &Path) -> Result<SweForgeTask> {
352356
}
353357
}
354358

355-
// Prepend runtime install commands derived from install_config version fields
359+
// Generate runtime install command from install_config version fields.
360+
// Stored separately so executor can run it without filter_install_command.
356361
if let Some(ref ic) = workspace.install_config {
357362
let runtime_cmd = runtime_install_command(ic);
358363
if !runtime_cmd.is_empty() {
359-
let installs = workspace.install.get_or_insert_with(Vec::new);
360-
installs.insert(0, runtime_cmd);
364+
workspace.runtime_install = Some(runtime_cmd);
361365
}
362366
}
363367

@@ -407,7 +411,9 @@ fn runtime_install_command(install_config: &std::collections::BTreeMap<String, S
407411
let v = node_ver.trim();
408412
cmds.push(format!(
409413
"curl -fsSL https://deb.nodesource.com/setup_{v}.x | sudo bash - && \
410-
sudo apt-get install -y nodejs"
414+
sudo apt-get install -y nodejs && \
415+
sudo corepack enable 2>/dev/null; \
416+
sudo npm install -g yarn pnpm 2>/dev/null; true"
411417
));
412418
}
413419

@@ -421,8 +427,9 @@ fn runtime_install_command(install_config: &std::collections::BTreeMap<String, S
421427
if let Some(java_ver) = install_config.get("java") {
422428
let v = java_ver.trim();
423429
cmds.push(format!(
424-
"sudo apt-get update -qq && sudo apt-get install -y -qq openjdk-{v}-jdk 2>/dev/null || \
425-
sudo apt-get install -y -qq default-jdk"
430+
"sudo apt-get update -qq && \
431+
sudo apt-get install -y -qq openjdk-{v}-jdk maven 2>/dev/null || \
432+
sudo apt-get install -y -qq default-jdk maven 2>/dev/null; true"
426433
));
427434
}
428435

src/task/registry.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@ fn convert_dataset_entry_to_task(entry: &DatasetEntry) -> Result<SweForgeTask> {
160160
difficulty_score: None,
161161
patch: Some(entry.patch.clone()),
162162
prompt: Some(entry.problem_statement.clone()),
163+
runtime_install: None,
163164
};
164165

165166
let test_script = build_test_script(&entry.test_patch, entry.fail_to_pass.as_deref());

0 commit comments

Comments
 (0)