Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CI tool and drop xshell dependency. #13279

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
9 changes: 0 additions & 9 deletions crates/bevy_utils/README.md

This file was deleted.

4 changes: 2 additions & 2 deletions tools/ci/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ license = "MIT OR Apache-2.0"

[dependencies]
argh = "0.1"
xshell = "0.2"
bitflags = "2.3"
serde_json = "1.0.116"
serde = { version = "1.0.200", features = ["derive"] }

[lints]
workspace = true
108 changes: 18 additions & 90 deletions tools/ci/src/ci.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
use crate::{
commands,
prepare::{Flag, Prepare, PreparedCommand},
};
use crate::commands;
use argh::FromArgs;

/// The CI command line tool for Bevy.
#[derive(FromArgs)]
pub struct CI {
#[argh(subcommand)]
command: Option<Commands>,
command: Commands,

/// continue running commands even if one fails
#[argh(switch)]
Expand All @@ -20,69 +17,22 @@ impl CI {
///
/// When run locally, results may differ from actual CI runs triggered by `.github/workflows/ci.yml`.
/// This is usually related to differing toolchains and configuration.
pub fn run(self) {
let sh = xshell::Shell::new().unwrap();

let prepared_commands = self.prepare(&sh);

let mut failures = vec![];

for command in prepared_commands {
// If the CI test is to be executed in a subdirectory, we move there before running the command.
// This will automatically move back to the original directory once dropped.
let _subdir_hook = command.subdir.map(|path| sh.push_dir(path));

// Execute each command, checking if it returned an error.
if command.command.envs(command.env_vars).run().is_err() {
let name = command.name;
let message = command.failure_message;

if self.keep_going {
// We use bullet points here because there can be more than one error.
failures.push(format!("- {name}: {message}"));
} else {
failures.push(format!("{name}: {message}"));
break;
}
}
}

// Log errors at the very end.
if !failures.is_empty() {
let failures = failures.join("\n");

panic!(
"One or more CI commands failed:\n\
{failures}"
);
}
}

fn prepare<'a>(&self, sh: &'a xshell::Shell) -> Vec<PreparedCommand<'a>> {
let mut flags = Flag::empty();

if self.keep_going {
flags |= Flag::KEEP_GOING;
}

match &self.command {
Some(command) => command.prepare(sh, flags),
None => {
// Note that we are running the subcommands directly rather than using any aliases
let mut cmds = vec![];
cmds.append(&mut commands::FormatCommand::default().prepare(sh, flags));
cmds.append(&mut commands::ClippyCommand::default().prepare(sh, flags));
cmds.append(&mut commands::TestCommand::default().prepare(sh, flags));
cmds.append(&mut commands::TestCheckCommand::default().prepare(sh, flags));
cmds.append(&mut commands::DocCheckCommand::default().prepare(sh, flags));
cmds.append(&mut commands::DocTestCommand::default().prepare(sh, flags));
cmds.append(&mut commands::CompileCheckCommand::default().prepare(sh, flags));
cmds.append(&mut commands::CompileCheckNoStdCommand::default().prepare(sh, flags));
cmds.append(&mut commands::CompileFailCommand::default().prepare(sh, flags));
cmds.append(&mut commands::BenchCheckCommand::default().prepare(sh, flags));
cmds.append(&mut commands::ExampleCheckCommand::default().prepare(sh, flags));
cmds
}
pub fn run(self) -> Result<(), ()> {
match self.command {
Commands::Lints(lints) => lints.run(self.keep_going),
Commands::Doc(doc) => doc.run(self.keep_going),
Commands::Compile(compile) => compile.run(self.keep_going),
Commands::Format(format) => format.run(),
Commands::Clippy(clippy) => clippy.run(),
Commands::Test(test) => test.run(self.keep_going),
Commands::TestCheck(test_check) => test_check.run(),
Commands::DocCheck(doc_check) => doc_check.run(),
Commands::DocTest(doc_test) => doc_test.run(self.keep_going),
Commands::CompileCheck(compile_check) => compile_check.run(),
Commands::CompileFail(compile_fail) => compile_fail.run(self.keep_going),
Commands::BenchCheck(bench_check) => bench_check.run(),
Commands::ExampleCheck(example_check) => example_check.run(),
Commands::CompileCheckNoStd(compile_check_no_std) => compile_check_no_std.run(),
}
}
}
Expand All @@ -108,25 +58,3 @@ enum Commands {
BenchCheck(commands::BenchCheckCommand),
ExampleCheck(commands::ExampleCheckCommand),
}

impl Prepare for Commands {
fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec<PreparedCommand<'a>> {
match self {
Commands::Lints(subcommand) => subcommand.prepare(sh, flags),
Commands::Doc(subcommand) => subcommand.prepare(sh, flags),
Commands::Compile(subcommand) => subcommand.prepare(sh, flags),

Commands::Format(subcommand) => subcommand.prepare(sh, flags),
Commands::Clippy(subcommand) => subcommand.prepare(sh, flags),
Commands::Test(subcommand) => subcommand.prepare(sh, flags),
Commands::TestCheck(subcommand) => subcommand.prepare(sh, flags),
Commands::DocCheck(subcommand) => subcommand.prepare(sh, flags),
Commands::DocTest(subcommand) => subcommand.prepare(sh, flags),
Commands::CompileCheck(subcommand) => subcommand.prepare(sh, flags),
Commands::CompileCheckNoStd(subcommand) => subcommand.prepare(sh, flags),
Commands::CompileFail(subcommand) => subcommand.prepare(sh, flags),
Commands::BenchCheck(subcommand) => subcommand.prepare(sh, flags),
Commands::ExampleCheck(subcommand) => subcommand.prepare(sh, flags),
}
}
}
58 changes: 46 additions & 12 deletions tools/ci/src/commands/bench_check.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,54 @@
use crate::{Flag, Prepare, PreparedCommand};
use super::{run_cargo_command, run_cargo_command_with_json, RustToolchain};
use crate::json::JsonCommandOutput;
use argh::FromArgs;
use xshell::cmd;

