Make vendor-web-sys xtask accept a SHA

Also, update wasm-bindgen URLs
This commit is contained in:
Andy Leiserson 2025-08-08 12:42:09 -07:00 committed by Connor Fitzgerald
parent 0c30efe857
commit 7816390566
3 changed files with 157 additions and 138 deletions

View File

@ -36,6 +36,8 @@ use regex_lite::{Regex, RegexBuilder};
use std::{ffi::OsString, sync::LazyLock};
use xshell::Shell;
use crate::util::git_version_at_least;
/// Path within the repository where the CTS will be checked out.
const CTS_CHECKOUT_PATH: &str = "cts";
@ -244,119 +246,3 @@ pub fn run_cts(shell: Shell, mut args: Arguments) -> anyhow::Result<()> {
Ok(())
}
fn git_version_at_least(shell: &Shell, version: GitVersion) -> anyhow::Result<bool> {
let output = shell
.cmd("git")
.args(["--version"])
.output()
.context("Failed to invoke `git --version`")?;
let Some(code) = output.status.code() else {
anyhow::bail!("`git --version` failed to return an exit code; interrupt via signal, maybe?")
};
anyhow::ensure!(code == 0, "`git --version` returned a nonzero exit code");
let fmt_err_msg = "`git --version` did not have the expected structure";
let stdout = String::from_utf8(output.stdout).expect(fmt_err_msg);
let parsed = parse_git_version_output(&stdout).expect(fmt_err_msg);
Ok(parsed >= version)
}
pub type GitVersion = [u8; 3];
fn parse_git_version_output(output: &str) -> anyhow::Result<GitVersion> {
const PREFIX: &str = "git version ";
let raw_version = output
.strip_prefix(PREFIX)
.with_context(|| format!("missing `{PREFIX}` prefix"))?;
let raw_version = raw_version.trim_end(); // There should always be a newline at the end, but
// we don't care if it's missing.
// Git for Windows suffixes the version with ".windows.<n>".
// Strip it if present.
let raw_version = raw_version
.split_once(".windows")
.map_or(raw_version, |(before, _after)| before);
let parsed = GitVersion::try_from(
raw_version
.splitn(3, '.')
.enumerate()
.map(|(idx, s)| {
s.parse().with_context(|| {
format!("failed to parse version number {idx} ({s:?}) as `u8`")
})
})
.collect::<Result<Vec<_>, _>>()?,
)
.map_err(|vec| anyhow::Error::msg(format!("less than 3 version numbers found: {vec:?}")))?;
log::debug!("detected Git version {raw_version}");
Ok(parsed)
}
#[test]
fn test_git_version_parsing() {
macro_rules! test_ok {
($input:expr, $expected:expr) => {
assert_eq!(parse_git_version_output($input).unwrap(), $expected);
};
}
test_ok!("git version 2.3.0", [2, 3, 0]);
test_ok!("git version 0.255.0", [0, 255, 0]);
test_ok!("git version 4.5.6", [4, 5, 6]);
test_ok!("git version 2.3.0.windows.1", [2, 3, 0]);
macro_rules! test_err {
($input:expr, $msg:expr) => {
assert_eq!(
parse_git_version_output($input).unwrap_err().to_string(),
$msg
)
};
}
test_err!("2.3.0", "missing `git version ` prefix");
test_err!("", "missing `git version ` prefix");
test_err!(
"git version 1.2",
"less than 3 version numbers found: [1, 2]"
);
test_err!(
"git version 9001",
"failed to parse version number 0 (\"9001\") as `u8`"
);
test_err!(
"git version ",
"failed to parse version number 0 (\"\") as `u8`"
);
test_err!(
"git version asdf",
"failed to parse version number 0 (\"asdf\") as `u8`"
);
test_err!(
"git version 23.beta",
"failed to parse version number 1 (\"beta\") as `u8`"
);
test_err!(
"git version 1.2.wat",
"failed to parse version number 2 (\"wat\") as `u8`"
);
test_err!(
"git version 1.2.3.",
"failed to parse version number 2 (\"3.\") as `u8`"
);
test_err!(
"git version 1.2.3.4",
"failed to parse version number 2 (\"3.4\") as `u8`"
);
}

