From 0d49b57b045af9c14f27f9f408c52a625577c17b Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Fri, 23 Feb 2024 16:52:27 +1000 Subject: [PATCH] Add xtask crate and use in CI (#635) --- .cargo/config | 2 + .github/workflows/rust.yml | 107 +++++---------- xtask/Cargo.toml | 8 ++ xtask/src/main.rs | 259 +++++++++++++++++++++++++++++++++++++ 4 files changed, 299 insertions(+), 77 deletions(-) create mode 100644 .cargo/config create mode 100644 xtask/Cargo.toml create mode 100644 xtask/src/main.rs diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 00000000..d8c2032b --- /dev/null +++ b/.cargo/config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --manifest-path ./xtask/Cargo.toml --" diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 42011b13..5220f261 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -17,19 +17,14 @@ jobs: os: "macOS-latest" runs-on: ${{matrix.os}} steps: - - uses: actions/checkout@v4 - with: - submodules: true - - name: Install rust channel - run: | - rustup install ${{matrix.rust_channel}} - rustup default ${{matrix.rust_channel}} - - name: Test debug - run: | - cargo test --verbose --features all - cargo test --verbose --features unstable-all - - name: Test release - run: cargo test --verbose --features all --release + - uses: actions/checkout@v4 + with: + submodules: true + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{matrix.rust_channel}} + - run: cargo xtask check + - run: cargo xtask test features: runs-on: ubuntu-latest @@ -37,78 +32,48 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - run: cargo test --no-default-features --features read - - run: cargo test --no-default-features --features write - - run: cargo test --no-default-features --features read_core,write_core,coff - - run: cargo test --no-default-features --features read_core,write_core,elf - - run: cargo test --no-default-features --features read_core,write_core,macho - - run: cargo test --no-default-features --features read_core,write_core,pe - - run: cargo test --no-default-features --features read_core,wasm - - run: cargo test --no-default-features --features read_core,xcoff - - run: cargo test --no-default-features --features std - - run: cargo test --no-default-features --features compression - - run: cargo test --no-default-features --features unaligned - - run: cargo test --no-default-features --features doc + - uses: dtolnay/rust-toolchain@stable + - run: cargo xtask features cross: - strategy: - matrix: - target: - # A 32-bit target. - - "i686-unknown-linux-gnu" - # A big-endian target - - "powerpc64-unknown-linux-gnu" runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - - name: Install rust - run: | - rustup install stable - rustup default stable - - run: cargo install cross - - run: rustup target add ${{matrix.target}} - - run: cross test --target ${{matrix.target}} --features all + - uses: dtolnay/rust-toolchain@stable + with: + targets: i686-unknown-linux-gnu, powerpc64-unknown-linux-gnu + - uses: taiki-e/install-action@v2 + with: + tool: cross + - run: cargo xtask cross - msrv-read: + msrv: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: submodules: true - - name: Install rust - run: rustup update 1.60.0 && rustup default 1.60.0 - - name: MSRV dependencies - run: | - cargo update -p memchr --precise 2.6.2 - - name: Test - run: cargo test -p object --verbose --no-default-features --features read,std + - uses: dtolnay/rust-toolchain@1.60.0 + - uses: dtolnay/rust-toolchain@1.65.0 + - run: cargo xtask msrv - msrv-all: + rustfmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable with: - submodules: true - - name: Install rust - run: rustup update 1.65.0 && rustup default 1.65.0 - - name: MSRV dependencies - run: | - cargo update -p ahash --precise 0.8.6 - - name: Test - run: cargo test -p object --verbose --features all + components: rustfmt + - run: cargo xtask fmt - rustfmt: + doc: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Install rust - run: | - rustup install stable - rustup default stable - rustup component add rustfmt - - run: cargo fmt --all -- --check + - uses: dtolnay/rust-toolchain@stable + - run: cargo xtask doc coverage: runs-on: ubuntu-latest @@ -119,22 +84,10 @@ jobs: - uses: actions/checkout@v4 with: submodules: true - - name: Install rust - run: | - rustup install stable - rustup default stable - - name: Run cargo-tarpaulin - run: cargo tarpaulin --features all --ignore-tests --out Lcov + - uses: dtolnay/rust-toolchain@stable + - run: cargo xtask coverage_lcov - name: Upload to Coveralls uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} path-to-lcov: './lcov.info' - - doc: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - run: cargo rustdoc --no-default-features --features doc - env: - RUSTDOCFLAGS: "-D warnings" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..28234af1 --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "xtask" +version = "0.0.0" +edition = "2021" + +[dependencies] + +[workspace] diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..e664693f --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,259 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +type DynError = Box; + +fn main() { + if let Err(e) = try_main() { + eprintln!("{}", e); + std::process::exit(-1); + } +} + +fn try_main() -> Result<(), DynError> { + let task = env::args().nth(1); + match task.as_deref() { + Some("ci") => cmd_ci()?, + Some("check") => cmd_check()?, + Some("build") => cmd_build()?, + Some("test") => cmd_test()?, + Some("features") => cmd_features()?, + Some("cross") => cmd_cross()?, + Some("msrv") => cmd_msrv()?, + Some("fmt") => cmd_fmt()?, + Some("doc") => cmd_doc()?, + Some("coverage") => cmd_coverage()?, + Some("coverage_lcov") => cmd_coverage_lcov()?, + Some("clippy") => cmd_clippy()?, + _ => print_help(), + } + Ok(()) +} + +fn print_help() { + eprintln!( + "Tasks: +ci runs everything in CI +check checks everything +build builds everything +test tests everything +features tests with various feature combinations +cross tests for other platforms +msrv tests minimum supported Rust version +fmt checks formatting +doc generates documentation for everything +coverage generates HTML test coverage with tarpaulin and pycobertura, and opens it +coverage_lcov generates Lcov test coverage with tarpaulin +lint lints everything +" + ) +} + +fn cmd_ci() -> Result<(), DynError> { + cmd_check()?; + cmd_test()?; + cmd_features()?; + cmd_cross()?; + cmd_msrv()?; + cmd_fmt()?; + cmd_doc()?; + Ok(()) +} + +fn cmd_check() -> Result<(), DynError> { + cargo(&["check", "--workspace", "--features", "all"]) +} + +fn cmd_build() -> Result<(), DynError> { + cargo(&["build", "--workspace", "--features", "all"]) +} + +fn cmd_test() -> Result<(), DynError> { + cargo(&["test", "--workspace", "--features", "all"]) +} + +fn cmd_features() -> Result<(), DynError> { + // Test the default features for everything. + cargo(&["test", "--workspace"])?; + + // Feature combinations for the `object` and `object-examples` packages. + for features in [ + // Test the main submodules. + "read", + "write", + // Test each file format individually. + "read_core,write_core,coff", + "read_core,write_core,elf", + "read_core,write_core,macho", + "read_core,write_core,pe", + "read_core,write_core,xcoff", + "read_core,wasm", + // Test miscellaneous features individually. + "std", + "compression", + "unaligned", + ] { + cargo(&[ + "test", + "-p", + "object", + "-p", + "object-examples", + "--no-default-features", + "--features", + features, + ])?; + } + Ok(()) +} + +fn cmd_cross() -> Result<(), DynError> { + for target in [ + // 32-bit target + "i686-unknown-linux-gnu", + // big-endian target + "powerpc64-unknown-linux-gnu", + ] { + cmd( + "cross", + &[ + "test", + "--workspace", + "--features", + "all", + "--target", + target, + ], + )?; + } + Ok(()) +} + +fn cmd_msrv() -> Result<(), DynError> { + // Test MSRV for object read feature. + cargo(&["update", "-p", "memchr", "--precise", "2.6.2"])?; + cmd_with( + "cargo", + &[ + "+1.60.0", + "test", + "-p", + "object", + "--no-default-features", + "--features", + "read,std", + ], + |cmd| { + cmd.env("CARGO_NET_GIT_FETCH_WITH_CLI", "true"); + }, + )?; + cargo(&["update", "-p", "memchr"])?; + // Test MSRV for object all features. + cargo(&["update", "-p", "ahash", "--precise", "0.8.6"])?; + cmd_with( + "cargo", + &["+1.65.0", "test", "-p", "object", "--features", "all"], + |cmd| { + cmd.env("CARGO_NET_GIT_FETCH_WITH_CLI", "true"); + }, + )?; + + cargo(&["update", "-p", "ahash"])?; + Ok(()) +} + +fn cmd_fmt() -> Result<(), DynError> { + cargo(&["fmt", "--", "--check"]) +} + +fn cmd_doc() -> Result<(), DynError> { + cargo_with( + &[ + "doc", + "--workspace", + "--lib", + "--no-default-features", + "--features", + "doc", + ], + |cmd| { + cmd.env("RUSTDOCFLAGS", "-D warnings"); + }, + ) +} + +fn cmd_coverage() -> Result<(), DynError> { + cargo(&[ + "tarpaulin", + "--features", + "all", + "--ignore-tests", + "--out", + "xml", + ])?; + cmd( + "pycobertura", + &[ + "show", + "--format", + "html", + "cobertura.xml", + "--output", + "cobertura.html", + ], + )?; + cmd("open", &["cobertura.html"])?; + Ok(()) +} + +fn cmd_coverage_lcov() -> Result<(), DynError> { + cargo(&[ + "tarpaulin", + "--features", + "all", + "--ignore-tests", + "--out", + "Lcov", + ]) +} + +fn cmd_clippy() -> Result<(), DynError> { + cargo(&["clippy", "--workspace", "--features", "all", "--all-targets"]) +} + +fn cargo(args: &[&str]) -> Result<(), DynError> { + cargo_with(args, |_| ()) +} + +fn cargo_with(args: &[&str], f: impl FnOnce(&mut Command)) -> Result<(), DynError> { + let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); + cmd_with(&cargo, args, f) +} + +fn cmd(cmd: &str, args: &[&str]) -> Result<(), DynError> { + cmd_with(cmd, args, |_| ()) +} + +fn cmd_with(program: &str, args: &[&str], f: F) -> Result<(), DynError> +where + F: FnOnce(&mut Command), +{ + println!("Running '{} {}'", program, args.join(" ")); + let mut command = Command::new(program); + command.current_dir(project_root()).args(args); + f(&mut command); + let status = command.status()?; + if !status.success() { + Err(format!("'{} {}' failed", program, args.join(" ")))?; + } + Ok(()) +} + +fn project_root() -> PathBuf { + Path::new(&env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(1) + .unwrap() + .to_path_buf() +}