Skip to content

Automatically install prerequisites for xtasks #7642

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

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -512,10 +512,10 @@ jobs:
rustup toolchain install ${{ env.REPO_MSRV }} --no-self-update --profile=minimal -c llvm-tools
cargo -V

- name: Install `cargo-nextest` and `cargo-llvm-cov`
- name: Install `cargo-binstall`
uses: taiki-e/install-action@v2
with:
tool: cargo-nextest,cargo-llvm-cov
tool: cargo-binstall,cargo-llvm-cov

- name: Debug symbols to line-tables-only
shell: bash
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ cts/

# Temporary clone location for wasm-bindgen mirroring
wgpu/src/backend/webgpu/webgpu_sys/wasm_bindgen_clone_tmp

# Location of installed helper utilities
.binaries

199 changes: 199 additions & 0 deletions xtask/src/dependencies.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
use std::process::Command;

use anyhow::Context;

pub struct Prerequisites {
pub cargo_nextest: bool,
pub taplo: bool,
pub wasm_bindgen: bool,
pub simple_http_server: bool,
pub cargo_llvm_cov: bool,
pub spirv_assembler: bool,
pub vulkan_sdk: bool,
}

impl Prerequisites {
pub const ALL: Self = Self {
cargo_nextest: true,
taplo: true,
wasm_bindgen: true,
simple_http_server: true,
cargo_llvm_cov: true,
spirv_assembler: true,
vulkan_sdk: true,
};

pub const NONE: Self = Self {
cargo_nextest: false,
taplo: false,
wasm_bindgen: false,
simple_http_server: false,
cargo_llvm_cov: false,
spirv_assembler: false,
vulkan_sdk: false,
};
}

/// Sets the PATH environment variable to include the .binaries/bin directory
/// so that our installed pre-requisites are available in the PATH.
pub fn set_path() -> Result<(), anyhow::Error> {
let path_separator = if cfg!(windows) { ";" } else { ":" };
let old_path = std::env::var("PATH").context("Couldn't get PATH")?;
unsafe {
std::env::set_var(
"PATH",
format!(
"{}/../.binaries/bin{path_separator}{}",
env!("CARGO_MANIFEST_DIR"),
old_path
),
)
};
Ok(())
}

fn indented_utf8(text: &[u8]) -> anyhow::Result<String> {
let text = std::str::from_utf8(text).context("Failed to convert to utf8")?;
Ok(indent_string(text.trim()))
}

fn indent_string(text: &str) -> String {
let mut indented = String::new();
for line in text.lines() {
indented.push_str(&format!(" {line}\n"));
}
indented
}

/// Runs a command silently, printing information about it if it fails.
fn print_on_failure(mut command: Command) -> anyhow::Result<()> {
let command_string = format!("{command:?}");
let output = command
.output()
.with_context(|| format!("Failed to run {command_string}"))?;

if !output.status.success() {
let stdout = indented_utf8(&output.stdout).context("stdout")?;
let stderr = indented_utf8(&output.stderr).context("stderr")?;

println!("\nCommand failed: {command_string}");
println!("stdout:\n{stdout}");
println!("stderr:\n{stderr}");

return Err(anyhow::anyhow!("Command failed"));
}

Ok(())
}

fn run_version_check(
shell: &xshell::Shell,
command: &[&str],
version_regex: Option<&str>,
) -> anyhow::Result<String> {
let output = shell.cmd(command[0]).args(&command[1..]).quiet().output()?;
let version = std::str::from_utf8(&output.stdout)
.context("Failed to convert output to utf8")?
.trim()
.to_string();

if let Some(regex) = version_regex {
let regex = regex_lite::Regex::new(regex).unwrap();

let Some(refined_version) = regex.find(&version) else {
return Err(anyhow::anyhow!(
"Failed to parse version from output: {version}"
));
};

Ok(refined_version.as_str().to_string())
} else {
Ok(version)
}
}

