Commit
Message
Changed Files (9)
-
modified CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md index a8e2ea3..e96550e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - If a `static` dir is found in custom theme folder, copy it to output - Add opengraph metadata to site generation +- Add AbbayeConfig schema gen and `dump-schema` command ### ⚙️ Miscellaneous Tasks -
modified README.md
diff --git a/README.md b/README.md index ca0457a..fb32f67 100644 --- a/README.md +++ b/README.md @@ -55,9 +55,8 @@ The `-musl` binaries are statically linked and should run everywhere, while the ### From source To build from source, you need to have Rust installed. You can install Rust using [rustup](https://rustup.rs/). -If you don't have rust installed, this project won't be of much use to you, as it currently only implements rust builders :stuck_out_tongue: -You can clone the [repository](https://git.sr.ht/~ololduck/abbaye) and build the project using `cargo build --release`. +You can clone the [repository](https://git.sr.ht/~ololduck/abbaye) and build the project using `cargo build --release`. The built binary will be located in `target/release/abbaye`. You can also install it directly using `cargo install --path .` ## Usage @@ -67,7 +66,8 @@ Here's an example configuration file to get you started: ```toml [site] name = "Abbaye" -base_url = "http://vit.am/~ololduck/abbaye/" # required for Atom feed generation (canonical URLs are used for feed items) +# required for Atom feed generation (canonical URLs are used for feed items) +base_url = "http://vit.am/~ololduck/abbaye/" [version_extractor] type = "git" # extract version from git tags @@ -85,6 +85,13 @@ no_deps = true # Don't include dependencies in the documentation [[builders]] type = "archive" # creates a compressed tarball of the source code (can be of anything, really) + +[[builders]] +type = "script" +script = [ + "echo $ABBAYE_BUILDING_VERSION > .version", +] +outputs = [".version"] ``` Then run `abbaye build` to build the site. The site will be generated in the `public/` directory by default. -
modified abbaye.toml
diff --git a/abbaye.toml b/abbaye.toml index c583416..ab828ae 100644 --- a/abbaye.toml +++ b/abbaye.toml @@ -24,5 +24,13 @@ targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] type = "cargo_doc" no_deps = true +[[builders]] +type = "script" +script = [ + "mkdir -p target", + "cargo run --bin abbaye -- dump-schema > target/abbaye.schema.json" +] +outputs = ["target/abbaye.schema.json"] + [[builders]] type = "archive" # creates a compressed tarball of the source code (can be of anything, really) -
modified src/builders/archive.rs
diff --git a/src/builders/archive.rs b/src/builders/archive.rs index 44bd88b..19b3c70 100644 --- a/src/builders/archive.rs +++ b/src/builders/archive.rs @@ -59,7 +59,7 @@ pub struct ArchiveBuilder; impl Builder for ArchiveBuilder { type ConfigType = ArchiveBuilderConfig; - async fn build(&self, config: Self::ConfigType) -> Result<Vec<ArtifactPath>> { + async fn build(&self, config: Self::ConfigType, _version: &str) -> Result<Vec<ArtifactPath>> { let source_dir = config .source_dir .unwrap_or_else(|| PathBuf::from(".")) -
modified src/builders/cargo.rs
diff --git a/src/builders/cargo.rs b/src/builders/cargo.rs index 8cc85c4..db94d87 100644 --- a/src/builders/cargo.rs +++ b/src/builders/cargo.rs @@ -48,17 +48,27 @@ pub struct CargoBuilder; impl Builder for CargoBuilder { type ConfigType = CargoBuilderConfig; - async fn build(&self, config: Self::ConfigType) -> Result<Vec<ArtifactPath>> { - let version = read_crate_version(config.manifest_path.as_deref()).await?; + async fn build( + &self, + config: Self::ConfigType, + abbaye_version: &str, + ) -> Result<Vec<ArtifactPath>> { + let crate_version = read_crate_version(config.manifest_path.as_deref()).await?; if config.targets.is_empty() { let host = get_host_target().await?; - run_cargo_build(&config, None, &host, &version).await + run_cargo_build(&config, None, &host, &crate_version, abbaye_version).await } else { let mut all_artifacts = Vec::new(); for target in &config.targets { - let artifacts = - run_cargo_build(&config, Some(target.as_str()), target, &version).await?; + let artifacts = run_cargo_build( + &config, + Some(target.as_str()), + target, + &crate_version, + abbaye_version, + ) + .await?; all_artifacts.extend(artifacts); } Ok(all_artifacts) @@ -66,8 +76,6 @@ impl Builder for CargoBuilder { } } -// ── Internals ──────────────────────────────────────────────────────────────── - /// Minimal representation of the JSON messages emitted by /// `cargo build --message-format=json`. #[derive(Deserialize)] @@ -94,9 +102,11 @@ async fn run_cargo_build( target: Option<&str>, triple: &str, version: &str, + abbaye_version: &str, ) -> Result<Vec<ArtifactPath>> { let mut cmd = Command::new("cargo"); cmd.args(["build", "--release", "--message-format=json"]); + cmd.env("ABBAYE_BUILDING_VERSION", abbaye_version); if let Some(t) = target { cmd.args(["--target", t]); @@ -235,8 +245,6 @@ async fn read_crate_version(manifest_path: Option<&Path>) -> Result<String> { .ok_or_else(|| miette!("no version field in [package] in {}", path.display())) } -// ── CargoDocBuilder ────────────────────────────────────────────────────────── - /// Configuration for [`CargoDocBuilder`]. #[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] pub struct CargoDocBuilderConfig { @@ -251,16 +259,20 @@ pub struct CargoDocBuilderConfig { pub no_deps: bool, } -/// Runs `cargo doc` and returns the `index.html` entry point for each -/// documented crate as an artifact. +/// Runs `cargo doc` and returns the whole doc directory as an artifact. pub struct CargoDocBuilder; impl Builder for CargoDocBuilder { type ConfigType = CargoDocBuilderConfig; - async fn build(&self, config: Self::ConfigType) -> Result<Vec<ArtifactPath>> { + async fn build( + &self, + config: Self::ConfigType, + abbaye_version: &str, + ) -> Result<Vec<ArtifactPath>> { let mut cmd = Command::new("cargo"); cmd.arg("doc"); + cmd.env("ABBAYE_BUILDING_VERSION", abbaye_version); if config.no_deps { cmd.arg("--no-deps"); -
modified src/builders/mod.rs
diff --git a/src/builders/mod.rs b/src/builders/mod.rs index 3665d7d..a4941ea 100644 --- a/src/builders/mod.rs +++ b/src/builders/mod.rs @@ -5,9 +5,11 @@ 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")] @@ -47,14 +49,32 @@ pub enum AnyBuilder { /// 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) -> Result<Vec<ArtifactPath>> { + pub async fn build(&self, version: &str) -> Result<Vec<ArtifactPath>> { match self { - Self::Archive(config) => ArchiveBuilder.build(config.clone()).await, - Self::Cargo(config) => CargoBuilder.build(config.clone()).await, - Self::CargoDoc(config) => CargoDocBuilder.build(config.clone()).await, + 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, } } } @@ -70,5 +90,10 @@ pub struct ArtifactPath { pub trait Builder { type ConfigType: Default + for<'de> Deserialize<'de> + Clone; - async fn build(&self, config: Self::ConfigType) -> Result<Vec<ArtifactPath>>; + /// 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>>; } -
added src/builders/script.rs
diff --git a/src/builders/script.rs b/src/builders/script.rs new file mode 100644 index 0000000..92fdec2 --- /dev/null +++ b/src/builders/script.rs @@ -0,0 +1,103 @@ +use std::path::PathBuf; + +use crate::builders::{ArtifactPath, Builder}; +use miette::{IntoDiagnostic, Result, miette}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use tokio::process::Command; + +/// Configuration for [`ScriptBuilder`]. +#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema)] +pub struct ScriptBuilderConfig { + /// Shell commands to execute in order. Each line is passed to `sh -c`, + /// so any POSIX shell syntax is supported. + /// + /// The environment variable `ABBAYE_BUILDING_VERSION` is set to the version + /// being built. (e.g. the git tag `v0.1.0` or whatever. Not Abbaye's own version) + /// + /// 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"] + /// ``` + pub script: Vec<String>, + + /// Paths of the files or directories produced by the script that should be + /// treated as release artifacts (copied to `dist/` and listed on the + /// release page). Each path is resolved relative to the working directory + /// in which `abbaye` is run. + /// + /// If a listed path is missing, the build fails. + pub outputs: Vec<ScriptBuilderOutput>, +} + +/// Lets the user specify a path for a script output, optionally with a custom name. +/// I need to check but it should enable the user to specify a directory as output. +/// If that's a good idea is an other question. +#[derive(Debug, Deserialize, Serialize, Clone, JsonSchema)] +#[serde(untagged)] +pub enum ScriptBuilderOutput { + PathWithName { path: PathBuf, name: String }, + Path(PathBuf), +} + +/// Executes a script and treats the listed output paths as release artifacts. +pub struct ScriptBuilder; + +impl Builder for ScriptBuilder { + type ConfigType = ScriptBuilderConfig; + + async fn build(&self, config: Self::ConfigType, version: &str) -> Result<Vec<ArtifactPath>> { + for line in &config.script { + let status = Command::new("sh") + .args(["-c", line]) + .env("ABBAYE_BUILDING_VERSION", version) + .status() + .await + .into_diagnostic()?; + + if !status.success() { + return Err(miette!( + "script command failed (exit {}):\n {}", + status.code().unwrap_or(-1), + line + )); + } + } + + // Collect every declared output path as an artifact. + let mut artifacts = Vec::new(); + for output in &config.outputs { + let (path, name) = match output { + ScriptBuilderOutput::Path(p) => { + let name = p + .file_name() + .ok_or_else(|| miette!("output path has no file name: {}", p.display()))? + .to_string_lossy() + .into_owned(); + (p, name) + } + ScriptBuilderOutput::PathWithName { path: p, name } => (p, name.clone()), + }; + if !path.exists() { + return Err(miette!( + "declared script output does not exist: {}", + path.display() + )); + } + artifacts.push(ArtifactPath { + path: path.clone(), + name, + hash: None, + }); + } + + Ok(artifacts) + } +} -
modified src/main.rs
diff --git a/src/main.rs b/src/main.rs index 2a1fce1..464e5ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,9 +55,8 @@ //! ### From source //! //! To build from source, you need to have Rust installed. You can install Rust using [rustup](https://rustup.rs/). -//! If you don't have rust installed, this project won't be of much use to you, as it currently only implements rust builders :stuck_out_tongue: //! -//! You can clone the [repository](https://git.sr.ht/~ololduck/abbaye) and build the project using `cargo build --release`. +//! You can clone the [repository](https://git.sr.ht/~ololduck/abbaye) and build the project using `cargo build --release`. The built binary will be located in `target/release/abbaye`. You can also install it directly using `cargo install --path .` //! //! ## Usage //! @@ -67,7 +66,8 @@ //! ```toml //! [site] //! name = "Abbaye" -//! base_url = "http://vit.am/~ololduck/abbaye/" # required for Atom feed generation (canonical URLs are used for feed items) +//! # required for Atom feed generation (canonical URLs are used for feed items) +//! base_url = "http://vit.am/~ololduck/abbaye/" //! //! [version_extractor] //! type = "git" # extract version from git tags @@ -85,6 +85,13 @@ //! //! [[builders]] //! type = "archive" # creates a compressed tarball of the source code (can be of anything, really) +//! +//! [[builders]] +//! type = "script" +//! script = [ +//! "echo $ABBAYE_BUILDING_VERSION > .version", +//! ] +//! outputs = [".version"] //! ``` //! //! Then run `abbaye build` to build the site. The site will be generated in the `public/` directory by default. -
modified src/site.rs
diff --git a/src/site.rs b/src/site.rs index 28c280b..dda6d85 100644 --- a/src/site.rs +++ b/src/site.rs @@ -114,7 +114,7 @@ pub async fn build_site(config: AbbayeConfig) -> Result<()> { let mut doc_artifacts = Vec::new(); for builder in &config.builders { - for artifact in builder.build().await? { + for artifact in builder.build(&version).await? { if artifact.path.is_dir() { doc_artifacts.push(artifact); } else {