use std::fs;
use std::io::BufReader;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use anyhow::{Context, Result, bail};
use cargo_metadata::{Artifact, Message, Metadata, Package, TargetKind};
use crate::workspace;
const TARGET: &str = "wasm32-wasip2";
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum Profile {
Debug,
Release,
}
impl Profile {
fn folder(self) -> &'static str {
match self {
Profile::Debug => "debug",
Profile::Release => "release",
}
}
}
pub struct BuiltProgram {
pub name: String,
pub component: PathBuf,
}
pub fn build(metadata: &Metadata, package: &Package, profile: Profile) -> Result<BuiltProgram> {
let built = cargo_build(metadata, package, profile)?;
let component = workspace::waos_folder(metadata)
.join(profile.folder())
.join(format!("{}.wasm", package.name));
place(&built, &component)?;
Ok(BuiltProgram {
name: package.name.to_string(),
component,
})
}
fn cargo_build(metadata: &Metadata, package: &Package, profile: Profile) -> Result<PathBuf> {
let mut cargo = Command::new(cargo_binary());
cargo.current_dir(&metadata.workspace_root).args([
"build",
"--target",
TARGET,
"--message-format=json-render-diagnostics",
"--package",
&package.name,
]);
if profile == Profile::Release {
cargo.arg("--release");
}
cargo.env("CARGO_PROFILE_DEV_DEBUG", "line-tables-only");
let mut child = cargo
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.spawn()
.context("running cargo")?;
let stdout = child.stdout.take().expect("stdout is piped");
let mut component = None;
for message in Message::parse_stream(BufReader::new(stdout)) {
if let Message::CompilerArtifact(artifact) = message.context("reading cargo's messages")?
&& artifact.package_id == package.id
{
component = component_of(&artifact).or(component);
}
}
let status = child.wait().context("waiting for cargo")?;
if !status.success() {
bail!("cargo build failed");
}
component.with_context(|| {
format!(
"cargo built no component for {}: a program is a package with a binary target",
package.name
)
})
}
fn cargo_binary() -> PathBuf {
std::env::var_os("CARGO")
.map(PathBuf::from)
.unwrap_or_else(|| PathBuf::from("cargo"))
}
fn component_of(artifact: &Artifact) -> Option<PathBuf> {
let kinds = &artifact.target.kind;
if !kinds.contains(&TargetKind::Bin) && !kinds.contains(&TargetKind::CDyLib) {
return None;
}
artifact
.executable
.clone()
.or_else(|| {
artifact
.filenames
.iter()
.find(|file| file.extension() == Some("wasm"))
.cloned()
})
.map(|path| path.into_std_path_buf())
}
fn place(built: &Path, component: &Path) -> Result<()> {
if let Some(folder) = component.parent() {
fs::create_dir_all(folder).with_context(|| format!("making {}", folder.display()))?;
}
fs::copy(built, component)
.with_context(|| format!("copying {} to {}", built.display(), component.display()))?;
Ok(())
}