Abbaye

at 4940405

use miette::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;

pub mod archive;
pub mod cargo;
pub mod script;

use archive::{ArchiveBuilder, ArchiveBuilderConfig};
use cargo::{CargoBuilder, CargoBuilderConfig, CargoDocBuilder, CargoDocBuilderConfig};
use script::{ScriptBuilder, ScriptBuilderConfig};

#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AnyBuilder {
    /// Creates a `.tar.gz` archive of the source tree, automatically excluding
    /// files and directories matched by any `.gitignore` found in the hierarchy.
    ///
    /// ```toml
    /// [[builders]]
    /// type = "archive"
    /// source_dir = "."                      # optional, defaults to CWD
    /// output = "myproject-1.0.0.tar.gz"     # optional, defaults to source.tar.gz
    /// prefix = "myproject-1.0.0"            # optional, defaults to source_dir name
    /// ```
    Archive(ArchiveBuilderConfig),

    /// Compiles the crate in release mode with `cargo build --release`.
    /// One or more target triples can be specified for cross-compilation;
    /// omitting `targets` builds for the host platform.
    ///
    /// ```toml
    /// [[builders]]
    /// type = "cargo"
    /// targets = ["x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl"]
    /// manifest_path = "Cargo.toml"          # optional
    /// ```
    Cargo(CargoBuilderConfig),

    /// Generates API documentation with `cargo doc`.
    /// Returns the per-crate doc directory (e.g. `target/doc/my_crate`) as an
    /// artifact so it can be published or archived by a later pipeline step.
    ///
    /// ```toml
    /// [[builders]]
    /// type = "cargo_doc"
    /// no_deps = true                        # optional, skip dependency docs
    /// manifest_path = "Cargo.toml"          # optional
    /// ```
    CargoDoc(CargoDocBuilderConfig),

    /// Runs an arbitrary sequence of shell commands and collects declared
    /// output paths as release artifacts.
    ///
    /// Each script line is passed to `sh -c`; the build fails immediately if
    /// any command exits with a non-zero status.
    ///
    /// ```toml
    /// [[builders]]
    /// type = "script"
    /// script = [
    ///   "make release",
    ///   "strip target/mybin",
    /// ]
    /// outputs = ["target/mybin"]
    /// ```
    Script(ScriptBuilderConfig),
}

impl AnyBuilder {
    pub async fn build(&self, version: &str) -> Result<Vec<ArtifactPath>> {
        match self {
            Self::Archive(config) => ArchiveBuilder.build(config.clone(), version).await,
            Self::Cargo(config) => CargoBuilder.build(config.clone(), version).await,
            Self::CargoDoc(config) => CargoDocBuilder.build(config.clone(), version).await,
            Self::Script(config) => ScriptBuilder.build(config.clone(), version).await,
        }
    }
}

pub struct ArtifactPath {
    pub path: PathBuf,
    pub name: String,
    /// Lowercase hexadecimal SHA1 digest of the artifact's contents, if computed.
    pub hash: Option<String>,
}

#[allow(async_fn_in_trait)]
pub trait Builder {
    type ConfigType: Default + for<'de> Deserialize<'de> + Clone;

    /// Run the builder and return the produced artifacts.
    ///
    /// `version` is the abbaye release version currently being built (e.g.
    /// `"1.2.3"`). Implementations that spawn subprocesses must expose it as
    /// the `ABBAYE_BUILDING_VERSION` environment variable.
    async fn build(&self, config: Self::ConfigType, version: &str) -> Result<Vec<ArtifactPath>>;
}