use super::build_target::BuildTarget;
use std::path::Path;
use std::process::{Command, Stdio};
pub fn build_agent_project(
agent_name: &str,
build_target: &BuildTarget,
shared_target_dir: Option<&Path>,
) -> Result<(), std::io::Error> {
let project_path = super::agent_workspace_path(agent_name);
run_cargo_compile_in_path(&project_path, "build", build_target, shared_target_dir)
}
pub fn check_agent_project(
agent_name: &str,
build_target: &BuildTarget,
shared_target_dir: Option<&Path>,
) -> Result<(), std::io::Error> {
let project_path = super::agent_workspace_path(agent_name);
run_cargo_compile_in_path(&project_path, "check", build_target, shared_target_dir)
}
pub(crate) fn build_workspace(
project_path: &Path,
build_target: &BuildTarget,
) -> Result<(), std::io::Error> {
run_cargo_compile_in_path(project_path, "build", build_target, None)
}
fn run_cargo_compile_in_path(
project_path: &Path,
command: &str,
build_target: &BuildTarget,
shared_target_dir: Option<&Path>,
) -> Result<(), std::io::Error> {
if !project_path.exists() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Agent path does not exist",
));
}
let mut cargo_command =
prepare_cargo_compile_command(project_path, command, build_target, shared_target_dir);
let status = cargo_command.status()?;
if !status.success() {
let target_detail = build_target
.cargo_target()
.map(|target| {
format!(
" for target '{target}'. See compiler output above for missing rustup target, linker, or SDK details."
)
})
.unwrap_or_default();
return Err(std::io::Error::new(
std::io::ErrorKind::Other,
format!("Cargo {command} failed{target_detail}"),
));
}
Ok(())
}
fn prepare_cargo_compile_command(
project_path: &Path,
command: &str,
build_target: &BuildTarget,
shared_target_dir: Option<&Path>,
) -> Command {
let mut cargo_command = Command::new("cargo");
cargo_command
.args(build_target.cargo_args(command))
.current_dir(project_path)
.stdout(Stdio::inherit())
.stderr(Stdio::inherit());
if let Some(target_dir) = shared_target_dir {
cargo_command.env("CARGO_TARGET_DIR", target_dir);
}
cargo_command
}
#[cfg(test)]
mod tests {
use super::prepare_cargo_compile_command;
use crate::agent_builder::build_target::BuildTarget;
use std::ffi::OsString;
use std::path::Path;
#[test]
fn shared_target_dir_is_applied_to_check_commands() {
let build_target = BuildTarget::from_cli(Some("x86_64-pc-windows-msvc"))
.expect("explicit target should resolve");
let command = prepare_cargo_compile_command(
Path::new("/tmp/demo-agent"),
"check",
&build_target,
Some(Path::new("/tmp/shared-target")),
);
let args = command
.get_args()
.map(|value| value.to_os_string())
.collect::<Vec<_>>();
let current_dir = command
.get_current_dir()
.expect("current dir should be set")
.to_path_buf();
let envs = command
.get_envs()
.map(|(key, value)| (key.to_os_string(), value.map(|entry| entry.to_os_string())))
.collect::<Vec<_>>();
assert_eq!(current_dir, Path::new("/tmp/demo-agent"));
assert_eq!(
args,
vec![
OsString::from("check"),
OsString::from("--target"),
OsString::from("x86_64-pc-windows-msvc"),
]
);
assert!(envs.iter().any(|(key, value)| {
key == "CARGO_TARGET_DIR"
&& value.as_ref() == Some(&OsString::from("/tmp/shared-target"))
}));
}
#[test]
fn shared_target_dir_is_applied_to_build_commands() {
let build_target = BuildTarget::from_cli(Some("x86_64-pc-windows-msvc"))
.expect("explicit target should resolve");
let command = prepare_cargo_compile_command(
Path::new("/tmp/demo-agent"),
"build",
&build_target,
Some(Path::new("/tmp/shared-target")),
);
let args = command
.get_args()
.map(|value| value.to_os_string())
.collect::<Vec<_>>();
let current_dir = command
.get_current_dir()
.expect("current dir should be set")
.to_path_buf();
let envs = command
.get_envs()
.map(|(key, value)| (key.to_os_string(), value.map(|entry| entry.to_os_string())))
.collect::<Vec<_>>();
assert_eq!(current_dir, Path::new("/tmp/demo-agent"));
assert_eq!(
args,
vec![
OsString::from("build"),
OsString::from("--target"),
OsString::from("x86_64-pc-windows-msvc"),
]
);
assert!(envs.iter().any(|(key, value)| {
key == "CARGO_TARGET_DIR"
&& value.as_ref() == Some(&OsString::from("/tmp/shared-target"))
}));
}
#[test]
fn build_commands_do_not_force_shared_target_dir_by_default() {
let build_target = BuildTarget::from_cli(None).expect("default target should resolve");
let command = prepare_cargo_compile_command(
Path::new("/tmp/demo-agent"),
"build",
&build_target,
None,
);
assert!(!command.get_envs().any(|(key, _)| key == "CARGO_TARGET_DIR"));
}
}