use std::path::PathBuf; use clap::{Args, Subcommand}; use xshell::pushd; use crate::{cmd, Metadata, Result}; mod spec_links; use spec_links::check_spec_links; const MSRV: &str = "1.62"; const NIGHTLY: &str = "nightly"; #[derive(Args)] pub struct CiArgs { #[clap(subcommand)] pub cmd: Option, } #[derive(Subcommand)] pub enum CiCmd { /// Check crates compile with the MSRV Msrv, /// Check all crates with all features (msrv) MsrvAll, /// Check ruma-client with default features (msrv) MsrvClient, /// Check ruma crate with default features (msrv) MsrvRuma, /// Check ruma-identifiers with `ruma_identifiers_storage="Box"` MsrvOwnedIdBox, /// Check ruma-identifiers with `ruma_identifiers_storage="Arc"` MsrvOwnedIdArc, /// Run all the tasks that use the stable version Stable, /// Check all crates with all features (stable) StableAll, /// Check ruma-client without default features (stable) StableClient, /// Check ruma-common with only the required features (stable) StableCommon, /// Run all tests with almost all features (stable) TestAll, /// Run doc tests with almost all features (stable) TestDoc, /// Test ruma-common with the compat feature (stable) TestCommon, /// Run all the tasks that use the nightly version Nightly, /// Check formatting (nightly) Fmt, /// Check ruma crate with `full` features (nightly) NightlyFull, /// Check all crates with all features (nightly) NightlyAll, /// Lint default features with clippy (nightly) ClippyDefault, /// Lint ruma-common with clippy on a wasm target (nightly) ClippyWasm, /// Lint almost all features with clippy (nightly) ClippyAll, /// Run all lints that don't need compilation Lint, /// Check sorting of dependencies (lint) Dependencies, /// Check spec links point to a recent version (lint) SpecLinks, /// Check typos Typos, } /// Task to run CI tests. pub struct CiTask { /// Which command to run. cmd: Option, /// The root of the workspace. project_root: PathBuf, } impl CiTask { pub(crate) fn new(cmd: Option) -> Result { let project_root = Metadata::load()?.workspace_root; Ok(Self { cmd, project_root }) } pub(crate) fn run(self) -> Result<()> { let _p = pushd(&self.project_root)?; match self.cmd { Some(CiCmd::Msrv) => self.msrv()?, Some(CiCmd::MsrvAll) => self.msrv_all()?, Some(CiCmd::MsrvClient) => self.msrv_client()?, Some(CiCmd::MsrvRuma) => self.msrv_ruma()?, Some(CiCmd::MsrvOwnedIdBox) => self.msrv_owned_id_box()?, Some(CiCmd::MsrvOwnedIdArc) => self.msrv_owned_id_arc()?, Some(CiCmd::Stable) => self.stable()?, Some(CiCmd::StableAll) => self.stable_all()?, Some(CiCmd::StableClient) => self.stable_client()?, Some(CiCmd::StableCommon) => self.stable_common()?, Some(CiCmd::TestAll) => self.test_all()?, Some(CiCmd::TestDoc) => self.test_doc()?, Some(CiCmd::TestCommon) => self.test_common()?, Some(CiCmd::Nightly) => self.nightly()?, Some(CiCmd::Fmt) => self.fmt()?, Some(CiCmd::NightlyFull) => self.nightly_full()?, Some(CiCmd::NightlyAll) => self.nightly_all()?, Some(CiCmd::ClippyDefault) => self.clippy_default()?, Some(CiCmd::ClippyWasm) => self.clippy_wasm()?, Some(CiCmd::ClippyAll) => self.clippy_all()?, Some(CiCmd::Lint) => self.lint()?, Some(CiCmd::Dependencies) => self.dependencies()?, Some(CiCmd::SpecLinks) => check_spec_links(&self.project_root.join("crates"))?, Some(CiCmd::Typos) => self.typos()?, None => { self.msrv() .and(self.stable()) .and(self.nightly()) .and(self.lint()) .and(self.typos())?; } } Ok(()) } /// Check that the crates compile with the MSRV. fn msrv(&self) -> Result<()> { self.msrv_all()?; self.msrv_client()?; self.msrv_ruma() } /// Check all crates with all features with the MSRV, except: /// * ruma (would pull in ruma-signatures) /// * ruma-client (tested only with client-api feature due to most / all optional HTTP client /// deps having less strict MSRV) /// * ruma-signatures (MSRV exception) /// * xtask (no real reason to enforce an MSRV for it) fn msrv_all(&self) -> Result<()> { cmd!( "rustup run {MSRV} cargo check --workspace --all-features --exclude ruma --exclude ruma-client --exclude ruma-signatures --exclude xtask" ) .run() .map_err(Into::into) } /// Check ruma-client with default features with the MSRV. fn msrv_client(&self) -> Result<()> { cmd!("rustup run {MSRV} cargo check -p ruma-client --features client-api") .run() .map_err(Into::into) } /// 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) } /// Run all the tasks that use the stable version. fn stable(&self) -> Result<()> { self.stable_all()?; self.stable_client()?; self.stable_common()?; self.test_all()?; self.test_doc()?; self.test_common() } /// Check all crates with all features with the stable version. fn stable_all(&self) -> Result<()> { cmd!("rustup run stable cargo check --workspace --all-features").run().map_err(Into::into) } /// 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") .run() .map_err(Into::into) } /// Check ruma-common with onjy the required features with the stable version. fn stable_common(&self) -> Result<()> { cmd!("rustup run stable cargo check -p ruma-common --no-default-features --features client,server").run().map_err(Into::into) } /// 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) } /// 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) } /// Test ruma-common with the compat feature with the stable version. fn test_common(&self) -> Result<()> { cmd!("rustup run stable cargo test -p ruma-common --features events --features compat compat").run().map_err(Into::into) } /// Run all the tasks that use the nightly version. fn nightly(&self) -> Result<()> { self.fmt()?; self.nightly_full()?; self.clippy_default()?; self.clippy_wasm()?; self.clippy_all() } /// Check the formatting with the nightly version. fn fmt(&self) -> Result<()> { cmd!("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) } /// Check all crates with all features with the nightly version. /// /// Also checks that all features that are used in the code exist. fn nightly_all(&self) -> Result<()> { cmd!("rustup run {NIGHTLY} cargo check --workspace --all-features -Z unstable-options -Z check-cfg=features") .env("RUSTFLAGS", "-D warnings") .run() .map_err(Into::into) } /// Check ruma-common with `ruma_identifiers_storage="Box"` fn msrv_owned_id_box(&self) -> Result<()> { cmd!("rustup run {MSRV} cargo check -p ruma-common") .env("RUSTFLAGS", "--cfg=ruma_identifiers_storage=\"Box\"") .run() .map_err(Into::into) } /// Check ruma-common with `ruma_identifiers_storage="Arc"` fn msrv_owned_id_arc(&self) -> Result<()> { cmd!("rustup run {MSRV} cargo check -p ruma-common") .env("RUSTFLAGS", "--cfg=ruma_identifiers_storage=\"Arc\"") .run() .map_err(Into::into) } /// Lint default features with clippy with the nightly version. fn clippy_default(&self) -> Result<()> { cmd!( " rustup run {NIGHTLY} cargo clippy --workspace --all-targets --features=full -- -D warnings " ) .run() .map_err(Into::into) } /// Lint ruma-common with clippy with the nightly version and wasm target. /// /// ruma-common is currently the only crate with wasm-specific code. If that changes, this /// method should be updated. fn clippy_wasm(&self) -> Result<()> { cmd!( " rustup run {NIGHTLY} cargo clippy --target wasm32-unknown-unknown -p ruma-common --features api,events,js,markdown,rand " ) .run() .map_err(Into::into) } /// Lint almost all features with clippy with the nightly version. fn clippy_all(&self) -> Result<()> { cmd!( " rustup run {NIGHTLY} cargo clippy --workspace --all-targets --features=__ci,compat -- -D warnings " ) .run() .map_err(Into::into) } /// Run all lints that don't need compilation. fn lint(&self) -> Result<()> { // Check dependencies being sorted let dependencies_res = self.dependencies(); // Check that all links point to the same version of the spec let spec_links_res = check_spec_links(&self.project_root.join("crates")); dependencies_res.and(spec_links_res) } /// Check the sorting of dependencies with the nightly version. fn dependencies(&self) -> Result<()> { if cmd!("cargo sort --version").run().is_err() { return Err( "Could not find cargo-sort. Install it by running `cargo install cargo-sort`" .into(), ); } cmd!( " rustup run {NIGHTLY} cargo sort --workspace --grouped --check --order package,lib,features,dependencies,target,dev-dependencies,build-dependencies " ) .run() .map_err(Into::into) } /// Check the typos. fn typos(&self) -> Result<()> { if cmd!("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) } }