Commit
Message
Changed Files (6)
-
modified CHANGELOG.md
diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d90cb..6119f1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## [unreleased] +### 🚀 Features + +- Add feature support to Cargo builder and commit wrapper script + ### 🐛 Bug Fixes - *(builders)* Correct glob walking in script builder and add tests @@ -7,6 +11,10 @@ ### 🚜 Refactor - *(render)* Improve Gemtext nesting logic and add rendering tests + +### ⚙️ Miscellaneous Tasks + +- Update CHANGELOG.md to match git-cliff output ## [0.10.2] - 2026-06-22 ### 🐛 Bug Fixes -
modified mise.toml
diff --git a/mise.toml b/mise.toml index 4c3dbe9..a4540ca 100644 --- a/mise.toml +++ b/mise.toml @@ -81,34 +81,12 @@ run = ["cargo build --release"] sources = ["Cargo.toml", "Cargo.lock", "*.rs", "**/*.rs"] [tasks.release] -depends = [ - "generate-readme", - "generate-changelog", - "generate-schema", - "build::release", - "fmt", - "lint", - "clean", -] +description = "release the project (delegates to scripts/release.sh)" usage = """ arg "<new_version>" help="New version to release" """ confirm = "Are you sure you want to release?" -description = "release the project" -run = [ - 'git rev-parse --abbrev-ref HEAD | grep -q "^main$" || { echo "Not on main branch, aborting."; exit 1; }', - 'sed -i "/^\[package\]/,/^\[/{s/^version = \".*\"/version = \"${usage_new_version}\"/}" Cargo.toml && git add Cargo.toml', - "cargo generate-lockfile && git add Cargo.lock", - 'git commit -am "chore: release v${usage_new_version}"', - "mise run generate-changelog && git commit --amend --no-edit -n || echo \"No changelog generated\"", - "prek run --all-files", - "mise run 'build::release'", - "git tag --force v${usage_new_version}", - "mise run generate-changelog && git commit -a --amend --no-edit -n", - "git tag --force v${usage_new_version}", - "git push --force", -] -sources = ["Cargo.toml", "Cargo.lock", "*.rs", "**/*.rs"] +run = ["bash scripts/release.sh ${usage_new_version}"] depends_post = ["deploy-docs", "abbaye"] [tasks.clean] -
added scripts/commit.sh
diff --git a/scripts/commit.sh b/scripts/commit.sh new file mode 100755 index 0000000..d13f9a7 --- /dev/null +++ b/scripts/commit.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +# Wrapper around `git commit` that regenerates CHANGELOG.md via git-cliff +# and amends it into the same commit, keeping the changelog always in sync. +# +# Usage: ./scripts/commit.sh [--tag <name>] [<git-commit-args>…] +# +# All arguments after the optional `--tag` are forwarded verbatim to +# `git commit`. If the commit succeeds, git-cliff regenerates CHANGELOG.md +# and the result is folded into the commit. +# +# Use `--tag <name>` when the commit should be tagged before git-cliff runs +# (required for release commits so cliff sees the new version). The tag is +# created between the commit and the changelog regeneration. +# +# Prerequisites: +# - git-cliff installed (see mise.toml or cargo install git-cliff) + +set -euo pipefail + +TAG="" + +if [[ "${1:-}" == "--tag" ]]; then + TAG="$2" + shift 2 +fi + +# Pass through to git commit. If it fails (e.g. empty commit, aborted +# message editor) we bail before touching the changelog. +git commit "$@" + +# If a tag was requested, create it now so git-cliff can find it. +if [[ -n "$TAG" ]]; then + git tag "$TAG" +fi + +# Regenerate changelog from the updated history. +git cliff -o CHANGELOG.md + +# Fold the regenerated changelog into the commit we just made. +# Use -n to skip pre-commit hooks: the check-changelog-cliff hook would +# run against the *staged* changelog, which at this point is already in +# sync, but the hook's git-cliff invocation may disagree depending on +# where the tag pointer sits relative to HEAD. +git add CHANGELOG.md +git commit --amend --no-edit -n -
modified scripts/release.sh
diff --git a/scripts/release.sh b/scripts/release.sh index 174da33..6126dc1 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -2,7 +2,7 @@ # # Bump, tag, build, and push a new release. # -# Usage: ./scripts/release.sh <version> +# Usage: ./scripts/release.sh [-y] <version> # # Prerequisites: # - On `main` branch, working tree clean @@ -22,9 +22,18 @@ set -euo pipefail +# Parse flags +YES=0 +while [[ "${1:-}" == -* ]]; do + case "$1" in + -y|--yes) YES=1; shift ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + VERSION="${1:-}" if [[ -z "$VERSION" ]]; then - echo "Usage: $0 <version>" + echo "Usage: $0 [-y] <version>" exit 1 fi @@ -47,10 +56,13 @@ VERSION="${VERSION#v}" # Ensure native aarch64-musl cross-compiler linker is available. export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER="aarch64-linux-musl-gcc" -read -rp "Release v${VERSION}? [y/N] " CONFIRM -if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then - echo "Aborted." - exit 1 +# Confirm unless -y was passed or stdin is not a terminal. +if [[ "$YES" == "0" ]] && [[ -t 0 ]]; then + read -rp "Release v${VERSION}? [y/N] " CONFIRM + if [[ "$CONFIRM" != "y" && "$CONFIRM" != "Y" ]]; then + echo "Aborted." + exit 1 + fi fi # ── Pre-flight regeneration ──────────────────────────────────────────────── @@ -70,26 +82,11 @@ cargo set-version "$VERSION" git add Cargo.toml Cargo.lock if ! git diff --cached --quiet; then - git commit -m "chore: release v${VERSION}" -n + scripts/commit.sh --tag "v${VERSION}" -m "chore: release v${VERSION}" -n else echo "Warning: No changes detected in Cargo.toml or Cargo.lock. Skipping version commit." fi -# ── Tag (before changelog so git-cliff can find this version) ────────────── - -echo "=== Tagging v${VERSION} ===" -git tag "v${VERSION}" - -# ── Changelog (amend into release commit) ────────────────────────────────── - -echo "=== Generating changelog ===" -git cliff -o CHANGELOG.md -git add CHANGELOG.md -# Skip hooks: the changelog-check hook would fail because it runs against the -# staged changelog while the tag now points at HEAD~1, making git-cliff think -# this version is already released. We run hooks explicitly next. -git commit --amend --no-edit -n - # ── Mise plugin test ──────────────────────────────────────────────────────── echo "=== Testing mise plugin ===" -
modified src/builders/cargo.rs
diff --git a/src/builders/cargo.rs b/src/builders/cargo.rs index 6c97933..a9c57d1 100644 --- a/src/builders/cargo.rs +++ b/src/builders/cargo.rs @@ -56,6 +56,45 @@ pub struct CargoBuilderConfig { #[serde(default)] pub use_cross: bool, + /// Cargo feature flags to activate. + /// + /// Passed as `--features <comma-joined>`. When non-empty, the artifact + /// name includes a feature suffix (e.g. `myapp-1.0-x86_64-full`) so + /// artifacts built with different feature sets do not collide in the + /// distribution directory. + /// + /// Combined with `no_default_features` to disable the default feature set. + /// + /// ```toml + /// [[builders]] + /// type = "cargo" + /// features = ["full"] + /// ``` + #[serde(default)] + pub features: Vec<String>, + + /// Do not activate the `default` feature (`--no-default-features`). + /// + /// When set without `features`, the artifact name is suffixed with + /// `no-default` to distinguish it from a default-features build. + #[serde(default)] + pub no_default_features: bool, + + /// Override the auto-generated feature suffix in artifact names. + /// + /// By default the suffix is the `+`-joined list of feature names (e.g. + /// `full`, `foo+bar`), or `no-default` when only `no_default_features` is + /// set. Set this to a custom string to replace the suffix entirely, or to + /// `""` to omit any suffix. + /// + /// ```toml + /// [[builders]] + /// type = "cargo" + /// features = ["full"] + /// suffix = "production" + /// ``` + pub suffix: Option<String>, + /// Run cross-compilation targets in parallel using isolated temporary /// target directories. /// @@ -95,6 +134,9 @@ impl Default for CargoBuilderConfig { bins: Vec::new(), parallel: default_parallel(), use_cross: false, + features: Vec::new(), + no_default_features: false, + suffix: None, } } } @@ -284,6 +326,14 @@ async fn run_cargo_build( cmd.arg("--target-dir").arg(dir); } + if !config.features.is_empty() { + cmd.arg("--features").arg(config.features.join(",")); + } + + if config.no_default_features { + cmd.arg("--no-default-features"); + } + let mut child = cmd .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -359,8 +409,8 @@ async fn run_cargo_build( continue; } - // Name the artifact as `{stem}-{version}-{triple}{ext}` so that - // binaries for different targets can coexist in the same dist dir. + // Name the artifact as `{stem}-{version}-{triple}[-{suffix}]{ext}` so + // that binaries for different targets / feature sets can coexist. let stem = path .file_stem() .map(|s| s.to_string_lossy().into_owned()) @@ -369,7 +419,13 @@ async fn run_cargo_build( .extension() .map(|e| format!(".{}", e.to_string_lossy())) .unwrap_or_default(); - let name = format!("{stem}-{version}-{triple}{dot_ext}"); + let feature_suf = + feature_suffix(&config.features, config.no_default_features, &config.suffix); + let name = if let Some(ref suf) = feature_suf { + format!("{stem}-{version}-{triple}-{suf}{dot_ext}") + } else { + format!("{stem}-{version}-{triple}{dot_ext}") + }; artifacts.push(ArtifactPath { path, @@ -472,6 +528,28 @@ async fn read_crate_version(manifest_path: Option<&std::path::Path>) -> Result<S .ok_or_else(|| miette!("no version field in [package] in {}", path.display())) } +/// Compute the feature-derived suffix for an artifact name. +/// +/// Returns `None` when no suffix is needed (backward-compatible default). +fn feature_suffix( + features: &[String], + no_default_features: bool, + suffix: &Option<String>, +) -> Option<String> { + suffix + .clone() + .or_else(|| { + if !features.is_empty() { + Some(features.join("+")) + } else if no_default_features { + Some("no-default".to_owned()) + } else { + None + } + }) + .filter(|s| !s.is_empty()) +} + #[cfg(test)] mod tests { use super::*; @@ -608,6 +686,96 @@ mod tests { assert_eq!(name, "abbaye-0.10.0-x86_64-pc-windows-msvc.exe"); } + // ─── feature_suffix ─────────────────────────────────────────────────────── + + #[test] + fn suffix_none_when_no_features_and_defaults() { + let s = feature_suffix(&[], false, &None); + assert_eq!(s, None); + } + + #[test] + fn suffix_single_feature() { + let s = feature_suffix(&["full".into()], false, &None); + assert_eq!(s.as_deref(), Some("full")); + } + + #[test] + fn suffix_multiple_features_joined_with_plus() { + let s = feature_suffix(&["foo".into(), "bar".into()], false, &None); + assert_eq!(s.as_deref(), Some("foo+bar")); + } + + #[test] + fn suffix_no_default_without_features() { + let s = feature_suffix(&[], true, &None); + assert_eq!(s.as_deref(), Some("no-default")); + } + + #[test] + fn suffix_no_default_with_features_uses_features() { + let s = feature_suffix(&["full".into()], true, &None); + assert_eq!(s.as_deref(), Some("full")); + } + + #[test] + fn suffix_custom_override() { + let s = feature_suffix(&["full".into()], false, &Some("production".into())); + assert_eq!(s.as_deref(), Some("production")); + } + + #[test] + fn suffix_empty_string_treated_as_none() { + let s = feature_suffix(&["full".into()], false, &Some(String::new())); + assert_eq!(s, None); + } + + // ─── Artifact name generation ───────────────────────────────────────────── + + #[test] + fn artifact_name_with_single_feature() { + let stem = "abbaye"; + let dot_ext = ""; + let version = "0.10.0"; + let triple = "x86_64-unknown-linux-musl"; + let suf = "full"; + let name = format!("{stem}-{version}-{triple}-{suf}{dot_ext}"); + assert_eq!(name, "abbaye-0.10.0-x86_64-unknown-linux-musl-full"); + } + + #[test] + fn artifact_name_with_exe_and_feature() { + let stem = "abbaye"; + let dot_ext = ".exe"; + let version = "0.10.0"; + let triple = "x86_64-pc-windows-msvc"; + let suf = "lite"; + let name = format!("{stem}-{version}-{triple}-{suf}{dot_ext}"); + assert_eq!(name, "abbaye-0.10.0-x86_64-pc-windows-msvc-lite.exe"); + } + + #[test] + fn artifact_name_with_no_default_only() { + let stem = "abbaye"; + let dot_ext = ""; + let version = "0.10.0"; + let triple = "x86_64-unknown-linux-gnu"; + let suf = "no-default"; + let name = format!("{stem}-{version}-{triple}-{suf}{dot_ext}"); + assert_eq!(name, "abbaye-0.10.0-x86_64-unknown-linux-gnu-no-default"); + } + + #[test] + fn artifact_name_with_custom_suffix() { + let stem = "abbaye"; + let dot_ext = ""; + let version = "0.10.0"; + let triple = "x86_64-unknown-linux-musl"; + let suf = "production"; + let name = format!("{stem}-{version}-{triple}-{suf}{dot_ext}"); + assert_eq!(name, "abbaye-0.10.0-x86_64-unknown-linux-musl-production"); + } + // ─── relocate_artifacts ────────────────────────────────────────────────── #[tokio::test] @@ -764,6 +932,26 @@ mod tests { let config = CargoBuilderConfig::default(); assert!(!config.use_cross, "use_cross should default to false"); } + + // ─── Feature fields defaults ────────────────────────────────────────────── + + #[test] + fn features_defaults_to_empty() { + let config = CargoBuilderConfig::default(); + assert!(config.features.is_empty()); + } + + #[test] + fn no_default_features_defaults_to_false() { + let config = CargoBuilderConfig::default(); + assert!(!config.no_default_features); + } + + #[test] + fn suffix_defaults_to_none() { + let config = CargoBuilderConfig::default(); + assert!(config.suffix.is_none()); + } } /// Configuration for [`CargoDocBuilder`]. -
modified src/builders/mod.rs
diff --git a/src/builders/mod.rs b/src/builders/mod.rs index 781617e..a491e74 100644 --- a/src/builders/mod.rs +++ b/src/builders/mod.rs @@ -59,7 +59,7 @@ //! | TOML `type` | Rust variant | What it does | //! |--------------|---------------|-----------------------------------------------------------| //! | `archive` | `Archive` | Creates a `.tar.gz` of the source tree. | -//! | `cargo` | `Cargo` | `cargo build --release` (or `cross build --release`), optionally for multiple targets. | +//! | `cargo` | `Cargo` | `cargo build --release` (or `cross build --release`), for multiple targets and/or feature sets. Supports `features`, `no_default_features`, and a custom artifact `suffix`. | //! | `cargo_doc` | `CargoDoc` | `cargo doc`. | //! | `markdown` | `Markdown` | Renders a directory of `.md` files to HTML. | //! | `script` | `Script` | Runs an arbitrary sequence of `sh -c` commands. | @@ -150,7 +150,10 @@ //! //! When a `cargo` builder lists multiple `targets`, it spawns one Tokio task //! per target inside its own inner `JoinSet` - a second level of concurrency -//! nested inside the outer builder task. +//! nested inside the outer builder task. Feature flags (`features`, +//! `no_default_features`) apply uniformly to all targets in the entry; use +//! separate `[[builders]]` entries with `depends_on` for different feature +//! sets per target. //! //! Each inner task: //! @@ -164,6 +167,11 @@ //! paths before the `TempDir` is dropped. //! 5. Emits a `ChildFinish` event when done. //! +//! Artifact names include a feature-derived suffix (e.g. +//! `myapp-0.10.0-x86_64-full`) so binaries built with different feature sets +//! do not collide in the distribution directory. See the `suffix` field for +//! custom overrides. +//! //! Within `run_cargo_build` itself, stderr and the JSON stdout are consumed //! concurrently in separate `tokio::spawn` tasks so neither stream blocks the //! other. @@ -327,11 +335,18 @@ pub enum AnyBuilder { /// One or more target triples can be specified for cross-compilation; /// omitting `targets` builds for the host platform. /// + /// Feature flags can be passed with `features` (optionally combined with + /// `no_default_features`). The artifact name is suffixed with the feature + /// set (e.g. `myapp-1.0-x86_64-full`); customise the suffix with `suffix`. + /// /// ```toml /// [[builders]] /// type = "cargo" /// targets = ["x86_64-unknown-linux-musl", "aarch64-unknown-linux-musl"] /// manifest_path = "Cargo.toml" # optional + /// features = ["full"] # optional + /// no_default_features = true # optional + /// suffix = "production" # optional, overrides auto suffix /// ``` Cargo(CargoBuilderConfig),