xtask: Upgrade xshell to 0.2

This commit is contained in:
Kévin Commaille 2024-11-29 15:47:17 +01:00 committed by strawberry
parent bab06ed375
commit ce715d4c13
6 changed files with 93 additions and 59 deletions

View File

@ -16,7 +16,7 @@ serde = { workspace = true }
serde_json = { workspace = true }
toml = { version = "0.8.2", default-features = false, features = ["parse"] }
toml_edit = { version = "0.22.22", optional = true }
xshell = "0.1.17"
xshell = "0.2.7"
[lints]
workspace = true

View File

@ -9,8 +9,10 @@ use serde::{de::IgnoredAny, Deserialize};
#[cfg(feature = "default")]
use toml_edit::{value, DocumentMut};
#[cfg(feature = "default")]
use xshell::{cmd, pushd, read_file, write_file};
use xshell::Shell;
#[cfg(feature = "default")]
use crate::cmd;
use crate::{util::ask_yes_no, Metadata, Result};
const CRATESIO_API: &str = "https://crates.io/api/v1/crates";
@ -74,15 +76,15 @@ impl Package {
#[cfg(feature = "default")]
impl Package {
/// Update the version of this crate.
pub fn update_version(&mut self, version: &Version, dry_run: bool) -> Result<()> {
pub fn update_version(&mut self, sh: &Shell, version: &Version, dry_run: bool) -> Result<()> {
println!("Updating {} to version {version}", self.name);
if !dry_run {
let mut document = read_file(&self.manifest_path)?.parse::<DocumentMut>()?;
let mut document = sh.read_file(&self.manifest_path)?.parse::<DocumentMut>()?;
document["package"]["version"] = value(version.to_string());
write_file(&self.manifest_path, document.to_string())?;
sh.write_file(&self.manifest_path, document.to_string())?;
}
self.version = version.clone();
@ -92,7 +94,12 @@ impl Package {
/// Update the version of this crate in dependant crates' manifests, with the given version
/// prefix.
pub(crate) fn update_dependants(&self, metadata: &Metadata, dry_run: bool) -> Result<()> {
pub(crate) fn update_dependants(
&self,
sh: &Shell,
metadata: &Metadata,
dry_run: bool,
) -> Result<()> {
if self.name == "ruma" {
for package in metadata.packages.iter().filter(|p| {
p.manifest_path.starts_with(&metadata.workspace_root)
@ -101,7 +108,8 @@ impl Package {
println!("Updating dependency in {} crate…", package.name);
if !dry_run {
let mut document = read_file(&package.manifest_path)?.parse::<DocumentMut>()?;
let mut document =
sh.read_file(&package.manifest_path)?.parse::<DocumentMut>()?;
let version = if !self.version.pre.is_empty() {
format!("={}", self.version)
@ -119,12 +127,12 @@ impl Package {
document[kind][&self.name]["version"] = value(version.as_str());
}
write_file(&package.manifest_path, document.to_string())?;
sh.write_file(&package.manifest_path, document.to_string())?;
}
}
} else {
let workspace_manifest_path = metadata.workspace_root.join("Cargo.toml");
let mut document = read_file(&workspace_manifest_path)?.parse::<DocumentMut>()?;
let mut document = sh.read_file(&workspace_manifest_path)?.parse::<DocumentMut>()?;
let workspace_deps = &mut document["workspace"]["dependencies"];
println!("Updating workspace dependency…");
@ -139,7 +147,7 @@ impl Package {
workspace_deps[&self.name]["version"] = value(version.as_str());
write_file(&workspace_manifest_path, document.to_string())?;
sh.write_file(&workspace_manifest_path, document.to_string())?;
}
}
@ -149,7 +157,7 @@ impl Package {
/// Get the changes for the version.
///
/// If `update` is `true`, update the changelog for the release of the given version.
pub fn changes(&self, update: bool) -> Result<String> {
pub fn changes(&self, sh: &Shell, update: bool) -> Result<String> {
if self.name == "ruma-macros" {
// ruma-macros doesn't have a changelog and won't create a tag.
return Ok(String::new());
@ -158,7 +166,7 @@ impl Package {
let mut changelog_path = self.manifest_path.clone();
changelog_path.set_file_name("CHANGELOG.md");
let changelog = read_file(&changelog_path)?;
let changelog = sh.read_file(&changelog_path)?;
let version = Version {
pre: semver::Prerelease::EMPTY,
build: semver::BuildMetadata::EMPTY,
@ -196,7 +204,7 @@ impl Package {
let rest = &changelog[changes_end..];
let changelog = format!("# [unreleased]\n\n# {}\n\n{changes}\n{rest}", self.version);
write_file(&changelog_path, changelog)?;
sh.write_file(&changelog_path, changelog)?;
}
Ok(changes.to_owned())
@ -211,16 +219,16 @@ impl Package {
}
/// Publish this package on crates.io.
pub fn publish(&self, client: &Client, dry_run: bool) -> Result<()> {
pub fn publish(&self, sh: &Shell, client: &Client, dry_run: bool) -> Result<()> {
println!("Publishing {} {} on crates.io…", self.name, self.version);
let _dir = pushd(self.manifest_path.parent().unwrap())?;
let _dir = sh.push_dir(self.manifest_path.parent().unwrap());
if self.is_published(client)? {
if !ask_yes_no("This version is already published. Skip this step and continue?")? {
return Err("Release interrupted by user.".into());
}
} else if !dry_run {
cmd!("cargo publish").run()?;
cmd!(sh, "cargo publish").run()?;
}
Ok(())

View File

@ -4,7 +4,7 @@
use std::path::Path;
use clap::{Args, Subcommand};
use xshell::pushd;
use xshell::Shell;
use crate::{cmd, Metadata, Result, NIGHTLY};
@ -81,12 +81,16 @@ pub struct CiTask {
/// The metadata of the workspace.
project_metadata: Metadata,
/// The shell API to use to run commands.
sh: Shell,
}
impl CiTask {
pub(crate) fn new(cmd: Option<CiCmd>) -> Result<Self> {
let project_metadata = Metadata::load()?;
Ok(Self { cmd, project_metadata })
let sh = Shell::new()?;
let project_metadata = Metadata::load(&sh)?;
Ok(Self { cmd, sh, project_metadata })
}
fn project_root(&self) -> &Path {
@ -94,7 +98,7 @@ impl CiTask {
}
pub(crate) fn run(self) -> Result<()> {
let _p = pushd(self.project_root())?;
let _p = self.sh.push_dir(self.project_root());
match self.cmd {
Some(CiCmd::Msrv) => self.msrv()?,
@ -147,6 +151,7 @@ impl CiTask {
/// * xtask (no real reason to enforce an MSRV for it)
fn msrv_all(&self) -> Result<()> {
cmd!(
&self.sh,
"rustup run {MSRV} cargo check --workspace --all-features
--exclude ruma
--exclude ruma-macros
@ -159,7 +164,7 @@ impl CiTask {
/// Check ruma crate with default features with the MSRV.
fn msrv_ruma(&self) -> Result<()> {
cmd!("rustup run {MSRV} cargo check -p ruma").run().map_err(Into::into)
cmd!(&self.sh, "rustup run {MSRV} cargo check -p ruma").run().map_err(Into::into)
}
/// Run all the tasks that use the stable version.
@ -177,6 +182,7 @@ impl CiTask {
// ruma-macros is pulled in as a dependency, but excluding it on the command line means its
// features don't get activated. It has only a single feature, which is nightly-only.
cmd!(
&self.sh,
"rustup run stable cargo check
--workspace --all-features --exclude ruma-macros"
)
@ -186,7 +192,7 @@ impl CiTask {
/// Check ruma-client without default features with the stable version.
fn stable_client(&self) -> Result<()> {
cmd!("rustup run stable cargo check -p ruma-client --no-default-features")
cmd!(&self.sh, "rustup run stable cargo check -p ruma-client --no-default-features")
.run()
.map_err(Into::into)
}
@ -194,6 +200,7 @@ impl CiTask {
/// Check ruma-common with onjy the required features with the stable version.
fn stable_common(&self) -> Result<()> {
cmd!(
&self.sh,
"rustup run stable cargo check -p ruma-common
--no-default-features --features client,server"
)
@ -203,20 +210,24 @@ impl CiTask {
/// Run tests on all crates with almost all features with the stable version.
fn test_all(&self) -> Result<()> {
cmd!("rustup run stable cargo test --tests --features __ci").run().map_err(Into::into)
cmd!(&self.sh, "rustup run stable cargo test --tests --features __ci")
.run()
.map_err(Into::into)
}
/// Run tests on all crates with almost all features and the compat features with the stable
/// version.
fn test_compat(&self) -> Result<()> {
cmd!("rustup run stable cargo test --tests --features __ci,compat")
cmd!(&self.sh, "rustup run stable cargo test --tests --features __ci,compat")
.run()
.map_err(Into::into)
}
/// Run doctests on all crates with almost all features with the stable version.
fn test_doc(&self) -> Result<()> {
cmd!("rustup run stable cargo test --doc --features __ci").run().map_err(Into::into)
cmd!(&self.sh, "rustup run stable cargo test --doc --features __ci")
.run()
.map_err(Into::into)
}
/// Run all the tasks that use the nightly version.
@ -230,12 +241,14 @@ impl CiTask {
/// Check the formatting with the nightly version.
fn fmt(&self) -> Result<()> {
cmd!("rustup run {NIGHTLY} cargo fmt -- --check").run().map_err(Into::into)
cmd!(&self.sh, "rustup run {NIGHTLY} cargo fmt -- --check").run().map_err(Into::into)
}
/// Check ruma crate with full feature with the nightly version.
fn nightly_full(&self) -> Result<()> {
cmd!("rustup run {NIGHTLY} cargo check -p ruma --features full").run().map_err(Into::into)
cmd!(&self.sh, "rustup run {NIGHTLY} cargo check -p ruma --features full")
.run()
.map_err(Into::into)
}
/// Check all crates with all features with the nightly version.
@ -243,6 +256,7 @@ impl CiTask {
/// Also checks that all features that are used in the code exist.
fn nightly_all(&self) -> Result<()> {
cmd!(
&self.sh,
"
rustup run {NIGHTLY} cargo check
--workspace --all-features -Z unstable-options
@ -259,7 +273,7 @@ impl CiTask {
/// Check ruma-common with `ruma_identifiers_storage="Box"`
fn msrv_owned_id_box(&self) -> Result<()> {
cmd!("rustup run {MSRV} cargo check -p ruma-common")
cmd!(&self.sh, "rustup run {MSRV} cargo check -p ruma-common")
.env("RUSTFLAGS", "--cfg=ruma_identifiers_storage=\"Box\"")
.run()
.map_err(Into::into)
@ -267,7 +281,7 @@ impl CiTask {
/// Check ruma-common with `ruma_identifiers_storage="Arc"`
fn msrv_owned_id_arc(&self) -> Result<()> {
cmd!("rustup run {MSRV} cargo check -p ruma-common")
cmd!(&self.sh, "rustup run {MSRV} cargo check -p ruma-common")
.env("RUSTFLAGS", "--cfg=ruma_identifiers_storage=\"Arc\"")
.run()
.map_err(Into::into)
@ -276,6 +290,7 @@ impl CiTask {
/// Lint default features with clippy with the nightly version.
fn clippy_default(&self) -> Result<()> {
cmd!(
&self.sh,
"
rustup run {NIGHTLY} cargo clippy
--workspace --all-targets --features=full -- -D warnings
@ -288,6 +303,7 @@ impl CiTask {
/// Lint ruma with clippy with the nightly version and wasm target.
fn clippy_wasm(&self) -> Result<()> {
cmd!(
&self.sh,
"
rustup run {NIGHTLY} cargo clippy --target wasm32-unknown-unknown -p ruma --features
__unstable-mscs,api,canonical-json,client-api,events,html-matrix,identity-service-api,js,markdown,rand,signatures,unstable-unspecified -- -D warnings
@ -301,6 +317,7 @@ impl CiTask {
/// Lint almost all features with clippy with the nightly version.
fn clippy_all(&self) -> Result<()> {
cmd!(
&self.sh,
"
rustup run {NIGHTLY} cargo clippy
--workspace --all-targets --features=__ci,compat -- -D warnings
@ -324,13 +341,14 @@ impl CiTask {
/// Check the sorting of dependencies with the nightly version.
fn dependencies(&self) -> Result<()> {
if cmd!("cargo sort --version").run().is_err() {
if cmd!(&self.sh, "cargo sort --version").run().is_err() {
return Err(
"Could not find cargo-sort. Install it by running `cargo install cargo-sort`"
.into(),
);
}
cmd!(
&self.sh,
"
rustup run {NIGHTLY} cargo sort
--workspace --grouped --check
@ -343,11 +361,11 @@ impl CiTask {
/// Check the typos.
fn typos(&self) -> Result<()> {
if cmd!("typos --version").run().is_err() {
if cmd!(&self.sh, "typos --version").run().is_err() {
return Err(
"Could not find typos. Install it by running `cargo install typos-cli`".into()
);
}
cmd!("typos").run().map_err(Into::into)
cmd!(&self.sh, "typos").run().map_err(Into::into)
}
}

View File

@ -1,4 +1,5 @@
use clap::Args;
use xshell::Shell;
use crate::{cmd, Result, NIGHTLY};
@ -20,7 +21,8 @@ impl DocTask {
rustdocflags += " -Dwarnings";
}
let mut cmd = cmd!("rustup run {NIGHTLY} cargo doc --all-features --no-deps")
let sh = Shell::new()?;
let mut cmd = cmd!(sh, "rustup run {NIGHTLY} cargo doc --all-features --no-deps")
.env("RUSTDOCFLAGS", rustdocflags);
if self.open {

View File

@ -30,6 +30,7 @@ use ci::{CiArgs, CiTask};
use doc::DocTask;
#[cfg(feature = "default")]
use release::{ReleaseArgs, ReleaseTask};
use xshell::Shell;
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
@ -75,8 +76,8 @@ struct Metadata {
impl Metadata {
/// Load a new `Metadata` from the command line.
pub fn load() -> Result<Metadata> {
let metadata_json = cmd!("cargo metadata --no-deps --format-version 1").read()?;
pub fn load(sh: &Shell) -> Result<Metadata> {
let metadata_json = cmd!(sh, "cargo metadata --no-deps --format-version 1").read()?;
Ok(from_json_str(&metadata_json)?)
}
@ -96,11 +97,11 @@ struct Config {
#[cfg(feature = "default")]
impl Config {
/// Load a new `Config` from `config.toml`.
fn load() -> Result<Self> {
fn load(sh: &Shell) -> Result<Self> {
use std::{env, path::Path};
let path = Path::new(&env!("CARGO_MANIFEST_DIR")).join("config.toml");
let config = xshell::read_file(path)?;
let config = sh.read_file(path)?;
Ok(toml::from_str(&config)?)
}
}
@ -117,7 +118,7 @@ struct GithubConfig {
#[macro_export]
macro_rules! cmd {
($cmd:tt) => {
xshell::cmd!($cmd).echo_cmd(false)
($sh: expr, $cmd:tt) => {
xshell::cmd!($sh, $cmd).quiet()
};
}

View File

@ -4,6 +4,7 @@ use clap::Args;
use reqwest::{blocking::Client, StatusCode};
use semver::Version;
use serde_json::json;
use xshell::Shell;
use crate::{cargo::Package, cmd, util::ask_yes_no, GithubConfig, Metadata, Result};
@ -40,6 +41,9 @@ pub struct ReleaseTask {
/// The github configuration required to publish a release.
config: GithubConfig,
/// The shell API to use to run commands.
sh: Shell,
/// List the steps but don't actually change anything
pub dry_run: bool,
}
@ -47,7 +51,8 @@ pub struct ReleaseTask {
impl ReleaseTask {
/// Create a new `ReleaseTask` with the given `name` and `version`.
pub(crate) fn new(name: String, version: Version, dry_run: bool) -> Result<Self> {
let metadata = Metadata::load()?;
let sh = Shell::new()?;
let metadata = Metadata::load(&sh)?;
let package = metadata
.packages
@ -56,11 +61,11 @@ impl ReleaseTask {
.find(|p| p.name == name)
.ok_or(format!("Package {name} not found in cargo metadata"))?;
let config = crate::Config::load()?.github;
let config = crate::Config::load(&sh)?.github;
let http_client = Client::builder().user_agent("ruma xtask").build()?;
Ok(Self { metadata, package, version, http_client, config, dry_run })
Ok(Self { metadata, package, version, http_client, config, sh, dry_run })
}
/// Run the task to effectively create a release.
@ -81,10 +86,10 @@ impl ReleaseTask {
return Err("This crate version is already released".into());
}
let remote = Self::git_remote()?;
let remote = self.git_remote()?;
println!("Checking status of git repository…");
if !cmd!("git status -s -uno").read()?.is_empty()
if !cmd!(&self.sh, "git status -s -uno").read()?.is_empty()
&& !ask_yes_no("This git repository contains uncommitted changes. Continue?")?
{
return Ok(());
@ -101,8 +106,8 @@ impl ReleaseTask {
}
let create_commit = if self.package.version != self.version {
self.package.update_version(&self.version, self.dry_run)?;
self.package.update_dependants(&self.metadata, self.dry_run)?;
self.package.update_version(&self.sh, &self.version, self.dry_run)?;
self.package.update_dependants(&self.sh, &self.metadata, self.dry_run)?;
true
} else if !ask_yes_no(&format!(
"Package is already version {}. Skip creating a commit and continue?",
@ -113,19 +118,19 @@ impl ReleaseTask {
false
};
let changes = &self.package.changes(!prerelease && !self.dry_run)?;
let changes = &self.package.changes(&self.sh, !prerelease && !self.dry_run)?;
if create_commit {
self.commit()?;
}
self.package.publish(&self.http_client, self.dry_run)?;
self.package.publish(&self.sh, &self.http_client, self.dry_run)?;
let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
let branch = cmd!(&self.sh, "git rev-parse --abbrev-ref HEAD").read()?;
if publish_only {
println!("Pushing to remote repository…");
if !self.dry_run {
cmd!("git push {remote} {branch}").run()?;
cmd!(&self.sh, "git push {remote} {branch}").run()?;
}
println!("Crate published successfully!");
@ -135,18 +140,18 @@ impl ReleaseTask {
let tag = &self.tag_name();
println!("Creating git tag '{tag}'…");
if cmd!("git tag -l {tag}").read()?.is_empty() {
if cmd!(&self.sh, "git tag -l {tag}").read()?.is_empty() {
if !self.dry_run {
cmd!("git tag -s {tag} -m {title} -m {changes}").read()?;
cmd!(&self.sh, "git tag -s {tag} -m {title} -m {changes}").read()?;
}
} else if !ask_yes_no("This tag already exists. Skip this step and continue?")? {
return Ok(());
}
println!("Pushing to remote repository…");
if cmd!("git ls-remote --tags {remote} {tag}").read()?.is_empty() {
if cmd!(&self.sh, "git ls-remote --tags {remote} {tag}").read()?.is_empty() {
if !self.dry_run {
cmd!("git push {remote} {branch} {tag}").run()?;
cmd!(&self.sh, "git push {remote} {branch} {tag}").run()?;
}
} else if !ask_yes_no("This tag has already been pushed. Skip this step and continue?")? {
return Ok(());
@ -192,9 +197,9 @@ impl ReleaseTask {
}
/// Load the GitHub config from the config file.
fn git_remote() -> Result<String> {
let branch = cmd!("git rev-parse --abbrev-ref HEAD").read()?;
let remote = cmd!("git config branch.{branch}.remote").read()?;
fn git_remote(&self) -> Result<String> {
let branch = cmd!(&self.sh, "git rev-parse --abbrev-ref HEAD").read()?;
let remote = cmd!(&self.sh, "git config branch.{branch}.remote").read()?;
if remote.is_empty() {
return Err("Could not get current git remote".into());
@ -229,7 +234,7 @@ impl ReleaseTask {
return Err("User aborted commit".into());
}
"d" | "diff" => {
cmd!("git diff").run()?;
cmd!(&self.sh, "git diff").run()?;
}
_ => {
println!("Unknown command.");
@ -247,7 +252,7 @@ impl ReleaseTask {
println!("Creating commit with message '{message}'…");
if !self.dry_run {
cmd!("git commit -a -m {message}").read()?;
cmd!(&self.sh, "git commit -a -m {message}").read()?;
}
Ok(())