Add build-package and bump-version subcommands to xtask package (#1172)

* Implement builder pattern for cargo command-line args, refactoring

* Add an `xtask` subcommand to build a package (not its examples)

* Add an `xtask` subcommand to bump the versions of packages
This commit is contained in:
Jesse Braham 2024-02-16 11:21:31 +00:00 committed by GitHub
parent 2a5996f408
commit 0483b47e77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 278 additions and 38 deletions

View File

@ -9,4 +9,6 @@ anyhow = "1.0.79"
clap = { version = "4.5.0", features = ["derive"] }
env_logger = "0.11.1"
log = "0.4.20"
semver = "1.0.21"
strum = { version = "0.26.1", features = ["derive"] }
toml_edit = "0.22.5"

View File

@ -10,6 +10,8 @@ Usage: xtask <COMMAND>
Commands:
build-documentation Build documentation for the specified chip
build-examples Build all examples for the specified chip
build-package Build the specified package with the given options
bump-version Bump the version of the specified package(s)
help Print this message or the help of the given subcommand(s)
Options:

108
xtask/src/cargo.rs Normal file
View File

@ -0,0 +1,108 @@
//! Tools for working with Cargo.
use std::{
path::Path,
process::{Command, Stdio},
};
use anyhow::{bail, Result};
/// Execute cargo with the given arguments and from the specified directory.
pub fn run(args: &[String], cwd: &Path) -> Result<()> {
if !cwd.is_dir() {
bail!("The `cwd` argument MUST be a directory");
}
let status = Command::new("cargo")
.args(args)
.current_dir(cwd)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.status()?;
// Make sure that we return an appropriate exit code here, as Github Actions
// requires this in order to function correctly:
if status.success() {
Ok(())
} else {
bail!("Failed to execute cargo subcommand")
}
}
#[derive(Debug, Default)]
pub struct CargoArgsBuilder {
toolchain: Option<String>,
subcommand: String,
target: Option<String>,
features: Vec<String>,
args: Vec<String>,
}
impl CargoArgsBuilder {
#[must_use]
pub fn toolchain<S>(mut self, toolchain: S) -> Self
where
S: Into<String>,
{
self.toolchain = Some(toolchain.into());
self
}
#[must_use]
pub fn subcommand<S>(mut self, subcommand: S) -> Self
where
S: Into<String>,
{
self.subcommand = subcommand.into();
self
}
#[must_use]
pub fn target<S>(mut self, target: S) -> Self
where
S: Into<String>,
{
self.target = Some(target.into());
self
}
#[must_use]
pub fn features(mut self, features: &[String]) -> Self {
self.features = features.to_vec();
self
}
#[must_use]
pub fn arg<S>(mut self, arg: S) -> Self
where
S: Into<String>,
{
self.args.push(arg.into());
self
}
#[must_use]
pub fn build(self) -> Vec<String> {
let mut args = vec![];
if let Some(toolchain) = self.toolchain {
args.push(format!("+{toolchain}"));
}
args.push(self.subcommand);
if let Some(target) = self.target {
args.push(format!("--target={target}"));
}
if !self.features.is_empty() {
args.push(format!("--features={}", self.features.join(",")));
}
for arg in self.args {
args.push(arg);
}
args
}
}

View File

@ -2,14 +2,17 @@ use std::{
collections::VecDeque,
fs,
path::{Path, PathBuf},
process::{Command, Stdio},
};
use anyhow::{anyhow, bail, Result};
use anyhow::{bail, Result};
use clap::ValueEnum;
use strum::{Display, EnumIter, IntoEnumIterator};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, ValueEnum)]
use self::cargo::CargoArgsBuilder;
mod cargo;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, ValueEnum)]
#[strum(serialize_all = "kebab-case")]
pub enum Package {
EspHal,
@ -111,6 +114,14 @@ impl Metadata {
}
}
#[derive(Debug, Clone, Copy, Display, ValueEnum)]
#[strum(serialize_all = "lowercase")]
pub enum Version {
Major,
Minor,
Patch,
}
/// Build the documentation for the specified package and device.
pub fn build_documentation(
workspace: &Path,
@ -124,20 +135,22 @@ pub fn build_documentation(
log::info!("Building '{package_name}' documentation targeting '{chip}'");
// Build up an array of command-line arguments to pass to `cargo doc`:
let mut args = vec![
"doc".into(),
"-Zbuild-std=core".into(), // Required for Xtensa, for some reason
format!("--target={target}"),
format!("--features={}", chip.to_string()),
];
// Build up an array of command-line arguments to pass to `cargo`:
let mut builder = CargoArgsBuilder::default()
.subcommand("doc")
.arg("-Zbuild-std=core") // Required for Xtensa, for some reason
.target(target)
.features(&[chip.to_string()]);
if open {
args.push("--open".into());
builder = builder.arg("--open");
}
let args = builder.build();
log::debug!("{args:#?}");
// Execute `cargo doc` from the package root:
cargo(&args, &package_path)?;
cargo::run(&args, &package_path)?;
Ok(())
}
@ -217,40 +230,105 @@ pub fn build_example(
format!("--example={}", example.name())
};
let mut features = example.features().to_vec();
features.push(chip.to_string());
let mut builder = CargoArgsBuilder::default()
.subcommand("build")
.arg("-Zbuild-std=alloc,core")
.arg("--release")
.target(target)
.features(&features)
.arg(bin);
// If targeting an Xtensa device, we must use the '+esp' toolchain modifier:
let mut args = vec![];
if target.starts_with("xtensa") {
args.push("+esp".into());
builder = builder.toolchain("esp");
}
args.extend(vec![
"build".into(),
"-Zbuild-std=alloc,core".into(),
"--release".into(),
format!("--target={target}"),
format!("--features={},{}", chip, example.features().join(",")),
bin,
]);
let args = builder.build();
log::debug!("{args:#?}");
cargo(&args, package_path)?;
cargo::run(&args, package_path)?;
Ok(())
}
fn cargo(args: &[String], cwd: &Path) -> Result<()> {
let status = Command::new("cargo")
.args(args)
.current_dir(cwd)
.stdout(Stdio::piped())
.stderr(Stdio::inherit())
.status()?;
/// Build the specified package, using the given toolchain/target/features if
/// provided.
pub fn build_package(
package_path: &Path,
features: Vec<String>,
toolchain: Option<String>,
target: Option<String>,
) -> Result<()> {
log::info!("Building package '{}'", package_path.display());
if !features.is_empty() {
log::info!(" Features: {}", features.join(","));
}
if let Some(ref target) = target {
log::info!(" Target: {}", target);
}
let mut builder = CargoArgsBuilder::default()
.subcommand("build")
.arg("-Zbuild-std=core")
.arg("--release");
if let Some(toolchain) = toolchain {
builder = builder.toolchain(toolchain);
}
if let Some(target) = target {
builder = builder.target(target);
}
if !features.is_empty() {
builder = builder.features(&features);
}
let args = builder.build();
log::debug!("{args:#?}");
cargo::run(&args, package_path)?;
// Make sure that we return an appropriate exit code here, as Github Actions
// requires this in order to function correctly:
if status.success() {
Ok(())
} else {
Err(anyhow!("Failed to execute cargo subcommand"))
}
/// Bump the version of the specified package by the specified amount.
pub fn bump_version(workspace: &Path, package: Package, amount: Version) -> Result<()> {
let manifest_path = workspace.join(package.to_string()).join("Cargo.toml");
let manifest = fs::read_to_string(&manifest_path)?;
let mut manifest = manifest.parse::<toml_edit::Document>()?;
let version = manifest["package"]["version"]
.to_string()
.trim()
.trim_matches('"')
.to_string();
let prev_version = &version;
let mut version = semver::Version::parse(&version)?;
match amount {
Version::Major => {
version.major += 1;
version.minor = 0;
version.patch = 0;
}
Version::Minor => {
version.minor += 1;
version.patch = 0;
}
Version::Patch => {
version.patch += 1;
}
}
log::info!("Bumping version for package: {package} ({prev_version} -> {version})");
manifest["package"]["version"] = toml_edit::value(version.to_string());
fs::write(manifest_path, manifest.to_string())?;
Ok(())
}

View File

@ -2,7 +2,8 @@ use std::path::{Path, PathBuf};
use anyhow::{bail, Result};
use clap::{Args, Parser};
use xtask::{Chip, Package};
use strum::IntoEnumIterator;
use xtask::{Chip, Package, Version};
// ----------------------------------------------------------------------------
// Command-line Interface
@ -13,6 +14,10 @@ enum Cli {
BuildDocumentation(BuildDocumentationArgs),
/// Build all examples for the specified chip.
BuildExamples(BuildExamplesArgs),
/// Build the specified package with the given options.
BuildPackage(BuildPackageArgs),
/// Bump the version of the specified package(s)
BumpVersion(BumpVersionArgs),
}
#[derive(Debug, Args)]
@ -38,6 +43,32 @@ struct BuildExamplesArgs {
chip: Chip,
}
#[derive(Debug, Args)]
struct BuildPackageArgs {
/// Package to build.
#[arg(value_enum)]
package: Package,
/// Target to build for.
#[arg(long)]
target: Option<String>,
/// Features to build with.
#[arg(long, value_delimiter = ',')]
features: Vec<String>,
/// Toolchain to build with.
#[arg(long)]
toolchain: Option<String>,
}
#[derive(Debug, Args)]
struct BumpVersionArgs {
/// How much to bump the version by.
#[arg(value_enum)]
amount: Version,
/// Package(s) to target.
#[arg(value_enum, default_values_t = Package::iter())]
packages: Vec<Package>,
}
// ----------------------------------------------------------------------------
// Application
@ -52,6 +83,8 @@ fn main() -> Result<()> {
match Cli::parse() {
Cli::BuildDocumentation(args) => build_documentation(&workspace, args),
Cli::BuildExamples(args) => build_examples(&workspace, args),
Cli::BuildPackage(args) => build_package(&workspace, args),
Cli::BumpVersion(args) => bump_version(&workspace, args),
}
}
@ -107,6 +140,23 @@ fn build_examples(workspace: &Path, mut args: BuildExamplesArgs) -> Result<()> {
.try_for_each(|example| xtask::build_example(&package_path, args.chip, target, example))
}
fn build_package(workspace: &Path, args: BuildPackageArgs) -> Result<()> {
// Absolute path of the package's root:
let package_path = workspace.join(args.package.to_string());
// Build the package using the provided features and/or target, if any:
xtask::build_package(&package_path, args.features, args.toolchain, args.target)
}
fn bump_version(workspace: &Path, args: BumpVersionArgs) -> Result<()> {
// Bump the version by the specified amount for each given package:
for package in args.packages {
xtask::bump_version(workspace, package, args.amount)?;
}
Ok(())
}
// ----------------------------------------------------------------------------
// Helper Functions