diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2ed27c9c95..cd3ef54a3d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -34,7 +34,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest, ubuntu-latest] + os: [ubuntu-latest] rust: [1.64.0, nightly] runs-on: ${{ matrix.os }} steps: @@ -47,11 +47,9 @@ jobs: toolchain: ${{ matrix.rust }} override: true - uses: Swatinem/rust-cache@v2 + - uses: taiki-e/install-action@nextest - name: Test twitch_api - uses: actions-rs/cargo@v1 - with: - command: test - args: --locked --all-targets --features "${{ env.CI_TWITCH_API_FEATURES }}" --workspace + run: cargo xtask test fmt: name: Rustfmt runs-on: ubuntu-latest @@ -84,46 +82,8 @@ jobs: override: true components: clippy - uses: Swatinem/rust-cache@v2 - - name: Run clippy --all-targets --no-default-features - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --no-default-features -p twitch_api - - name: Run clippy --all-targets --no-default-features --features "helix" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --no-default-features --features "helix" -p twitch_api - - name: Run clippy --all-targets --no-default-features --features "helix client" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --no-default-features --features "helix client" -p twitch_api - - name: Run clippy --all-targets --no-default-features --features "tmi" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --no-default-features --features "tmi" -p twitch_api - - name: Run clippy --all-targets --no-default-features --features "pubsub" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --no-default-features --features "pubsub" -p twitch_api - - name: Run clippy --all-targets --features "${{ env.CI_TWITCH_API_FEATURES }} trace_unknown_fields" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --features "${{ env.CI_TWITCH_API_FEATURES }} trace_unknown_fields" -p twitch_api - - name: Run clippy --all-targets --features "${{ env.CI_TWITCH_API_FEATURES }}" - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --features "${{ env.CI_TWITCH_API_FEATURES }} _all" -p twitch_api - - name: Run clippy --all-targets --all-features --workspace - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --locked --all-targets --all-features --workspace + - name: Clippy + run: cargo xtask check --locked -- --deny warnings docs: name: Docs runs-on: ubuntu-latest @@ -136,11 +96,5 @@ jobs: toolchain: nightly override: true - uses: Swatinem/rust-cache@v2 - # We do the following to make sure docs.rs can document properly without anything broken, and that docs are working. - - name: Run doc tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --doc --features "${{ env.CI_TWITCH_API_FEATURES }} _all" - name: Check twitch_api docs run: cargo xtask doc diff --git a/Cargo.lock b/Cargo.lock index 95c384cb96..524ecb0355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -727,6 +727,12 @@ version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + [[package]] name = "encoding_rs" version = "0.8.31" @@ -1261,6 +1267,15 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.1" @@ -3049,6 +3064,7 @@ version = "0.1.0" dependencies = [ "clap", "color-eyre", + "itertools", "once_cell", "serde", "serde_json", diff --git a/src/helix/mod.rs b/src/helix/mod.rs index a499463a83..47740714a7 100644 --- a/src/helix/mod.rs +++ b/src/helix/mod.rs @@ -32,7 +32,7 @@ use serde::Deserialize; #[doc(no_inline)] #[cfg(feature = "twitch_oauth2")] pub use twitch_oauth2::Scope; -#[cfg(feature = "twitch_oauth2")] +#[cfg(feature = "client")] use twitch_oauth2::TwitchToken; #[cfg(feature = "client")] @@ -75,7 +75,7 @@ struct InnerResponse { } #[derive(Deserialize, Debug)] -#[cfg(feature = "unsupported")] +#[cfg(all(feature = "unsupported", feature = "client"))] #[cfg_attr(nightly, doc(cfg(feature = "unsupported")))] struct CustomInnerResponse<'a> { #[serde(borrow)] diff --git a/src/lib.rs b/src/lib.rs index 9fdbe292db..ddeca8a891 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,9 +217,10 @@ impl<'a, C: HttpClient<'a>> TwitchClient<'a, C> { #[cfg(any(feature = "helix", feature = "tmi"))] pub fn with_client(client: C) -> TwitchClient<'a, C> where C: Clone { - // FIXME: This Clone is not used when only using one of the endpoints TwitchClient { - #[cfg(feature = "tmi")] + #[cfg(all(feature = "tmi", not(feature = "helix")))] + tmi: TmiClient::with_client(client), + #[cfg(all(feature = "tmi", feature = "helix"))] tmi: TmiClient::with_client(client.clone()), #[cfg(feature = "helix")] helix: HelixClient::with_client(client), diff --git a/src/pubsub/channel_subscriptions.rs b/src/pubsub/channel_subscriptions.rs index aaec9da79d..a4b0d65091 100644 --- a/src/pubsub/channel_subscriptions.rs +++ b/src/pubsub/channel_subscriptions.rs @@ -285,6 +285,7 @@ mod tests { use super::*; #[test] + #[cfg(feature = "time")] fn subscription_doc_example_resub() { // twitch docs broken as usual. /emotes/id is a string and /months is missing let message = r##" @@ -329,6 +330,7 @@ mod tests { } #[test] + #[cfg(feature = "time")] fn subscription_doc_example_subgift() { let message = r##" { @@ -367,6 +369,7 @@ mod tests { } #[test] + #[cfg(feature = "time")] fn new_sub() { let message = r##" { diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 1b744fa939..0c0beedfd9 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -13,3 +13,4 @@ xshell = "0.2.2" once_cell = "1.15" serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } +itertools = "0.10.5" diff --git a/xtask/src/check.rs b/xtask/src/check.rs new file mode 100644 index 0000000000..abb3ed2dee --- /dev/null +++ b/xtask/src/check.rs @@ -0,0 +1,42 @@ +use itertools::Itertools; +use xshell::{cmd, Shell}; + +#[derive(Debug, clap::Args)] +pub struct Check { + #[clap(long)] + list: bool, + #[clap(long)] + locked: bool, + #[clap(last = true)] + rest: Vec, +} + +impl Check { + pub fn run(&self, sh: &Shell) -> Result<(), color_eyre::Report> { + let Check { rest, locked, list } = self; + let set = crate::features(); + if *list { + println!("{}", set.iter().map(|v| v.join(", ")).join("\n")); + return Ok(()); + } + println!("Doing {} checks", set.len()); + let locked = if *locked { Some("--locked") } else { None }; + for features in set { + let features = if !features.is_empty() { + Some(format!("--no-default-features --features={}", features.iter().join(","))) + } else { + None + }; + let _section = crate::section(format!( + "Features: {}", + features.as_deref().unwrap_or_default() + )); + cmd!( + sh, + "cargo clippy {locked...} --all-targets {features...} -- {rest...}" + ) + .run()?; + } + Ok(()) + } +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index fc6543e6e2..9f4d90ce7e 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,15 +1,36 @@ +pub mod check; +pub mod test; + use std::path::{Path, PathBuf}; use clap::Parser; use color_eyre::Help; +use itertools::Itertools; use once_cell::sync::OnceCell; use serde::Deserialize; use xshell::{cmd, Shell}; static RUSTDOCFLAGS: &[&str] = &["--cfg", "nightly"]; static RUSTFLAGS: &[&str] = &["--cfg", "nightly"]; -static TWITCH_API_FEATURES: &str = +static TWITCH_API_DOC_FEATURES: &str = "twitch_oauth2/all twitch_oauth2/mock_api all unsupported deny_unknown_fields _all"; +static FEATURE_SETS: &[&[&str]] = &[ + &["helix"], + &["tmi"], + &["helix", "pubsub", "eventsub", "hmac"], +]; +static OPT_FEATURES: &[&[&str]] = &[ + &["unsupported"], + &["twitch_oauth2"], + &["client", "deny_unknown_fields"], + &["client", "deny_unknown_fields", "unsupported"], +]; +static EXTRA_FEATURES: &[&[&str]] = &[ + &["pubsub", "twitch_types/time", "deny_unknown_fields"], + &["trace_unknown_fields", "deny_unknown_fields", "helix"], + &["helix", "tmi"], + &["helix", "eventsub"], +]; #[derive(Debug, Parser)] pub enum Args { @@ -22,6 +43,8 @@ pub enum Args { #[clap(last = true)] last: Option, }, + Check(check::Check), + Test(test::Test), } fn main() -> color_eyre::Result<()> { @@ -125,6 +148,8 @@ fn main() -> color_eyre::Result<()> { .run()?; } } + Args::Check(check) => check.run(&sh)?, + Args::Test(test) => test.run(&sh)?, } Ok(()) } @@ -197,6 +222,70 @@ pub fn get_cargo_workspace() -> &'static Path { }) } +fn features() -> Vec> { + let mut set: Vec> = vec![all_features()]; + for feats in FEATURE_SETS.iter() { + set.push(feats.iter().copied().sorted().collect()); + for opt in OPT_FEATURES.iter() { + set.push( + feats + .iter() + .copied() + .chain(opt.iter().copied()) + .sorted() + .collect(), + ); + } + } + // extras + for extra in EXTRA_FEATURES { + set.push(extra.to_vec()) + } + set.sort_by_key(|f| -(f.len() as i32)); + set.insert(0, vec![]); + set.into_iter().unique().collect() +} + +fn all_features() -> Vec<&'static str> { + crate::FEATURE_SETS + .iter() + .copied() + .flatten() + .chain(crate::OPT_FEATURES.iter().copied().flatten()) + .unique() + .copied() + .collect() +} + +fn section(name: impl Into) -> impl Drop { + use std::time::Instant; + let ci = std::env::var("CI").is_ok(); + let name = name.into(); + if ci { + println!("::group::{name}"); + } + let start = Instant::now(); + defer(move || { + let elapsed = start.elapsed(); + eprintln!("{name}: {elapsed:.2?}"); + if ci { + println!("::endgroup::"); + } + }) +} + +fn defer(f: F) -> impl Drop { + struct D(Option); + impl Drop for D { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f() + } + } + } + D(Some(f)) +} + #[cfg(test)] mod tests { use super::*; diff --git a/xtask/src/test.rs b/xtask/src/test.rs new file mode 100644 index 0000000000..c19963d8cf --- /dev/null +++ b/xtask/src/test.rs @@ -0,0 +1,31 @@ +use itertools::Itertools; +use xshell::{cmd, Shell}; + +#[derive(Debug, clap::Args)] +pub struct Test { + #[clap(long)] + no_nextest: bool, + #[clap(last = true)] + rest: Vec, +} + +impl Test { + pub fn run(&self, sh: &Shell) -> Result<(), color_eyre::Report> { + let Test { rest, no_nextest } = self; + let test: &[_] = if *no_nextest { + &["test"] + } else { + &["nextest", "run"] + }; + + let set = crate::features(); + println!("Doing {} test runs", set.len()); + for features in set { + let features = features.join(","); + let _section = crate::section(format!("Features: {features}")); + cmd!(sh, "cargo {test...} --features {features} -- {rest...}").run()?; + } + + Ok(()) + } +}