/// Checks that the benches compile.
#[derive(FromArgs, Default)]
#[argh(subcommand, name = "bench-check")]
pub struct BenchCheckCommand {}
pub struct BenchCheckCommand {
#[argh(switch)]
/// emit errors as json
emit_json: bool,
}

impl BenchCheckCommand {
const FLAGS: &'static [&'static str] = &[
"--benches",
"--target-dir",
"../target",
"--manifest-path",
"./benches/Cargo.toml",
];
const ENV_VARS: &'static [(&'static str, &'static str)] = &[];

/// Runs this command.
///
/// For use in aliases.
pub fn run_with_intermediate() -> Result<(), ()> {
run_cargo_command("check", RustToolchain::Active, Self::FLAGS, Self::ENV_VARS)
}

/// Runs this command with json output.
///
/// For use in aliases.
pub fn run_with_intermediate_json() -> Result<JsonCommandOutput, ()> {
run_cargo_command_with_json(
"check",
"bench-check",
RustToolchain::Active,
Self::FLAGS,
Self::ENV_VARS,
)
}

impl Prepare for BenchCheckCommand {
fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec<PreparedCommand<'a>> {
vec![PreparedCommand::new::<Self>(
cmd!(
sh,
"cargo check --benches --target-dir ../target --manifest-path ./benches/Cargo.toml"
),
"Failed to check the benches.",
)]
/// Runs this command.
pub fn run(self) -> Result<(), ()> {
if self.emit_json {
Self::run_with_intermediate_json().map(|json| {
println!("[{}]", json.as_json_string());
})
} else {
Self::run_with_intermediate()
}
}
}
59 changes: 47 additions & 12 deletions tools/ci/src/commands/clippy.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,55 @@
use crate::{Flag, Prepare, PreparedCommand};
use crate::json::JsonCommandOutput;
use argh::FromArgs;
use xshell::cmd;

use super::{run_cargo_command, run_cargo_command_with_json, RustToolchain};

/// Check for clippy warnings and errors.
#[derive(FromArgs, Default)]
#[argh(subcommand, name = "clippy")]
pub struct ClippyCommand {}
pub struct ClippyCommand {
#[argh(switch)]
/// emit errors as json
emit_json: bool,
}

impl ClippyCommand {
const FLAGS: &'static [&'static str] = &[
"--workspace",
"--all-targets",
"--all-features",
"--",
"-Dwarnings",
];
const ENV_VARS: &'static [(&'static str, &'static str)] = &[];

/// Runs this command.
///
/// For use in aliases.
pub fn run_with_intermediate() -> Result<(), ()> {
run_cargo_command("clippy", RustToolchain::Active, Self::FLAGS, Self::ENV_VARS)
}

/// Runs this command with json output.
///
/// For use in aliases.
pub fn run_with_intermediate_json() -> Result<JsonCommandOutput, ()> {
run_cargo_command_with_json(
"clippy",
"clippy",
RustToolchain::Active,
Self::FLAGS,
Self::ENV_VARS,
)
}

