use super::*;
pub(crate) fn case_rust_source_dir(case: &TestQemuCase) -> PathBuf {
case.case_dir.join("rust")
}
pub(super) fn rust_musl_target(arch: &str) -> anyhow::Result<&'static str> {
match arch {
"aarch64" => Ok("aarch64-unknown-linux-musl"),
"riscv64" => Ok("riscv64gc-unknown-linux-musl"),
"x86_64" => Ok("x86_64-unknown-linux-musl"),
"loongarch64" => Ok("loongarch64-unknown-linux-musl"),
_ => bail!(
"Rust-based QEMU test cases are only supported on aarch64, riscv64, x86_64, and \
loongarch64, but got `{arch}`"
),
}
}
pub(crate) fn prepare_rust_case_assets_sync(
arch: &str,
case: &TestQemuCase,
case_rootfs: &Path,
layout: &case_assets::CaseAssetLayout,
config: &CaseAssetConfig,
) -> anyhow::Result<()> {
let rust_dir = case_rust_source_dir(case);
ensure!(
rust_dir.is_dir(),
"missing case Rust source directory `{}`",
rust_dir.display()
);
let cargo_toml = rust_dir.join("Cargo.toml");
ensure!(
cargo_toml.is_file(),
"missing Cargo.toml in Rust case source directory `{}`",
rust_dir.display()
);
let target_triple = rust_musl_target(arch)?;
case_assets::reset_dir(&layout.overlay_dir)?;
case_assets::reset_dir(&layout.staging_root)?;
case_assets::reset_dir(&layout.command_wrapper_dir)?;
case_assets::reset_dir(&layout.cross_bin_dir)?;
fs::create_dir_all(&layout.apk_cache_dir)
.with_context(|| format!("failed to create {}", layout.apk_cache_dir.display()))?;
let mut add_target = Command::new("rustup");
add_target.arg("target").arg("add").arg(target_triple);
add_target
.exec()
.with_context(|| format!("failed to install Rust target `{target_triple}` via rustup"))?;
crate::rootfs::inject::extract_rootfs(case_rootfs, &layout.staging_root)?;
(config.prepare_staging_root)(&layout.staging_root)?;
write_musl_loader_search_path(arch, &layout.staging_root)?;
let spec = cross_compile_spec(arch)?;
let qemu_runner = find_host_binary_candidates(qemu_user_binary_names(arch)?)?;
write_cross_bin_wrappers(layout, spec, &qemu_runner)?;
let prebuild_script = case_rust_prebuild_script_path(case);
if prebuild_script.is_file() {
let extra_script_envs = prepare_guest_package_env(config, &layout.staging_root)?;
let prebuild_env =
prepare_guest_prebuild_env(arch, case, layout, extra_script_envs, config)?;
let mut command = build_prebuild_command(case, &prebuild_script, layout, &prebuild_env)?;
command.current_dir(&rust_dir);
command
.exec()
.with_context(|| format!("failed to run rust case prebuild.sh for `{}`", case.name))?;
}
let linker_env_key = format!(
"CARGO_TARGET_{}_LINKER",
target_triple.to_uppercase().replace('-', "_")
);
let linker_path = layout.cross_bin_dir.join("ld");
let mut cmd = Command::new("cargo");
cmd.arg("build")
.arg("--release")
.arg("--target")
.arg(target_triple)
.arg("--manifest-path")
.arg(&cargo_toml)
.arg("--target-dir")
.arg(&layout.build_dir)
.env("RUSTFLAGS", "-C target-feature=+crt-static")
.env(&linker_env_key, &linker_path)
.env(
"PKG_CONFIG_LIBDIR",
format!(
"{}:{}",
layout.staging_root.join("usr/lib/pkgconfig").display(),
layout.staging_root.join("usr/share/pkgconfig").display()
),
)
.env(
"PKG_CONFIG_SYSROOT_DIR",
layout.staging_root.display().to_string(),
)
.env("PKG_CONFIG_PATH", "");
cmd.exec().with_context(|| {
format!(
"failed to cross-compile Rust case `{}` for target `{target_triple}`",
case.name
)
})?;
let bin_name = rust_case_bin_name(&cargo_toml)?;
let bin_src = layout
.build_dir
.join(target_triple)
.join("release")
.join(&bin_name);
ensure!(
bin_src.is_file(),
"expected compiled Rust binary at `{}` but it was not found",
bin_src.display()
);
let dest_bin_dir = layout.overlay_dir.join("usr/bin");
fs::create_dir_all(&dest_bin_dir)
.with_context(|| format!("failed to create {}", dest_bin_dir.display()))?;
let bin_dst = dest_bin_dir.join(&bin_name);
fs::copy(&bin_src, &bin_dst).with_context(|| {
format!(
"failed to copy {} to {}",
bin_src.display(),
bin_dst.display()
)
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let mut perms = fs::metadata(&bin_dst)
.with_context(|| format!("failed to stat {}", bin_dst.display()))?
.permissions();
perms.set_mode(0o755);
fs::set_permissions(&bin_dst, perms)
.with_context(|| format!("failed to chmod {}", bin_dst.display()))?;
}
crate::rootfs::inject::inject_overlay(case_rootfs, &layout.overlay_dir)
}
pub(super) fn rust_case_bin_name(cargo_toml: &Path) -> anyhow::Result<String> {
#[derive(serde::Deserialize)]
struct CargoToml {
package: Option<CargoPackage>,
bin: Option<Vec<CargoBin>>,
}
#[derive(serde::Deserialize)]
struct CargoPackage {
name: String,
}
#[derive(serde::Deserialize)]
struct CargoBin {
name: String,
}
let content = fs::read_to_string(cargo_toml)
.with_context(|| format!("failed to read {}", cargo_toml.display()))?;
let manifest: CargoToml = toml::from_str(&content)
.with_context(|| format!("failed to parse {}", cargo_toml.display()))?;
if let Some(bins) = manifest.bin
&& let Some(first) = bins.into_iter().next()
{
return Ok(first.name);
}
manifest.package.map(|p| p.name).ok_or_else(|| {
anyhow::anyhow!(
"no `[package]` or `[[bin]]` found in {}",
cargo_toml.display()
)
})
}