Skip to main content

abbaye/builders/
mod.rs

1use miette::{IntoDiagnostic, Result};
2use serde::Deserialize;
3use sha1::{Digest, Sha1};
4use std::path::PathBuf;
5
6pub mod archive;
7pub mod cargo;
8
9use archive::{ArchiveBuilder, ArchiveBuilderConfig};
10use cargo::{CargoBuilder, CargoBuilderConfig, CargoDocBuilder, CargoDocBuilderConfig};
11
12#[derive(Debug, Deserialize)]
13#[serde(tag = "type", rename_all = "snake_case")]
14pub enum AnyBuilder {
15    /// Creates a `.tar.gz` archive of the source tree, automatically excluding
16    /// files and directories matched by any `.gitignore` found in the hierarchy.
17    ///
18    /// ```toml
19    /// [[builders]]
20    /// type = "archive"
21    /// source_dir = "."                      # optional, defaults to CWD
22    /// output = "myproject-1.0.0.tar.gz"     # optional, defaults to source.tar.gz
23    /// prefix = "myproject-1.0.0"            # optional, defaults to source_dir name
24    /// ```
25    Archive(ArchiveBuilderConfig),
26
27    /// Compiles the crate in release mode with `cargo build --release`.
28    /// One or more target triples can be specified for cross-compilation;
29    /// omitting `targets` builds for the host platform.
30    ///
31    /// ```toml
32    /// [[builders]]
33    /// type = "cargo"
34    /// targets = ["x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl"]
35    /// manifest_path = "Cargo.toml"          # optional
36    /// ```
37    Cargo(CargoBuilderConfig),
38
39    /// Generates API documentation with `cargo doc`.
40    /// Returns the per-crate doc directory (e.g. `target/doc/my_crate`) as an
41    /// artifact so it can be published or archived by a later pipeline step.
42    ///
43    /// ```toml
44    /// [[builders]]
45    /// type = "cargo_doc"
46    /// no_deps = true                        # optional, skip dependency docs
47    /// manifest_path = "Cargo.toml"          # optional
48    /// ```
49    CargoDoc(CargoDocBuilderConfig),
50}
51
52impl AnyBuilder {
53    pub async fn build(&self) -> Result<Vec<ArtifactPath>> {
54        match self {
55            Self::Archive(config) => ArchiveBuilder.build(config.clone()).await,
56            Self::Cargo(config) => CargoBuilder.build(config.clone()).await,
57            Self::CargoDoc(config) => CargoDocBuilder.build(config.clone()).await,
58        }
59    }
60}
61
62/// Read the file at `path` and return its SHA1 digest as a lowercase hex string.
63async fn hash_file(path: &PathBuf) -> Result<String> {
64    let contents = tokio::fs::read(path).await.into_diagnostic()?;
65    let mut hasher = Sha1::new();
66    hasher.update(&contents);
67    Ok(hasher
68        .finalize()
69        .iter()
70        .map(|b| format!("{b:02x}"))
71        .collect())
72}
73
74pub struct ArtifactPath {
75    pub path: PathBuf,
76    pub name: String,
77    /// Lowercase hexadecimal SHA1 digest of the artifact's contents, if computed.
78    pub hash: Option<String>,
79}
80
81#[allow(async_fn_in_trait)]
82pub trait Builder {
83    type ConfigType: Default + for<'de> Deserialize<'de> + Clone;
84
85    async fn build(&self, config: Self::ConfigType) -> Result<Vec<ArtifactPath>>;
86}