@@ -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+
365432fn load_tests_recursive (
366433 base : & Path ,
367434 dir : & Path ,
0 commit comments