View File

@ -1,10 +1,17 @@
use std::{io, process::Command};
use anyhow::Context;
use xshell::Shell;
pub(crate) struct Program {
pub crate_name: &'static str,
pub binary_name: &'static str,
}
pub(crate) fn looks_like_git_sha(input: &str) -> bool {
input.len() == 40 && input.chars().all(|c| c.is_ascii_hexdigit())
}
pub(crate) fn check_all_programs(programs: &[Program]) -> anyhow::Result<()> {
let mut failed_crates = Vec::new();
for &Program {
@ -41,3 +48,119 @@ pub(crate) fn check_all_programs(programs: &[Program]) -> anyhow::Result<()> {
Ok(())
}
pub(crate) fn git_version_at_least(shell: &Shell, version: GitVersion) -> anyhow::Result<bool> {
let output = shell
.cmd("git")
.args(["--version"])
.output()
.context("Failed to invoke `git --version`")?;
let Some(code) = output.status.code() else {
anyhow::bail!("`git --version` failed to return an exit code; interrupt via signal, maybe?")
};
anyhow::ensure!(code == 0, "`git --version` returned a nonzero exit code");
let fmt_err_msg = "`git --version` did not have the expected structure";
let stdout = String::from_utf8(output.stdout).expect(fmt_err_msg);
let parsed = parse_git_version_output(&stdout).expect(fmt_err_msg);
Ok(parsed >= version)
}
pub(crate) type GitVersion = [u8; 3];
fn parse_git_version_output(output: &str) -> anyhow::Result<GitVersion> {
const PREFIX: &str = "git version ";
let raw_version = output
.strip_prefix(PREFIX)
.with_context(|| format!("missing `{PREFIX}` prefix"))?;
let raw_version = raw_version.trim_end(); // There should always be a newline at the end, but
// we don't care if it's missing.
// Git for Windows suffixes the version with ".windows.<n>".
// Strip it if present.
let raw_version = raw_version
.split_once(".windows")
.map_or(raw_version, |(before, _after)| before);
let parsed = GitVersion::try_from(
raw_version
.splitn(3, '.')
.enumerate()
.map(|(idx, s)| {
s.parse().with_context(|| {
format!("failed to parse version number {idx} ({s:?}) as `u8`")
})
})
.collect::<Result<Vec<_>, _>>()?,
)
.map_err(|vec| anyhow::Error::msg(format!("less than 3 version numbers found: {vec:?}")))?;
log::debug!("detected Git version {raw_version}");
Ok(parsed)
}
#[test]
fn test_git_version_parsing() {
macro_rules! test_ok {
($input:expr, $expected:expr) => {
assert_eq!(parse_git_version_output($input).unwrap(), $expected);
};
}
test_ok!("git version 2.3.0", [2, 3, 0]);
test_ok!("git version 0.255.0", [0, 255, 0]);
test_ok!("git version 4.5.6", [4, 5, 6]);
test_ok!("git version 2.3.0.windows.1", [2, 3, 0]);
macro_rules! test_err {
($input:expr, $msg:expr) => {
assert_eq!(
parse_git_version_output($input).unwrap_err().to_string(),
$msg
)
};
}
test_err!("2.3.0", "missing `git version ` prefix");
test_err!("", "missing `git version ` prefix");
test_err!(
"git version 1.2",
"less than 3 version numbers found: [1, 2]"
);
test_err!(
"git version 9001",
"failed to parse version number 0 (\"9001\") as `u8`"
);
test_err!(
"git version ",
"failed to parse version number 0 (\"\") as `u8`"
);
test_err!(
"git version asdf",
"failed to parse version number 0 (\"asdf\") as `u8`"
);
test_err!(
"git version 23.beta",
"failed to parse version number 1 (\"beta\") as `u8`"
);
test_err!(
"git version 1.2.wat",
"failed to parse version number 2 (\"wat\") as `u8`"
);
test_err!(
"git version 1.2.3.",
"failed to parse version number 2 (\"3.\") as `u8`"
);
test_err!(
"git version 1.2.3.4",
"failed to parse version number 2 (\"3.4\") as `u8`"
);
}

View File

@ -3,7 +3,10 @@ use pico_args::Arguments;
use std::fmt::Write;
use xshell::Shell;
use crate::bad_arguments;
use crate::{
bad_arguments,
util::{git_version_at_least, looks_like_git_sha},
};
/// Path to the webgpu_sys folder relative to the root of the repository
const WEBGPU_SYS_PATH: &str = "wgpu/src/backend/webgpu/webgpu_sys";
@ -198,24 +201,31 @@ pub(crate) fn run_vendor_web_sys(shell: Shell, mut args: Arguments) -> anyhow::R
.remove_path(WEBGPU_SYS_PATH)
.context("could not remove webgpu_sys")?;
if let Some(ref version) = version {
if let Some(version) = version.as_deref() {
eprintln!("# Cloning wasm-bindgen repository with version {version}");
shell
.cmd("git")
.args([
"clone",
"-b",
version,
"--depth",
"1",
git_url
.as_deref()
.unwrap_or("https://github.com/rustwasm/wasm-bindgen.git"),
WASM_BINDGEN_TEMP_CLONE_PATH,
])
.ignore_stderr()
.run()
.context("Could not clone wasm-bindgen repository")?;
let mut cmd = shell.cmd("git").args(["clone"]);
if looks_like_git_sha(version) {
if git_version_at_least(&shell, [2, 49, 0])? {
cmd = cmd.args(["--revision", version]);
} else {
bad_arguments!("Must have git >= 2.49 to clone a SHA with --revision");
}
} else {
cmd = cmd.args(["-b", version]);
}
cmd.args([
"--depth",
"1",
git_url
.as_deref()
.unwrap_or("https://github.com/wasm-bindgen/wasm-bindgen.git"),
WASM_BINDGEN_TEMP_CLONE_PATH,
])
.ignore_stderr()
.run()
.context("Could not clone wasm-bindgen repository")?;
}
if let Some(ref path) = path_to_checkout_arg {
@ -229,7 +239,7 @@ pub(crate) fn run_vendor_web_sys(shell: Shell, mut args: Arguments) -> anyhow::R
// The indentation here does not matter, as rustfmt will normalize it.
let file_prefix = format!("\
// DO NOT EDIT THIS FILE!
//
//
// This module part of a subset of web-sys that is used by wgpu's webgpu backend.
//
// These bindings are vendored into wgpu for the sole purpose of letting
@ -246,8 +256,8 @@ pub(crate) fn run_vendor_web_sys(shell: Shell, mut args: Arguments) -> anyhow::R
// Vendoring also allows us to avoid building `web-sys` with
// `--cfg=web_sys_unstable_apis`, needed to get the WebGPU bindings.
//
// If you want to improve the generated code, please submit a PR to the https://github.com/rustwasm/wasm-bindgen repository.
//
// If you want to improve the generated code, please submit a PR to the https://github.com/wasm-bindgen/wasm-bindgen repository.
//
// This file was generated by the `cargo xtask vendor-web-sys {argument_description}` command.\n"
);
@ -286,7 +296,7 @@ pub(crate) fn run_vendor_web_sys(shell: Shell, mut args: Arguments) -> anyhow::R
let mut module_file_contents = format!(
"\
//! Bindings to the WebGPU API.
//!
//!
//! Internally vendored from the `web-sys` crate until the WebGPU binding are stabilized.
{file_prefix}
#![allow(unused_imports, non_snake_case)]\n"