2026-05-20 08:56:49 +02:00
|
|
|
//! 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,
|
|
|
|
|
};
|
|
|
|
|
|
2026-06-02 10:36:49 +02:00
|
|
|
use sha2::{Digest as _, Sha256};
|
2026-05-20 08:56:49 +02:00
|
|
|
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 {
|
2026-06-02 10:36:49 +02:00
|
|
|
let output_path = self.output_path();
|
|
|
|
|
let lock_file_name = self.lock_file_name(&output_path);
|
2026-05-20 08:56:49 +02:00
|
|
|
|
|
|
|
|
self.lock_dir
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_else(|| self.workspace_dir().join(".tf-binaries"))
|
2026-06-02 10:36:49 +02:00
|
|
|
.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()
|
2026-05-20 08:56:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn workspace_dir(&self) -> PathBuf {
|
|
|
|
|
self.working_dir
|
|
|
|
|
.clone()
|
|
|
|
|
.unwrap_or_else(|| env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
|
|
|
|
|
}
|
|
|
|
|
}
|