use super::{BlueprintArgs, BlueprintEnvVars, BlueprintSourceHandler};
use crate::config::BlueprintManagerContext;
use crate::error::{Error, Result};
use crate::rt::ResourceLimits;
use crate::rt::service::Service;
use crate::sdk::utils::make_executable;
use crate::sources::types::TestFetcher;
use blueprint_core::trace;
use blueprint_runner::config::BlueprintEnvironment;
use std::path::{Path, PathBuf};
use std::process::Stdio;
pub struct TestSourceFetcher {
pub fetcher: TestFetcher,
pub blueprint_id: u64,
pub blueprint_name: String,
resolved_binary_path: Option<PathBuf>,
}
impl TestSourceFetcher {
#[must_use]
pub fn new(fetcher: TestFetcher, blueprint_id: u64, blueprint_name: String) -> Self {
Self {
fetcher,
blueprint_id,
blueprint_name,
resolved_binary_path: None,
}
}
async fn get_binary(&mut self, _cache_dir: &Path) -> Result<PathBuf> {
let cargo_bin = self.fetcher.cargo_bin.clone();
let base_path_str = self.fetcher.base_path.clone();
let git_repo_root = get_git_repo_root_path_in(&base_path_str).await?;
let profile = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let base_path = std::path::absolute(&git_repo_root)?;
let target_dir = match std::env::var("CARGO_TARGET_DIR") {
Ok(target) => PathBuf::from(target),
Err(_) => git_repo_root.join("target"),
};
let binary_path = target_dir.join(profile).join(&cargo_bin);
let binary_path = std::path::absolute(&binary_path)?;
trace!("Base Path: {}", base_path.display());
trace!("Binary Path: {}", binary_path.display());
if binary_path.exists() && !cfg!(debug_assertions) {
trace!(
"Binary already built, using existing binary at {}",
binary_path.display()
);
trace!(
binary_path = %binary_path.display(),
"if you want to rebuild the binary, run `cargo clean` in the repository root or remove the built binary manually"
);
return Ok(binary_path);
}
let mut command = tokio::process::Command::new("cargo");
command
.arg("build")
.arg(format!("--target-dir={}", target_dir.display()))
.arg("--bin")
.arg(&cargo_bin);
if !cfg!(debug_assertions) {
command.arg("--release");
}
let output = match command
.current_dir(&base_path)
.stdin(Stdio::null())
.kill_on_drop(true)
.output()
.await
{
Ok(output) => output,
Err(err) => {
blueprint_core::warn!(
"Failed to run build command using cargo: {err}. Ensure that cargo is installed and available in your PATH."
);
return Err(Error::from(err));
}
};
trace!("Build command run, this may take a while...");
if !output.status.success() {
blueprint_core::warn!("Failed to build binary");
return Err(Error::BuildBinary(output));
}
trace!("Successfully built binary");
Ok(binary_path)
}
}
async fn get_git_repo_root_path_in<P: AsRef<Path>>(cwd: P) -> Result<PathBuf> {
let output = tokio::process::Command::new("git")
.arg("rev-parse")
.arg("--show-toplevel")
.current_dir(cwd)
.output()
.await?;
if !output.status.success() {
return Err(Error::FetchGitRoot(output));
}
Ok(PathBuf::from(String::from_utf8(output.stdout)?.trim()))
}
impl BlueprintSourceHandler for TestSourceFetcher {
async fn fetch(&mut self, cache_dir: &Path) -> Result<PathBuf> {
if let Some(binary_path) = &self.resolved_binary_path {
if binary_path.exists() {
return Ok(binary_path.clone());
}
self.resolved_binary_path = None;
}
let mut binary_path = self.get_binary(cache_dir).await?;
binary_path = make_executable(&binary_path)?;
self.resolved_binary_path = Some(binary_path.clone());
Ok(binary_path)
}
async fn spawn(
&mut self,
ctx: &BlueprintManagerContext,
limits: ResourceLimits,
blueprint_config: &BlueprintEnvironment,
id: u32,
env: BlueprintEnvVars,
args: BlueprintArgs,
_confidentiality_policy: blueprint_client_tangle::ConfidentialityPolicy,
sub_service_str: &str,
cache_dir: &Path,
runtime_dir: &Path,
) -> Result<Service> {
let resolved_binary_path = self.fetch(cache_dir).await?;
Service::from_binary(
ctx,
limits,
blueprint_config,
id,
env,
args,
&resolved_binary_path,
sub_service_str,
cache_dir,
runtime_dir,
)
.await
}
fn blueprint_id(&self) -> u64 {
self.blueprint_id
}
fn name(&self) -> String {
self.blueprint_name.clone()
}
}