diff --git a/xtask/README.md b/xtask/README.md index cc7d608bd..fd6db0d9b 100644 --- a/xtask/README.md +++ b/xtask/README.md @@ -10,12 +10,11 @@ Usage: xtask Commands: build Build-related subcommands run Run-related subcommands - bump-version Bump the version of the specified package(s) + release Release-related subcommands ci Perform (parts of) the checks done in CI fmt-packages Format all packages in the workspace with rustfmt lint-packages Lint all packages in the workspace with clippy - publish Attempt to publish the specified package - tag-releases Generate git tags for all new package releases + semver-check Semver Checks check-changelog Check the changelog for packages help Print this message or the help of the given subcommand(s) diff --git a/xtask/src/changelog.rs b/xtask/src/changelog.rs index e9c99f338..dd967bcb0 100644 --- a/xtask/src/changelog.rs +++ b/xtask/src/changelog.rs @@ -488,8 +488,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 changelog.finalize( Package::EspHal, - semver::Version::new(0, 2, 0), - jiff::Timestamp::now(), + &semver::Version::new(0, 2, 0), + "2025-05-08 08:36-04".parse().unwrap(), ); let expected = "# Changelog diff --git a/xtask/src/commands/mod.rs b/xtask/src/commands/mod.rs index 18c39e75d..5a82c1120 100644 --- a/xtask/src/commands/mod.rs +++ b/xtask/src/commands/mod.rs @@ -1,17 +1,16 @@ use std::path::Path; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::Args; use esp_metadata::Chip; -pub use self::{build::*, bump_version::*, check_changelog::*, run::*, semver_check::*}; -use crate::{cargo::CargoAction, Package}; +pub use self::{build::*, check_changelog::*, release::*, run::*}; +use crate::{Package, cargo::CargoAction}; mod build; -mod bump_version; mod check_changelog; +mod release; mod run; -mod semver_check; // ---------------------------------------------------------------------------- // Subcommand Arguments diff --git a/xtask/src/commands/release.rs b/xtask/src/commands/release.rs new file mode 100644 index 000000000..a1a8a1ffe --- /dev/null +++ b/xtask/src/commands/release.rs @@ -0,0 +1,34 @@ +use clap::Subcommand; + +pub mod bump_version; +pub mod publish; +pub mod semver_check; +pub mod tag_releases; + +pub use bump_version::*; +pub use publish::*; +pub use semver_check::*; +pub use tag_releases::*; + +// ---------------------------------------------------------------------------- +// Subcommands + +#[derive(Debug, Subcommand)] +pub enum Release { + /// Bump the version of the specified package(s). + /// + /// This command will, for each specified package: + /// - Verify that the crate can be released (e.g. it doesn't refer to git + /// dependencies) + /// - Update the version in `Cargo.toml` files + /// - Update the version in dependencies' `Cargo.toml` files + /// - Check if the changelog can be finalized + /// - Update the version in the changelog + /// - Replaces `{{currentVersion}}` markers in source files and the + /// migration guide. + BumpVersion(BumpVersionArgs), + /// Attempt to publish the specified package. + Publish(PublishArgs), + /// Generate git tags for all new package releases. + TagReleases(TagReleasesArgs), +} diff --git a/xtask/src/commands/bump_version.rs b/xtask/src/commands/release/bump_version.rs similarity index 100% rename from xtask/src/commands/bump_version.rs rename to xtask/src/commands/release/bump_version.rs diff --git a/xtask/src/commands/release/publish.rs b/xtask/src/commands/release/publish.rs new file mode 100644 index 000000000..c61da666c --- /dev/null +++ b/xtask/src/commands/release/publish.rs @@ -0,0 +1,53 @@ +use std::path::Path; + +use anyhow::{Result, bail}; +use clap::Args; + +use crate::{Package, cargo::CargoArgsBuilder, windows_safe_path}; + +#[derive(Debug, Args)] +pub struct PublishArgs { + /// Package to publish (performs a dry-run by default). + #[arg(value_enum)] + package: Package, + + /// Do not pass the `--dry-run` argument, actually try to publish. + #[arg(long)] + no_dry_run: bool, +} + +pub fn publish(workspace: &Path, args: PublishArgs) -> Result<()> { + let package_name = args.package.to_string(); + let package_path = windows_safe_path(&workspace.join(&package_name)); + + use Package::*; + let mut publish_args = match args.package { + Examples | HilTest | QaTest => { + bail!( + "Invalid package '{}' specified, this package should not be published!", + args.package + ) + } + + EspBacktrace | EspHal | EspHalEmbassy | EspIeee802154 | EspLpHal | EspPrintln + | EspRiscvRt | EspStorage | EspWifi | XtensaLxRt => vec!["--no-verify"], + + _ => vec![], + }; + + if !args.no_dry_run { + publish_args.push("--dry-run"); + } + + let builder = CargoArgsBuilder::default() + .subcommand("publish") + .args(&publish_args); + + let args = builder.build(); + log::debug!("{args:#?}"); + + // Execute `cargo publish` command from the package root: + crate::cargo::run(&args, &package_path)?; + + Ok(()) +} diff --git a/xtask/src/commands/semver_check.rs b/xtask/src/commands/release/semver_check.rs similarity index 100% rename from xtask/src/commands/semver_check.rs rename to xtask/src/commands/release/semver_check.rs diff --git a/xtask/src/commands/release/tag_releases.rs b/xtask/src/commands/release/tag_releases.rs new file mode 100644 index 000000000..1b67cd350 --- /dev/null +++ b/xtask/src/commands/release/tag_releases.rs @@ -0,0 +1,75 @@ +use std::{path::Path, process::Command}; + +use anyhow::Result; +use clap::Args; +use strum::IntoEnumIterator; + +use crate::{Package, package_version}; + +#[derive(Debug, Args)] +pub struct TagReleasesArgs { + /// Package(s) to tag. + #[arg(long, value_enum, value_delimiter = ',', default_values_t = Package::iter())] + packages: Vec, + + /// Actually try and create the tags + #[arg(long)] + no_dry_run: bool, +} + +pub fn tag_releases(workspace: &Path, mut args: TagReleasesArgs) -> Result<()> { + args.packages.sort(); + + #[derive(serde::Serialize)] + struct DocumentationItem { + name: String, + tag: String, + } + + let mut created = Vec::new(); + for package in args.packages { + // If a package does not require documentation, this also means that it is not + // published (maybe this function needs a better name), so we can skip tagging + // it: + if !package.is_published() { + continue; + } + + let version = package_version(workspace, package)?; + let tag = package.tag(&version); + + if args.no_dry_run { + let output = Command::new("git") + .arg("tag") + .arg(&tag) + .current_dir(workspace) + .output()?; + + if output.stderr.is_empty() { + log::info!("Created tag '{tag}'"); + } else { + let err = String::from_utf8_lossy(&output.stderr); + let err = err.trim_start_matches("fatal: "); + log::warn!("{}", err); + } + } else { + log::info!("Would create '{tag}' if `--no-dry-run` was passed.") + } + created.push(DocumentationItem { + name: package.to_string(), + tag, + }); + } + + if args.no_dry_run { + log::info!("Created {} tags", created.len()); + log::info!("IMPORTANT: Don't forget to push the tags to the correct remote!"); + } + + log::info!( + "Documentation workflow input for these packages:\r\n\r\n {:#}", + serde_json::to_string(&created)? + ); + + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f088651b8..7847fe914 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,18 +1,17 @@ use std::{ fs, path::{Path, PathBuf}, - process::Command, time::Instant, }; -use anyhow::{bail, Result}; +use anyhow::{Result, bail}; use clap::{Args, Parser}; use esp_metadata::{Chip, Config}; use strum::IntoEnumIterator; use xtask::{ + Package, cargo::{CargoAction, CargoArgsBuilder}, commands::*, - Package, }; // ---------------------------------------------------------------------------- @@ -26,19 +25,10 @@ enum Cli { /// Run-related subcommands #[clap(subcommand)] Run(Run), + /// Release-related subcommands + #[clap(subcommand)] + Release(Release), - /// Bump the version of the specified package(s). - /// - /// This command will, for each specified package: - /// - Verify that the crate can be released (e.g. it doesn't refer to git - /// dependencies) - /// - Update the version in `Cargo.toml` files - /// - Update the version in dependencies' `Cargo.toml` files - /// - Check if the changelog can be finalized - /// - Update the version in the changelog - /// - Replaces `{{currentVersion}}` markers in source files and the - /// migration guide. - BumpVersion(BumpVersionArgs), /// Perform (parts of) the checks done in CI Ci(CiArgs), /// Format all packages in the workspace with rustfmt @@ -46,10 +36,6 @@ enum Cli { FmtPackages(FmtPackagesArgs), /// Lint all packages in the workspace with clippy LintPackages(LintPackagesArgs), - /// Attempt to publish the specified package. - Publish(PublishArgs), - /// Generate git tags for all new package releases. - TagReleases(TagReleasesArgs), /// Semver Checks SemverCheck(SemverCheckArgs), /// Check the changelog for packages. @@ -89,28 +75,6 @@ struct LintPackagesArgs { fix: bool, } -#[derive(Debug, Args)] -struct PublishArgs { - /// Package to publish (performs a dry-run by default). - #[arg(value_enum)] - package: Package, - - /// Do not pass the `--dry-run` argument, actually try to publish. - #[arg(long)] - no_dry_run: bool, -} - -#[derive(Debug, Args)] -struct TagReleasesArgs { - /// Package(s) to tag. - #[arg(long, value_enum, value_delimiter = ',', default_values_t = Package::iter())] - packages: Vec, - - /// Actually try and create the tags - #[arg(long)] - no_dry_run: bool, -} - #[derive(Debug, Args)] struct CheckChangelogArgs { /// Package(s) to tag. @@ -156,12 +120,16 @@ fn main() -> Result<()> { Run::Tests(args) => tests(&workspace, args, CargoAction::Run), }, - Cli::BumpVersion(args) => bump_version(&workspace, args), + // Release-related subcommands: + Cli::Release(release) => match release { + Release::BumpVersion(args) => bump_version(&workspace, args), + Release::TagReleases(args) => tag_releases(&workspace, args), + Release::Publish(args) => publish(&workspace, args), + }, + Cli::Ci(args) => run_ci_checks(&workspace, args), Cli::FmtPackages(args) => fmt_packages(&workspace, args), Cli::LintPackages(args) => lint_packages(&workspace, args), - Cli::Publish(args) => publish(&workspace, args), - Cli::TagReleases(args) => tag_releases(&workspace, args), Cli::SemverCheck(args) => semver_checks(&workspace, args), Cli::CheckChangelog(args) => check_changelog(&workspace, &args.packages, args.normalize), } @@ -304,42 +272,6 @@ fn lint_package( Ok(()) } -fn publish(workspace: &Path, args: PublishArgs) -> Result<()> { - let package_name = args.package.to_string(); - let package_path = xtask::windows_safe_path(&workspace.join(&package_name)); - - use Package::*; - let mut publish_args = match args.package { - Examples | HilTest | QaTest => { - bail!( - "Invalid package '{}' specified, this package should not be published!", - args.package - ) - } - - EspBacktrace | EspHal | EspHalEmbassy | EspIeee802154 | EspLpHal | EspPrintln - | EspRiscvRt | EspStorage | EspWifi | XtensaLxRt => vec!["--no-verify"], - - _ => vec![], - }; - - if !args.no_dry_run { - publish_args.push("--dry-run"); - } - - let builder = CargoArgsBuilder::default() - .subcommand("publish") - .args(&publish_args); - - let args = builder.build(); - log::debug!("{args:#?}"); - - // Execute `cargo publish` command from the package root: - xtask::cargo::run(&args, &package_path)?; - - Ok(()) -} - fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> { println!("::add-matcher::.github/rust-matchers.json"); @@ -506,60 +438,3 @@ fn run_ci_checks(workspace: &Path, args: CiArgs) -> Result<()> { Ok(()) } - -fn tag_releases(workspace: &Path, mut args: TagReleasesArgs) -> Result<()> { - args.packages.sort(); - - #[derive(serde::Serialize)] - struct DocumentationItem { - name: String, - tag: String, - } - - let mut created = Vec::new(); - for package in args.packages { - // If a package does not require documentation, this also means that it is not - // published (maybe this function needs a better name), so we can skip tagging - // it: - if !package.is_published() { - continue; - } - - let version = xtask::package_version(workspace, package)?; - let tag = package.tag(&version); - - if args.no_dry_run { - let output = Command::new("git") - .arg("tag") - .arg(&tag) - .current_dir(workspace) - .output()?; - - if output.stderr.is_empty() { - log::info!("Created tag '{tag}'"); - } else { - let err = String::from_utf8_lossy(&output.stderr); - let err = err.trim_start_matches("fatal: "); - log::warn!("{}", err); - } - } else { - log::info!("Would create '{tag}' if `--no-dry-run` was passed.") - } - created.push(DocumentationItem { - name: package.to_string(), - tag, - }); - } - - if args.no_dry_run { - log::info!("Created {} tags", created.len()); - log::info!("IMPORTANT: Don't forget to push the tags to the correct remote!"); - } - - log::info!( - "Documentation workflow input for these packages:\r\n\r\n {:#}", - serde_json::to_string(&created)? - ); - - Ok(()) -}