diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4a5102cafb..a53ad8a6bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -780,3 +780,22 @@ jobs: command: check bans licenses sources arguments: --all-features --workspace rust-version: ${{ env.REPO_MSRV }} + + check-feature-dependencies: + # runtime is normally 1 minute + timeout-minutes: 5 + + name: "Feature Dependencies" + runs-on: ubuntu-latest + steps: + - name: checkout repo + uses: actions/checkout@v4 + + - name: Install Repo MSRV toolchain + run: | + rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal + rustup override set ${{ env.REPO_MSRV }} + cargo -V + + - name: Run `cargo feature-dependencies` + run: cargo xtask check-feature-dependencies diff --git a/xtask/src/check_feature_dependencies.rs b/xtask/src/check_feature_dependencies.rs new file mode 100644 index 0000000000..6601bc63b9 --- /dev/null +++ b/xtask/src/check_feature_dependencies.rs @@ -0,0 +1,114 @@ +use pico_args::Arguments; +use xshell::Shell; + +#[derive(Debug)] +enum Search<'a> { + #[expect(dead_code)] + Positive(&'a str), + Negative(&'a str), +} + +#[derive(Debug)] +struct Requirement<'a> { + human_readable_name: &'a str, + target: &'a str, + packages: &'a [&'a str], + features: &'a [&'a str], + default_features: bool, + search_terms: &'a [Search<'a>], +} + +const ALL_WGPU_FEATURES: &[&str] = &[ + "dx12", + "metal", + "webgpu", + "angle", + "vulkan-portability", + "webgl", + "spirv", + "glsl", + "wgsl", + "naga-ir", + "serde", + "replay", + "counters", + "fragile-send-sync-non-atomic-wasm", + "static-dxc", +]; + +pub fn check_feature_dependencies(shell: Shell, arguments: Arguments) -> anyhow::Result<()> { + let mut _args = arguments.finish(); + + let features_no_webgl: Vec<&str> = ALL_WGPU_FEATURES + .iter() + .copied() + .filter(|feature| *feature != "webgl") + .collect(); + + let requirements = [ + Requirement { + human_readable_name: "wasm32 without `webgl` feature does not depend on `wgpu-core`", + target: "wasm32-unknown-unknown", + packages: &["wgpu"], + features: &features_no_webgl, + default_features: false, + search_terms: &[Search::Negative("wgpu-core")], + }, + Requirement { + human_readable_name: + "wasm32 with `webgpu` and `wgsl` feature does not depend on `naga`", + target: "wasm32-unknown-unknown", + packages: &["wgpu"], + features: &["webgpu", "wgsl"], + default_features: false, + search_terms: &[Search::Negative("naga")], + }, + ]; + + let mut any_failures = false; + for requirement in requirements { + let mut cmd = shell + .cmd("cargo") + .args(["tree", "--target", requirement.target]); + + for package in requirement.packages { + cmd = cmd.arg("--package").arg(package); + } + + if !requirement.default_features { + cmd = cmd.arg("--no-default-features"); + } + + if !requirement.features.is_empty() { + cmd = cmd.arg("--features").arg(requirement.features.join(",")); + } + + log::info!("Checking Requirement: {}", requirement.human_readable_name); + log::debug!("{:#?}", requirement); + log::debug!("$ {cmd}"); + + let output = cmd.read()?; + + log::debug!("{output}"); + + for search_term in requirement.search_terms { + let found = match search_term { + Search::Positive(search_term) => output.contains(search_term), + Search::Negative(search_term) => !output.contains(search_term), + }; + + if found { + log::info!("✅ Passed!"); + } else { + log::info!("❌ Failed"); + any_failures = true; + } + } + } + + if any_failures { + anyhow::bail!("Some feature dependencies are not met"); + } + + Ok(()) +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index f173fe9690..8cfc12f70d 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -3,6 +3,7 @@ use std::process::ExitCode; use anyhow::Context; use pico_args::Arguments; +mod check_feature_dependencies; mod run_wasm; mod test; mod util; @@ -12,6 +13,9 @@ const HELP: &str = "\ Usage: xtask Commands: + check-feature-dependencies + Check certain dependency invariants are upheld. + run-wasm Build and run web examples @@ -71,6 +75,9 @@ fn main() -> anyhow::Result { shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/.."); match subcommand.as_deref() { + Some("check-feature-dependencies") => { + check_feature_dependencies::check_feature_dependencies(shell, args)? + } Some("run-wasm") => run_wasm::run_wasm(shell, args)?, Some("test") => test::run_tests(shell, args)?, Some("vendor-web-sys") => vendor_web_sys::run_vendor_web_sys(shell, args)?, diff --git a/xtask/src/run_wasm.rs b/xtask/src/run_wasm.rs index d71d88722f..d8048b3ca8 100644 --- a/xtask/src/run_wasm.rs +++ b/xtask/src/run_wasm.rs @@ -6,28 +6,22 @@ use xshell::Shell; use crate::util::{check_all_programs, Program}; pub(crate) fn run_wasm(shell: Shell, mut args: Arguments) -> anyhow::Result<()> { - let no_serve = args.contains("--no-serve"); + let should_serve = !args.contains("--no-serve"); let release = args.contains("--release"); - let programs_needed: &[_] = if no_serve { - &[Program { - crate_name: "wasm-bindgen-cli", - binary_name: "wasm-bindgen", - }] - } else { - &[ - Program { - crate_name: "wasm-bindgen-cli", - binary_name: "wasm-bindgen", - }, - Program { - crate_name: "simple-http-server", - binary_name: "simple-http-server", - }, - ] - }; - - check_all_programs(programs_needed)?; + let mut programs_needed = vec![Program { + crate_name: "wasm-bindgen-cli", + binary_name: "wasm-bindgen", + }]; + + if should_serve { + programs_needed.push(Program { + crate_name: "simple-http-server", + binary_name: "simple-http-server", + }); + } + + check_all_programs(&programs_needed)?; let release_flag: &[_] = if release { &["--release"] } else { &[] }; let output_dir = if release { "release" } else { "debug" }; @@ -91,7 +85,7 @@ pub(crate) fn run_wasm(shell: Shell, mut args: Arguments) -> anyhow::Result<()> .with_context(|| format!("Failed to copy static file \"{}\"", file.display()))?; } - if !no_serve { + if should_serve { log::info!("serving on port 8000"); // Explicitly specify the IP address to 127.0.0.1 since otherwise simple-http-server will