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}