Skip to content

Commit 25b2e94

Browse files
committed
feat: auto-install language runtimes from install_config version fields
- Add runtime_install_command() that reads go/node/rust/java versions from install_config and generates proper install commands - Go: reads go.mod for actual required version, downloads from go.dev - Node: installs via nodesource setup script - Rust: installs via rustup - Java: installs via apt openjdk package - Prepend runtime install as first install command in parse_task() - SSH commands source /etc/profile.d/*.sh for PATH persistence - Tested end-to-end: Go 1.24 installed from go.mod, tests pass
1 parent 63d8174 commit 25b2e94

File tree

2 files changed

+73
-1
lines changed

2 files changed

+73
-1
lines changed

src/executor.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,11 @@ async fn ssh_exec(
591591
) -> Result<(String, String, i32)> {
592592
let ssh_target = format!("{}@{}", user, host);
593593
let port_str = port.to_string();
594+
// Source /etc/profile.d scripts so runtime installs (Go, Node, Rust) are on PATH
595+
let wrapped_cmd = format!(
596+
"for f in /etc/profile.d/*.sh; do [ -r \"$f\" ] && . \"$f\"; done 2>/dev/null; {}",
597+
cmd
598+
);
594599
let mut args: Vec<&str> = vec![
595600
"ssh",
596601
"-o", "StrictHostKeyChecking=no",
@@ -601,7 +606,7 @@ async fn ssh_exec(
601606
if let Some(key) = ssh_key {
602607
args.extend_from_slice(&["-i", key]);
603608
}
604-
args.extend_from_slice(&["-p", &port_str, &ssh_target, cmd]);
609+
args.extend_from_slice(&["-p", &port_str, &ssh_target, &wrapped_cmd]);
605610
run_cmd(&args, Path::new("/tmp"), timeout, None).await
606611
}
607612

src/task/mod.rs

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,15 @@ pub fn parse_task(task_dir: &Path) -> Result<SweForgeTask> {
352352
}
353353
}
354354

355+
// Prepend runtime install commands derived from install_config version fields
356+
if let Some(ref ic) = workspace.install_config {
357+
let runtime_cmd = runtime_install_command(ic);
358+
if !runtime_cmd.is_empty() {
359+
let installs = workspace.install.get_or_insert_with(Vec::new);
360+
installs.insert(0, runtime_cmd);
361+
}
362+
}
363+
355364
Ok(SweForgeTask {
356365
id,
357366
workspace,
@@ -362,6 +371,64 @@ pub fn parse_task(task_dir: &Path) -> Result<SweForgeTask> {
362371
})
363372
}
364373

374+
/// Generate shell commands to install the correct language runtime on a fresh
375+
/// Ubuntu container. Reads version fields from install_config (go, node, rust,
376+
/// java) and returns a single shell string that installs the runtime via
377+
/// official binaries/version managers instead of apt packages.
378+
/// Generate a shell command to install the correct language runtime on a fresh
379+
/// Ubuntu container. Reads version fields from install_config and downloads
380+
/// the runtime via official binaries. The command is prepended to the install
381+
/// list so the runtime is available for subsequent install/test commands.
382+
///
383+
/// PATH modifications are persisted via /etc/profile.d so they survive across
384+
/// separate SSH invocations.
385+
fn runtime_install_command(install_config: &std::collections::BTreeMap<String, String>) -> String {
386+
let mut cmds: Vec<String> = Vec::new();
387+
388+
if install_config.contains_key("go") {
389+
// Install Go by reading the required version from go.mod at runtime.
390+
// Falls back to install_config version, then 1.23.0.
391+
// This handles existing tasks whose install_config has a stale version.
392+
let fallback = install_config.get("go").map(|v| {
393+
let v = if v.starts_with("1.") { v.as_str() } else { "1.23" };
394+
if v.matches('.').count() == 1 { format!("{v}.0") } else { v.to_string() }
395+
}).unwrap_or_else(|| "1.23.0".to_string());
396+
397+
cmds.push(format!(
398+
"GO_VER=$(grep -oP '^go\\s+\\K[0-9.]+' go.mod 2>/dev/null || echo '{fallback}'); \
399+
GO_VER=$(echo $GO_VER | awk -F. '{{if(NF==2) print $0\".0\"; else print $0}}'); \
400+
sudo rm -rf /usr/local/go && \
401+
curl -fsSL https://go.dev/dl/go${{GO_VER}}.linux-amd64.tar.gz | sudo tar -C /usr/local -xzf - && \
402+
echo 'export PATH=/usr/local/go/bin:$PATH' | sudo tee /etc/profile.d/go.sh > /dev/null"
403+
));
404+
}
405+
406+
if let Some(node_ver) = install_config.get("node") {
407+
let v = node_ver.trim();
408+
cmds.push(format!(
409+
"curl -fsSL https://deb.nodesource.com/setup_{v}.x | sudo bash - && \
410+
sudo apt-get install -y nodejs"
411+
));
412+
}
413+
414+
if install_config.contains_key("rust") {
415+
cmds.push(
416+
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
417+
echo 'export PATH=$HOME/.cargo/bin:$PATH' | sudo tee /etc/profile.d/rust.sh > /dev/null".to_string()
418+
);
419+
}
420+
421+
if let Some(java_ver) = install_config.get("java") {
422+
let v = java_ver.trim();
423+
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"
426+
));
427+
}
428+
429+
cmds.join(" && ")
430+
}
431+
365432
fn load_tests_recursive(
366433
base: &Path,
367434
dir: &Path,

0 commit comments

Comments
 (0)