use std::io::ErrorKind;
use std::path::PathBuf;
use std::process;
use anyhow::{anyhow, bail, Context, Result};
use nkeys::KeyPairType;
use tracing::{trace, warn};
use crate::build::SignConfig;
use crate::cli::par::{create_provider_archive, detect_arch, ParCreateArgs};
use crate::cli::{extract_keypair, OutputKind};
use crate::parser::{CommonConfig, GoConfig, LanguageConfig, ProviderConfig, RustConfig};
pub(crate) async fn build_provider(
provider_config: &ProviderConfig,
language_config: &LanguageConfig,
common_config: &CommonConfig,
signing_config: Option<&SignConfig>,
) -> Result<PathBuf> {
let (provider_path_buf, bin_name) = match language_config {
LanguageConfig::Rust(rust_config) => {
build_rust_provider(provider_config, rust_config, common_config)?
}
LanguageConfig::Go(go_config) => {
build_go_provider(provider_config, go_config, common_config)?
}
_ => bail!("Unsupported language for provider: {:?}", language_config),
};
trace!("Retrieving provider binary from {:?}", provider_path_buf);
let provider_path_buf = provider_path_buf
.canonicalize()
.context("failed to resolve file path")?;
let provider_bytes = tokio::fs::read(&provider_path_buf).await.with_context(|| {
format!(
"missing provider binary at [{}]",
provider_path_buf.display()
)
})?;
let mut par = create_provider_archive(
ParCreateArgs {
vendor: provider_config.vendor.to_string(),
revision: Some(common_config.revision),
version: Some(common_config.version.to_string()),
schema: None,
name: common_config.name.to_string(),
arch: detect_arch(),
},
&provider_bytes,
)
.context("failed to create initial provider archive with built provider")?;
let Some(sign_config) = signing_config else {
warn!("No signing configuration supplied, could only build provider");
return Ok(provider_path_buf);
};
let destination = common_config
.path
.join("build")
.join(format!("{bin_name}.par.gz"));
if let Some(parent) = destination.parent() {
tokio::fs::create_dir_all(parent)
.await
.with_context(|| format!("failed to create directory [{}]", parent.display()))?;
}
let issuer = extract_keypair(
sign_config.issuer.as_deref(),
Some(&provider_path_buf.to_string_lossy()),
sign_config.keys_directory.clone(),
KeyPairType::Account,
sign_config.disable_keygen,
OutputKind::Json,
)?;
let subject = extract_keypair(
sign_config.subject.as_deref(),
Some(&provider_path_buf.to_string_lossy()),
sign_config.keys_directory.clone(),
KeyPairType::Service,
sign_config.disable_keygen,
OutputKind::Json,
)?;
par.write(destination.as_path(), &issuer, &subject, true)
.await
.map_err(|e| anyhow::anyhow!(e))?;
Ok(if destination.is_absolute() {
destination
} else {
common_config.path.join(destination)
})
}
fn build_rust_provider(
provider_config: &ProviderConfig,
rust_config: &RustConfig,
common_config: &CommonConfig,
) -> Result<(PathBuf, String)> {
let mut command = match rust_config.cargo_path.as_ref() {
Some(path) => process::Command::new(path),
None => process::Command::new("cargo"),
};
std::env::set_current_dir(&common_config.path)?;
trace!("Building provider in {:?}", common_config.path);
let mut build_args = Vec::with_capacity(4);
build_args.push("build");
if !rust_config.debug {
build_args.push("--release");
}
if let Some(override_target) = &provider_config.rust_target {
build_args.extend_from_slice(&["--target", override_target]);
};
let result = command.args(build_args).status().map_err(|e| {
if e.kind() == ErrorKind::NotFound {
anyhow!("{:?} command is not found", command.get_program())
} else {
anyhow!(e)
}
})?;
if !result.success() {
bail!("Compiling provider failed: {result}")
}
let metadata = cargo_metadata::MetadataCommand::new().no_deps().exec()?;
let bin_name = if let Some(bin_name) = &provider_config.bin_name {
bin_name.to_string()
} else {
metadata
.packages
.iter()
.find_map(|p| {
p.targets.iter().find_map(|t| {
if t.kind.iter().any(|k| k == "bin") {
Some(t.name.clone())
} else {
None
}
})
}).context("Could not infer provider binary name in metadata, please specify under provider.bin_name")?
};
let mut provider_path_buf = rust_config
.target_path
.clone()
.unwrap_or_else(|| PathBuf::from(metadata.target_directory.as_std_path()));
if let Some(override_target) = &provider_config.rust_target {
provider_path_buf.push(override_target);
}
if rust_config.debug {
provider_path_buf.push("debug");
} else {
provider_path_buf.push("release");
}
provider_path_buf.push(&bin_name);
Ok((provider_path_buf, bin_name))
}
fn build_go_provider(
provider_config: &ProviderConfig,
go_config: &GoConfig,
common_config: &CommonConfig,
) -> Result<(PathBuf, String)> {
let mut generate_command = match go_config.go_path.as_ref() {
Some(path) => process::Command::new(path),
None => process::Command::new("go"),
};
std::env::set_current_dir(&common_config.path)?;
trace!("Building provider in {:?}", common_config.path);
if !go_config.disable_go_generate {
let result = generate_command
.args(["generate", "./..."])
.status()
.map_err(|e| {
if e.kind() == ErrorKind::NotFound {
anyhow!("{:?} command is not found", generate_command.get_program())
} else {
anyhow!(e)
}
})?;
if !result.success() {
bail!("Generating interfaces failed: {result}")
}
}
let bin_name = if let Some(bin_name) = &provider_config.bin_name {
bin_name.to_string()
} else {
bail!("Could not infer provider binary name, please specify in wasmcloud.toml under provider.bin_name")
};
let mut build_command = match go_config.go_path.as_ref() {
Some(path) => process::Command::new(path),
None => process::Command::new("go"),
};
let result = build_command
.args(["build", "-o", &bin_name])
.status()
.map_err(|e| {
if e.kind() == ErrorKind::NotFound {
anyhow!("{:?} command is not found", build_command.get_program())
} else {
anyhow!(e)
}
})?;
if !result.success() {
bail!("Compiling provider failed: {result}")
}
Ok((PathBuf::from(&bin_name), bin_name))
}