pub fn setup_prerequisites(shell: &xshell::Shell, prereq: Prerequisites) -> anyhow::Result<()> {
let binstall_output = run_version_check(shell, &["cargo", "binstall", "-V"], None);

println!("Checking prerequisites...");

let mut fail = false;

if prereq.vulkan_sdk {
let vulkan_sdk_path = shell.var("VULKAN_SDK");
match vulkan_sdk_path {
Ok(path) if !path.trim().is_empty() => {
println!(" ✅ Found VULKAN_SDK set to {}", path)
}
_ => {
println!(
" ❌ VULKAN_SDK not set, please install the Vulkan SDK from https://vulkan.lunarg.com/"
);
fail = true;
}
};
}

if prereq.spirv_assembler {
let spirv_as_version = run_version_check(shell, &["spirv-as", "--version"], Some("v[^ ]*"));

match spirv_as_version {
Ok(version) => println!(" ✅ Found spirv-as {version}"),
Err(_) => {
println!(" ❌ spirv-as not found, please install the Vulkan SDK from https://vulkan.lunarg.com/");
fail = true;
}
}
}

let command = &if let Ok(output) = binstall_output {
println!(" ✅ Found cargo binstall {output}",);
vec!["binstall", "--no-confirm"]
} else {
println!(
" ⚠️ cargo binstall not found, using cargo install instead, this may take a while"
);
println!(
" Please install cargo binstall for faster installs: cargo install cargo-binstall"
);
vec!["install"]
};

if prereq.cargo_nextest {
install_dep(command, "cargo-nextest@0.9.93")?;
}

if prereq.taplo {
install_dep(command, "taplo-cli@0.9.3")?;
}

if prereq.cargo_llvm_cov {
install_dep(command, "cargo-llvm-cov@0.6.16")?;
}

if prereq.wasm_bindgen {
install_dep(command, "wasm-bindgen-cli@0.2.100")?;
}

if prereq.simple_http_server {
install_dep(command, "simple-http-server@0.6.11")?;
}

if fail {
log::error!("Some prerequisites are missing, please install them and try again.");
return Err(anyhow::anyhow!("Missing prerequisites"));
}

Ok(())
}

fn install_dep(raw_command: &[&str], dep: &str) -> Result<(), anyhow::Error> {
println!(" ⚒️ Installing {dep}...");
let mut command = Command::new("cargo");
command.args(raw_command);
command.arg(dep);

print_on_failure(command)?;
println!(" ✅ Installed {dep}");
Ok(())
}
15 changes: 12 additions & 3 deletions xtask/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,19 @@ use std::process::ExitCode;
use anyhow::Context;
use pico_args::Arguments;

mod dependencies;
mod run_wasm;
mod test;
mod util;
mod vendor_web_sys;

const HELP: &str = "\
Usage: xtask <COMMAND>

Commands:
setup
Check or install all dependencies needed for development. Installable
dependencies are installed to .binaries in the repo root.

run-wasm
Build and run web examples

Expand Down Expand Up @@ -56,7 +60,8 @@ fn main() -> anyhow::Result<ExitCode> {
env_logger::builder()
.filter_level(log::LevelFilter::Info)
.parse_default_env()
.format_indent(Some(0))
.format_target(false)
.format_timestamp(None)
.init();

let mut args = Arguments::from_env();
Expand All @@ -73,9 +78,13 @@ fn main() -> anyhow::Result<ExitCode> {
// -- Shell Creation --

let shell = xshell::Shell::new().context("Couldn't create xshell shell")?;
shell.change_dir(String::from(env!("CARGO_MANIFEST_DIR")) + "/..");

dependencies::set_path()?;

match subcommand.as_deref() {
Some("setup") => {
dependencies::setup_prerequisites(&shell, dependencies::Prerequisites::ALL)?
}
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)?,
Expand Down
23 changes: 9 additions & 14 deletions xtask/src/run_wasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,20 @@ use anyhow::Context;
use pico_args::Arguments;
use xshell::Shell;

use crate::util::{check_all_programs, Program};
use crate::dependencies;

pub(crate) fn run_wasm(shell: Shell, mut args: Arguments) -> anyhow::Result<()> {
let should_serve = !args.contains("--no-serve");
let release = args.contains("--release");

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)?;
dependencies::setup_prerequisites(
&shell,
dependencies::Prerequisites {
wasm_bindgen: true,
simple_http_server: should_serve,
..dependencies::Prerequisites::NONE
},
)?;

let release_flag: &[_] = if release { &["--release"] } else { &[] };
let output_dir = if release { "release" } else { "debug" };
Expand Down
13 changes: 13 additions & 0 deletions xtask/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ use anyhow::Context;
use pico_args::Arguments;
use xshell::Shell;

use crate::dependencies;

pub fn run_tests(shell: Shell, mut args: Arguments) -> anyhow::Result<()> {
let llvm_cov = args.contains("--llvm-cov");
let list = args.contains("--list");
// Retries handled by cargo nextest natively

dependencies::setup_prerequisites(
&shell,
dependencies::Prerequisites {
cargo_nextest: true,
cargo_llvm_cov: llvm_cov,
vulkan_sdk: true,
spirv_assembler: true,
..dependencies::Prerequisites::NONE
},
)?;

// These needs to match the command in "run wgpu-info" in `.github/workflows/ci.yml`
let llvm_cov_flags: &[_] = if llvm_cov {
&["llvm-cov", "--no-cfg-coverage", "--no-report"]
Expand Down
43 changes: 0 additions & 43 deletions xtask/src/util.rs

This file was deleted.

Loading