diff --git a/xtask/src/cts.rs b/xtask/src/cts.rs index 2dfbb2013..fdeae96c3 100644 --- a/xtask/src/cts.rs +++ b/xtask/src/cts.rs @@ -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 { - 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 { - 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.". - // 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::, _>>()?, - ) - .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`" - ); -} diff --git a/xtask/src/util.rs b/xtask/src/util.rs index e38afde60..eb3e7819f 100644 --- a/xtask/src/util.rs +++ b/xtask/src/util.rs @@ -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 { + 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 { + 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.". + // 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::, _>>()?, + ) + .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`" + ); +} diff --git a/xtask/src/vendor_web_sys.rs b/xtask/src/vendor_web_sys.rs index f519ae786..15f25f452 100644 --- a/xtask/src/vendor_web_sys.rs +++ b/xtask/src/vendor_web_sys.rs @@ -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"