impl Prepare for ClippyCommand {
fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec<PreparedCommand<'a>> {
vec![PreparedCommand::new::<Self>(
cmd!(
sh,
"cargo clippy --workspace --all-targets --all-features -- -Dwarnings"
),
"Please fix clippy errors in output above.",
)]
/// Runs this command.
pub fn run(self) -> Result<(), ()> {
if self.emit_json {
Self::run_with_intermediate_json().map(|json| {
println!("[{}]", json.as_json_string());
})
} else {
Self::run_with_intermediate()
}
}
}
46 changes: 31 additions & 15 deletions tools/ci/src/commands/compile.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
use crate::{
commands::{
BenchCheckCommand, CompileCheckCommand, CompileFailCommand, ExampleCheckCommand,
TestCheckCommand,
},
Flag, Prepare, PreparedCommand,
use crate::commands::{
BenchCheckCommand, CompileCheckCommand, CompileFailCommand, ExampleCheckCommand,
TestCheckCommand,
};
use argh::FromArgs;

Expand All @@ -12,14 +9,33 @@ use argh::FromArgs;
#[argh(subcommand, name = "compile")]
pub struct CompileCommand {}

impl Prepare for CompileCommand {
fn prepare<'a>(&self, sh: &'a xshell::Shell, flags: Flag) -> Vec<PreparedCommand<'a>> {
let mut commands = vec![];
commands.append(&mut CompileFailCommand::default().prepare(sh, flags));
commands.append(&mut BenchCheckCommand::default().prepare(sh, flags));
commands.append(&mut ExampleCheckCommand::default().prepare(sh, flags));
commands.append(&mut CompileCheckCommand::default().prepare(sh, flags));
commands.append(&mut TestCheckCommand::default().prepare(sh, flags));
commands
impl CompileCommand {
/// Runs this command.
pub fn run(self, no_fail_fast: bool) -> Result<(), ()> {
let compile_fail_result = CompileFailCommand::run_with_intermediate(no_fail_fast);

if !no_fail_fast && compile_fail_result.is_err() {
return compile_fail_result;
}

let bench_check_result = BenchCheckCommand::run_with_intermediate();

if !no_fail_fast && compile_fail_result.is_err() {
return bench_check_result;
}

let example_check_result = ExampleCheckCommand::run_with_intermediate();

if !no_fail_fast && compile_fail_result.is_err() {
return example_check_result;
}

let compile_check_result = CompileCheckCommand::run_with_intermediate();

if !no_fail_fast && compile_fail_result.is_err() {
return compile_check_result;
}

TestCheckCommand::run_with_intermediate()
}
}
50 changes: 41 additions & 9 deletions tools/ci/src/commands/compile_check.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,49 @@
use crate::{Flag, Prepare, PreparedCommand};
use crate::json::JsonCommandOutput;
use argh::FromArgs;
use xshell::cmd;

use super::{run_cargo_command, run_cargo_command_with_json, RustToolchain};

/// Checks that the project compiles.
#[derive(FromArgs, Default)]
#[argh(subcommand, name = "compile-check")]
pub struct CompileCheckCommand {}
pub struct CompileCheckCommand {
#[argh(switch)]
/// emit errors as json
emit_json: bool,
}

impl CompileCheckCommand {
const FLAGS: &'static [&'static str] = &["--workspace"];
const ENV_VARS: &'static [(&'static str, &'static str)] = &[];

/// Runs this command.
///
/// For use in aliases.
pub fn run_with_intermediate() -> Result<(), ()> {
run_cargo_command("check", RustToolchain::Active, Self::FLAGS, Self::ENV_VARS)
}

/// Runs this command with json output.
///
/// For use in aliases.
pub fn run_with_intermediate_json() -> Result<JsonCommandOutput, ()> {
run_cargo_command_with_json(
"check",
"compile-check",
RustToolchain::Active,
Self::FLAGS,
Self::ENV_VARS,
)
}

impl Prepare for CompileCheckCommand {
fn prepare<'a>(&self, sh: &'a xshell::Shell, _flags: Flag) -> Vec<PreparedCommand<'a>> {
vec![PreparedCommand::new::<Self>(
cmd!(sh, "cargo check --workspace"),
"Please fix compiler errors in output above.",
)]
/// Runs this command.
pub fn run(self) -> Result<(), ()> {
if self.emit_json {
Self::run_with_intermediate_json().map(|json| {
println!("[{}]", json.as_json_string());
})
} else {
Self::run_with_intermediate()
}
}
}
Loading
Loading