132 lines
3.6 KiB
Rust
Raw Normal View History

//! Command build provider.
//!
//! This provider delegates binary preparation to a command supplied by the test
//! or app integration. The command may run Cargo, fetch from an internal cache,
//! invoke a project-specific build script, or do anything else needed to
//! produce the expected executable path.
use std::{
env,
path::{Path, PathBuf},
process::Command,
};
use sha2::{Digest as _, Sha256};
use tracing::info;
use crate::binary::{
BinaryProvider, BinaryProviderError, BuildBinaryProvider, lock::BinaryProviderLock,
optional_path_display,
};
impl BinaryProvider for BuildBinaryProvider {
fn try_resolve(&self) -> Result<Option<PathBuf>, BinaryProviderError> {
let output_path = self.output_path();
let _lock = BinaryProviderLock::acquire(&self.lock_path())?;
self.run_build()?;
self.ensure_output_exists(&output_path)?;
Ok(Some(output_path))
}
fn display(&self) -> String {
"build".to_owned()
}
fn cache_key(&self) -> String {
format!(
"build:{}:{}:{}",
self.command.display(),
self.output_path.display(),
optional_path_display(&self.working_dir)
)
}
}
impl BuildBinaryProvider {
fn run_build(&self) -> Result<(), BinaryProviderError> {
info!(
command = self.command.display(),
workspace = %self.workspace_dir().display(),
"building binary"
);
let status = self
.command()
.status()
.map_err(|source| BinaryProviderError::Io {
path: self.workspace_dir(),
source,
})?;
if !status.success() {
return Err(BinaryProviderError::BuildFailed {
status: status.to_string(),
});
}
Ok(())
}
fn ensure_output_exists(&self, output_path: &Path) -> Result<(), BinaryProviderError> {
if output_path.is_file() {
return Ok(());
}
Err(BinaryProviderError::MissingBuildOutput {
path: output_path.to_owned(),
})
}
fn command(&self) -> Command {
let mut command = Command::new(&self.command.program);
command
.current_dir(self.workspace_dir())
.args(&self.command.args);
command
}
fn output_path(&self) -> PathBuf {
if self.output_path.is_absolute() {
return self.output_path.clone();
}
self.workspace_dir().join(&self.output_path)
}
fn lock_path(&self) -> PathBuf {
let output_path = self.output_path();
let lock_file_name = self.lock_file_name(&output_path);
self.lock_dir
.clone()
.unwrap_or_else(|| self.workspace_dir().join(".tf-binaries"))
.join(lock_file_name)
}
fn lock_file_name(&self, output_path: &Path) -> String {
let binary_name = output_path
.file_name()
.and_then(|name| name.to_str())
.unwrap_or("binary");
let artifact_hash = self.artifact_path_hash(output_path);
format!("{binary_name}-{artifact_hash}.lock")
}
fn artifact_path_hash(&self, output_path: &Path) -> String {
Sha256::digest(output_path.to_string_lossy().as_bytes())
.iter()
.take(8)
.map(|byte| format!("{byte:02x}"))
.collect()
}
fn workspace_dir(&self) -> PathBuf {
self.working_dir
.clone()
.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
}