diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2d319a7..a1f318f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -284,8 +284,13 @@ jobs: - name: Fetch the pinned libav libraries run: python sidecar/ffrwd-wasm/fetch_av.py --target ${{ matrix.target }} --os ${{ runner.os }} + # On x86_64 Windows the build links DXC, whose build script asks the + # GitHub API about the release it downloads; the token keeps that + # request off the runner's unauthenticated rate limit. - name: Build wheel uses: PyO3/maturin-action@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: working-directory: sidecar/ffrwd-wasm command: build diff --git a/sidecar/Cargo.lock b/sidecar/Cargo.lock index 760b51a..14a158c 100644 --- a/sidecar/Cargo.lock +++ b/sidecar/Cargo.lock @@ -1337,8 +1337,6 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" [[package]] name = "mach-dxcompiler-rs" version = "0.1.6+2026.09.16-48d5a66.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "699d5b85d279d172f6ea742c6c47342fdd7c388872cea2e137e2c6721ef2e6d6" dependencies = [ "lhash", ] diff --git a/sidecar/Cargo.toml b/sidecar/Cargo.toml index 2df49c9..74a580e 100644 --- a/sidecar/Cargo.toml +++ b/sidecar/Cargo.toml @@ -16,3 +16,8 @@ license = "Apache-2.0" # resolved. [patch.crates-io] wasmtime-wasi-nn = { path = "vendor/wasmtime-wasi-nn" } +# mach-dxcompiler-rs, which links DXC on x86_64 MSVC, asks the GitHub API +# whether its release is immutable. The vendored copy authenticates that +# request with GITHUB_TOKEN when it is set, so a rate-limited runner does not +# fail the build. +mach-dxcompiler-rs = { path = "vendor/mach-dxcompiler-rs" } diff --git a/sidecar/vendor/mach-dxcompiler-rs/.cargo_vcs_info.json b/sidecar/vendor/mach-dxcompiler-rs/.cargo_vcs_info.json new file mode 100644 index 0000000..199db16 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "77b9a2f68c146d1cf45e52ed7e7ac2457be875d5" + }, + "path_in_vcs": "" +} \ No newline at end of file diff --git a/sidecar/vendor/mach-dxcompiler-rs/.gitignore b/sidecar/vendor/mach-dxcompiler-rs/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/.gitignore @@ -0,0 +1 @@ +/target diff --git a/sidecar/vendor/mach-dxcompiler-rs/Cargo.lock b/sidecar/vendor/mach-dxcompiler-rs/Cargo.lock new file mode 100644 index 0000000..02021a0 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/Cargo.lock @@ -0,0 +1,285 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "bindgen" +version = "0.71.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cc" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27f657647bcff5394bf56c7317665bbf790a137a50eaaa5c6bfbb9e27a518f2d" +dependencies = [ + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "lhash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a4c881f502e98c2241d2e5f50040ac73b30194d64452bb6260393b53f0dc9" + +[[package]] +name = "libc" +version = "0.2.164" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "mach-dxcompiler-rs" +version = "0.1.6+2026.09.16-48d5a66.1" +dependencies = [ + "bindgen", + "cc", + "lhash", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml b/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml new file mode 100644 index 0000000..b3de478 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml @@ -0,0 +1,55 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +name = "mach-dxcompiler-rs" +version = "0.1.6+2026.09.16-48d5a66.1" +build = "build.rs" +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = """ +Prebuilt, statically-linked DXC.\r +""" +readme = "README.md" +license = "MIT" +repository = "https://github.com/DouglasDwyer/mach-dxcompiler-rs" + +[package.metadata."docs.rs"] +features = ["cbindings"] + +[features] +cbindings = ["dep:bindgen"] +default = ["msvc_version_validation"] +internal_update_targets = [] +msvc_version_validation = ["dep:cc"] + +[lib] +name = "mach_dxcompiler_rs" +path = "src/lib.rs" + +[build-dependencies.bindgen] +version = "0.71.1" +features = ["runtime"] +optional = true +default-features = false + +[build-dependencies.lhash] +version = "1.1.0" +features = ["sha256"] +default-features = false + +[target.'cfg(target_env = "msvc")'.build-dependencies.cc] +version = "1.2.3" +optional = true diff --git a/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml.orig b/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml.orig new file mode 100644 index 0000000..b0e00d5 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/Cargo.toml.orig @@ -0,0 +1,30 @@ +[package] +name = "mach-dxcompiler-rs" +description = """ +Prebuilt, statically-linked DXC. +""" +version = "0.1.6+2026.09.16-48d5a66.1" +edition = "2021" +license = "MIT" +readme = "README.md" +repository = "https://github.com/DouglasDwyer/mach-dxcompiler-rs" + +[build-dependencies] +bindgen = { version = "0.71.1", optional = true, default-features = false, features = [ "runtime" ] } +lhash = { version = "1.1.0", default-features = false, features = ["sha256"] } + +[target.'cfg(target_env = "msvc")'.build-dependencies] +cc = { version = "1.2.3", optional = true } + +[features] +default = ["msvc_version_validation"] +cbindings = ["dep:bindgen"] +msvc_version_validation = ["dep:cc"] +# Maintainer-only: makes build.rs regenerate targets.rs from the latest immutable +# GitHub release instead of building. See update_targets.rs for usage. +internal_update_targets = [] + + +[package.metadata."docs.rs"] +# Not all-features: internal_update_targets would make build.rs fail instead of documenting. +features = ["cbindings"] diff --git a/sidecar/vendor/mach-dxcompiler-rs/LICENSE b/sidecar/vendor/mach-dxcompiler-rs/LICENSE new file mode 100644 index 0000000..d99ee72 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2021 Hexops Contributors (given via the Git commit history). + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/sidecar/vendor/mach-dxcompiler-rs/README.md b/sidecar/vendor/mach-dxcompiler-rs/README.md new file mode 100644 index 0000000..80a826e --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/README.md @@ -0,0 +1,36 @@ +# mach-dxcompiler-rs (vendored) + +The crate that downloads a prebuilt DXC and links it statically. wgpu-hal +pulls it in through `static-dxc`, which the sidecar enables on x86_64 MSVC +Windows (see `../wasi-webgpu-wasmtime/Cargo.toml`), so it builds only for that +target. + +Taken from crates.io, mach-dxcompiler-rs 0.1.6+2026.09.16-48d5a66.1 (upstream +[DouglasDwyer/mach-dxcompiler-rs](https://github.com/DouglasDwyer/mach-dxcompiler-rs) +at 77b9a2f), the version `../../Cargo.lock` resolved. MIT, as upstream +(`LICENSE`). This file replaces upstream's README, which describes the crate's +use. + +## Why + +Before it downloads anything, the build script asks the GitHub API whether the +release it downloads from is immutable, and refuses to build unless the answer +says so. Upstream makes that request without authentication. On a shared +GitHub Actions runner the unauthenticated rate limit is often used up, the +answer is a rate-limit error with no `immutable` field, and the build fails +with "Refusing to download a mutable GitHub release" although the release is +immutable. That failed a release's Windows x86_64 wheel build. + +## What changed from upstream + +* `build.rs`: when `GITHUB_TOKEN` is set and not empty, the release-metadata + request sends `Authorization: Bearer `. curl reads the header from + stdin, so the token is not on its command line. The archive download that + follows is unchanged and sends no token: it is redirected to GitHub's asset + storage, which needs none. The immutability check and the archive's SHA-256 + check are as upstream. +* This README. + +The release workflows set `GITHUB_TOKEN` to the workflow's own token for the +steps that build the sidecar on Windows. When upstream authenticates this +request itself, this copy can go. diff --git a/sidecar/vendor/mach-dxcompiler-rs/build.rs b/sidecar/vendor/mach-dxcompiler-rs/build.rs new file mode 100644 index 0000000..9902bcf --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/build.rs @@ -0,0 +1,366 @@ +//! Downloads and statically links the `mach-dxcompiler` C library. +//! +//! Vendored for ffrwd from mach-dxcompiler-rs 0.1.6+2026.09.16-48d5a66.1. The one +//! change: with `GITHUB_TOKEN` set, the release-metadata request to the GitHub API +//! is authenticated (see `fetch_release_json`). See README.md. +#![cfg_attr(feature = "internal_update_targets", allow(dead_code, unused_imports))] + +/// Generated release data: the current release tag and its per-target checksums. +mod targets; +/// The `internal_update_targets` feature's tool for regenerating `targets.rs`. +#[cfg(feature = "internal_update_targets")] +mod update_targets; + +use std::process::{Command, Stdio}; +use std::{ + env, fs, + io::{ErrorKind::NotFound, Write}, + path::{Path, PathBuf}, +}; +use targets::{AVAILABLE_TARGETS, RELEASE_TAG}; + +/// Owner of the GitHub repository hosting the prebuilt `mach-dxcompiler` releases. +const RELEASE_REPO_OWNER: &str = "DouglasDwyer"; +/// Name of the GitHub repository hosting the prebuilt `mach-dxcompiler` releases. +const RELEASE_REPO_NAME: &str = "mach-dxcompiler"; + +/// A prebuilt archive this crate can download for one target triple and CRT linkage. +struct Target { + /// Target triple, e.g. `"x86_64-linux-gnu"`. + pub name: &'static str, + /// Whether this archive links the CRT statically. Only targets that publish more + /// than one archive (currently just MSVC) select between builds using this; + /// every other target's sole entry is used regardless of its value. + pub static_crt: bool, + /// SHA-256 of the archive. + pub sha256: &'static str, +} + +/// A prebuilt release archive to download and link. +struct ReleaseArtifact { + /// URL of the `.tar.gz` archive. + pub url: String, + /// Expected SHA-256 of the archive. + pub sha256: &'static str, +} + +/// Downloads and links the static DXC binary. +#[cfg(not(feature = "internal_update_targets"))] +fn main() { + println!("cargo:rerun-if-changed=msvc_version.c"); + println!("cargo:rerun-if-changed=mach_dxc.h"); + println!("cargo:rerun-if-changed=targets.rs"); + #[cfg(all(feature = "msvc_version_validation", target_env = "msvc"))] + validate_msvc_version(); + verify_release_is_immutable(); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("Failed to get OUT_DIR environment")); + let artifact = get_target_artifact(static_crt()); + let file_path = out_dir.join("machdxcompiler.tar.gz"); + download_released_library(&artifact.url, &file_path); + verify_checksum(&file_path, artifact.sha256); + extract_tar_gz(&file_path, &out_dir); + #[cfg(feature = "cbindings")] + generate_bindings(); + link_binary(&out_dir); +} + +/// Emits the `cargo:rustc-link-*` directives needed to statically link `machdxcompiler`. +fn link_binary(out_dir: &Path) { + let os = env::var("CARGO_CFG_TARGET_OS").expect("Failed to get os"); + let abi = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); + + if os == "windows" && abi == "gnu" { + // The Zig compiler suffixes Windows/GNU libraries with `.lib`, but Rust expects `.a` + println!("cargo:rustc-link-lib=static:+verbatim=machdxcompiler.lib"); + } else { + println!("cargo:rustc-link-lib=static=machdxcompiler"); + } + + println!("cargo:rustc-link-search=native={}", out_dir.display()); + + if os == "windows" && abi == "gnu" { + // `-bundle` defers resolution to the final link instead of rustc eagerly packing + // the archive into this crate's own rlib. + println!("cargo:rustc-link-lib=static:-bundle,+whole-archive=stdc++"); + println!("cargo:rustc-link-lib=static:-bundle,+whole-archive=gcc_eh"); + // COM APIs used by DXC (SysAllocStringLen/SysFreeString, CoTaskMemAlloc/Free/Realloc). + println!("cargo:rustc-link-lib=dylib=ole32"); + println!("cargo:rustc-link-lib=dylib=oleaut32"); + } else if os == "linux" && abi == "gnu" { + println!("cargo:rustc-link-lib=dylib=stdc++"); + } +} + +/// Regenerates `targets.rs` instead of building, since the two are mutually exclusive: +/// there's no library to link until `targets.rs` reflects a real, immutable release. +#[cfg(feature = "internal_update_targets")] +fn main() { + update_targets::run(); +} + +/// Generates C API bindings. +#[cfg(feature = "cbindings")] +fn generate_bindings() { + let bindings = bindgen::Builder::default() + .rust_target(bindgen::RustTarget::Stable_1_73) + .header("mach_dxc.h") + .generate() + .expect("Unable to generate bindings"); + + let out_path = + PathBuf::from(env::var("OUT_DIR").expect("Failed to get OUT_DIR environment variable")); + bindings + .write_to_file(out_path.join("cbindings.rs")) + .expect("Couldn't write bindings!"); +} + +/// This checks if the installed version of the Microsoft Visual C++ compiler (MSVC) +/// is compatible with the features introduced in Visual Studio 2022 version 17.11. +/// It retrieves the compiler version using the `cl.exe` executable and compares it against +/// a predefined minimum version. Panics if the current version is lower than the required minimum version. +#[cfg(all(feature = "msvc_version_validation", target_env = "msvc"))] +fn validate_msvc_version() { + let target = std::env::var("TARGET").expect("Faild to get TARGET environment"); + + // https://learn.microsoft.com/en-us/cpp/build/reference/ep-preprocess-to-stdout-without-hash-line-directives?view=msvc-170 + // Preprocess the _MSC_VER macro to get current version + const CL_ARGS: &[&str] = &["/nologo", "/EP", "msvc_version.c"]; + + // https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170#version-macros + // MSVC version of Visual Studio 2022 version 17.11 + const MINIMUM_MSVC_VERSION: usize = 1941; + + let status = cc::windows_registry::find(&target, "cl.exe") + .expect("Failed to locate cl.exe.\nPlease ensure it is installed and included in your system's PATH environment variable.") + .args(CL_ARGS) + .output() + .expect(&format!("Failed to run cl.exe with args: {CL_ARGS:?}")); + if status.stdout.is_empty() { + panic!("Output from cl.exe is empty"); + } + let output_str = match std::str::from_utf8(&status.stdout) { + Ok(s) => s, + Err(e) => panic!( + "Failed to parse output of cl.exe as utf-8:\nsrc{:?}\nerror:\n{e:?}", + status.stdout + ), + }; + + let version_str = output_str.trim(); + let current_version = version_str.parse::().expect(&format!( + "Failed to parse version from output of cl.exe: {version_str}" + )); + if current_version < MINIMUM_MSVC_VERSION { + panic!("Please upgrade the Visual Studio, the minimum supported version of MSVC is {MINIMUM_MSVC_VERSION}, which is correspond to [Visual Studio 2022 version 17.11], but the current version of MSVC is {current_version}\nCheck versions on https://learn.microsoft.com/en-us/cpp/overview/compiler-versions?view=msvc-170#version-macros"); + } +} + +/// Fetches the JSON body of `GET /repos/{owner}/{repo}/releases/{path}` from the +/// GitHub REST API. Panics if the request fails or the response isn't valid UTF-8. +/// +/// With `GITHUB_TOKEN` set and not empty, the request carries +/// `Authorization: Bearer `, so it counts against the token's rate limit +/// rather than the unauthenticated limit of the machine's address. curl reads the +/// header from stdin, which keeps the token off its command line. +fn fetch_release_json(path: &str) -> String { + let api_url = format!( + "https://api.github.com/repos/{RELEASE_REPO_OWNER}/{RELEASE_REPO_NAME}/releases/{path}" + ); + let token = env::var("GITHUB_TOKEN") + .ok() + .filter(|token| !token.is_empty()); + + let mut command = Command::new("curl"); + command.arg("--location"); + if token.is_some() { + command.arg("--header").arg("@-").stdin(Stdio::piped()); + } else { + command.stdin(Stdio::null()); + } + let mut child = command + .arg(&api_url) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .expect("Failed to start Curl to query the GitHub releases API"); + if let Some(token) = token { + let mut stdin = child.stdin.take().expect("Curl's stdin is piped"); + writeln!(stdin, "Authorization: Bearer {token}") + .expect("Failed to pass the GitHub token to Curl"); + } + let output = child + .wait_with_output() + .expect("Failed to wait for Curl to query the GitHub releases API"); + if !output.status.success() { + panic!( + "Failed to query the GitHub releases API at {api_url}:\n{}", + String::from_utf8_lossy(&output.stderr) + ); + } + + String::from_utf8(output.stdout).expect("GitHub releases API returned a non-UTF-8 response") +} + +/// Verifies that [`RELEASE_TAG`] is an [immutable release], so its assets cannot be +/// swapped out after publication. Panics if the release is not immutable or its status +/// cannot be determined via the GitHub REST API. +/// +/// [immutable release]: https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases +fn verify_release_is_immutable() { + let body = fetch_release_json(&format!("tags/{RELEASE_TAG}")); + + if !release_json_is_immutable(&body) { + panic!( + "Refusing to download a mutable GitHub release.\n\n\ + `{RELEASE_REPO_OWNER}/{RELEASE_REPO_NAME}` release `{RELEASE_TAG}` is not an immutable \ + release, so its assets could be replaced after publication without any change to this \ + crate's source. Publish an immutable release and bump `RELEASE_TAG` before building.\n\ + https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases" + ); + } +} + +/// Returns `true` if the release API JSON reports `"immutable": true`. +fn release_json_is_immutable(body: &str) -> bool { + json_field(body, "immutable").is_some_and(|value| value.starts_with("true")) +} + +/// Returns the JSON text immediately following a top-level `"key":`, with leading +/// whitespace trimmed, scanning the payload directly to avoid a JSON-parser build +/// dependency. +fn json_field<'a>(json: &'a str, key: &str) -> Option<&'a str> { + let (_, after_key) = json.split_once(&format!("\"{key}\""))?; + Some(after_key.trim_start().strip_prefix(':')?.trim_start()) +} + +/// Verifies that the downloaded archive's SHA-256 matches `expected`, pinning the +/// exact native-binary bytes that get linked. Panics if the archive cannot be read +/// or its digest does not match. +fn verify_checksum(file_path: &Path, expected: &str) { + let bytes = fs::read(file_path).unwrap_or_else(|e| { + panic!( + "Failed to read downloaded archive {} for checksum verification: {e}", + file_path.display() + ) + }); + let actual = sha256_hex(&bytes); + + if !actual.eq_ignore_ascii_case(expected) { + let _ = fs::remove_file(file_path); + panic!( + "Checksum mismatch for downloaded archive {}.\n\ + \n\ + expected SHA-256: {expected}\n\ + actual SHA-256: {actual}\n\ + \n\ + The downloaded archive does not match the checksum pinned in build.rs. The \ + release asset may have been tampered with, or `AVAILABLE_TARGETS` may be stale \ + for release `{RELEASE_TAG}`. Refusing to link an unverified binary.", + file_path.display() + ); + } +} + +/// Computes the SHA-256 digest of `data`, returned as lowercase hex. +fn sha256_hex(data: &[u8]) -> String { + use std::fmt::Write; + + let mut hex = String::with_capacity(64); + for byte in lhash::sha256(data) { + let _ = write!(hex, "{byte:02x}"); + } + hex +} + +/// Gets the download URL and metadata for the appropriate DXC binary. +fn get_target_artifact(static_crt: bool) -> ReleaseArtifact { + let base_url = format!("https://github.com/{RELEASE_REPO_OWNER}/{RELEASE_REPO_NAME}/releases"); + let arch = env::var("CARGO_CFG_TARGET_ARCH").expect("Failed to get architecture"); + let mut os = env::var("CARGO_CFG_TARGET_OS").expect("Failed to get os"); + if env::var("CARGO_CFG_TARGET_VENDOR").unwrap_or_default() == "apple" && os == "darwin" { + os = "macos".to_owned(); + } + let mut abi = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default(); + if abi.is_empty() { + abi = "none".to_owned(); + } + let target_name = format!("{arch}-{os}-{abi}"); + + let candidates: Vec<_> = AVAILABLE_TARGETS + .iter() + .filter(|t| t.name == target_name) + .collect(); + let target = candidates + .iter() + .copied() + .max_by_key(|t| t.static_crt == static_crt) + .unwrap_or_else(|| { + panic!("Unsupported target: {target_name}\nCheck supported targets on {base_url}") + }); + + let crt = if candidates.len() > 1 && !target.static_crt { + "Dynamic_lib" + } else { + "lib" + }; + + ReleaseArtifact { + url: format!("{base_url}/download/{RELEASE_TAG}/{target_name}_ReleaseFast_{crt}.tar.gz"), + sha256: target.sha256, + } +} + +/// Downloads the provided URL to a file. +fn download_released_library(url: &str, file_path: &Path) { + match Command::new("curl") + .arg("--location") + .arg("-o") + .arg(file_path) + .arg(url) + .spawn() + .expect("Failed to start Curl to download DXC binary") + .wait() + { + Ok(result) => { + if !result.success() { + if let Err(e) = fs::remove_file(file_path) { + if e.kind() != NotFound { + panic!("Failed to remove incomplete file"); + } + } + panic!("{result}"); + } + } + Err(_) => { + if let Err(e) = fs::remove_file(file_path) { + if e.kind() != NotFound { + panic!("Failed to remove incomplete file"); + } + } + panic!("Failed to download DXC binary"); + } + } +} + +/// Extracts the file at the provided path as a `.tar.gz` file. +/// The contents are extracted to the current directory. +fn extract_tar_gz(path: &Path, output_dir: &Path) { + let result = Command::new("tar") + .current_dir(output_dir) + .arg("-xzf") + .arg(path) + .spawn() + .expect("Failed to start Tar to extract DXC binary") + .wait() + .expect("Failed to extract DXC binary"); + if !result.success() { + panic!("{result}"); + } +} + +/// Determines whether the CRT is being statically or dynamically linked. +fn static_crt() -> bool { + env::var("CARGO_ENCODED_RUSTFLAGS") + .map(|flags| flags.contains("target-feature=+crt-static")) + .unwrap_or(false) +} diff --git a/sidecar/vendor/mach-dxcompiler-rs/mach_dxc.h b/sidecar/vendor/mach-dxcompiler-rs/mach_dxc.h new file mode 100644 index 0000000..6c14957 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/mach_dxc.h @@ -0,0 +1,132 @@ +#ifndef MACH_DXC_H_ +#define MACH_DXC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(MACH_DXC_C_SHARED_LIBRARY) +# if defined(_WIN32) +# if defined(MACH_DXC_C_IMPLEMENTATION) +# define MACH_EXPORT __declspec(dllexport) +# else +# define MACH_EXPORT __declspec(dllimport) +# endif +# else // defined(_WIN32) +# if defined(MACH_DXC_C_IMPLEMENTATION) +# define MACH_EXPORT __attribute__((visibility("default"))) +# else +# define MACH_EXPORT +# endif +# endif // defined(_WIN32) +#else // defined(MACH_DXC_C_SHARED_LIBRARY) +# define MACH_EXPORT +#endif // defined(MACH_DXC_C_SHARED_LIBRARY) + +#if !defined(MACH_OBJECT_ATTRIBUTE) +#define MACH_OBJECT_ATTRIBUTE +#endif + +#include + +typedef struct MachDxcCompilerImpl* MachDxcCompiler MACH_OBJECT_ATTRIBUTE; +typedef struct MachDxcCompileResultImpl* MachDxcCompileResult MACH_OBJECT_ATTRIBUTE; +typedef struct MachDxcCompileErrorImpl* MachDxcCompileError MACH_OBJECT_ATTRIBUTE; +typedef struct MachDxcCompileObjectImpl* MachDxcCompileObject MACH_OBJECT_ATTRIBUTE; + + +typedef struct MachDxcIncludeResult { + const char* header_data; // UTF-8 or null + size_t header_length; +} MachDxcIncludeResult; + +typedef MachDxcIncludeResult* (*MachDxcIncludeFunc)(void* ctx, const char* header_name); + +typedef int (*MachDxcFreeIncludeFunc)(void* ctx, MachDxcIncludeResult* result); + +typedef struct MachDxcIncludeCallbacks { + void* include_ctx; + MachDxcIncludeFunc include_func; + MachDxcFreeIncludeFunc free_func; +} MachDxcIncludeCallbacks; + + +typedef struct MachDxcCompileOptions { + // Required + char const* code; + size_t code_len; + char const* const* args; + size_t args_len; + + // Optional + MachDxcIncludeCallbacks* include_callbacks; // nullable +} MachDxcCompileOptions; + + +//---------------- +// MachDxcCompiler +//---------------- + +/// Initializes a DXC compiler +/// +/// Invoke machDxcDeinit when done with the compiler. +MACH_EXPORT MachDxcCompiler machDxcInit(); + +/// Deinitializes the DXC compiler. +MACH_EXPORT void machDxcDeinit(MachDxcCompiler compiler); + +//--------------------- +// MachDxcCompileResult +//--------------------- + +/// Compiles the given code with the given dxc.exe CLI arguments +/// +/// Invoke machDxcCompileResultDeinit when done with the result. +MACH_EXPORT MachDxcCompileResult machDxcCompile( + MachDxcCompiler compiler, + MachDxcCompileOptions* options +); + +/// Returns an error object, or null in the case of success. +/// +/// Invoke machDxcCompileErrorDeinit when done with the error, iff it was non-null. +MACH_EXPORT MachDxcCompileError machDxcCompileResultGetError(MachDxcCompileResult err); + +/// Returns the compiled object code, or null if an error occurred. +MACH_EXPORT MachDxcCompileObject machDxcCompileResultGetObject(MachDxcCompileResult err); + +/// Deinitializes the DXC compiler. +MACH_EXPORT void machDxcCompileResultDeinit(MachDxcCompileResult err); + +//--------------------- +// MachDxcCompileObject +//--------------------- + +/// Returns a pointer to the raw bytes of the compiled object file. +MACH_EXPORT char const* machDxcCompileObjectGetBytes(MachDxcCompileObject err); + +/// Returns the length of the compiled object file. +MACH_EXPORT size_t machDxcCompileObjectGetBytesLength(MachDxcCompileObject err); + +/// Deinitializes the compiled object, calling Get methods after this is illegal. +MACH_EXPORT void machDxcCompileObjectDeinit(MachDxcCompileObject err); + +//-------------------- +// MachDxcCompileError +//-------------------- + +/// Returns a pointer to the null-terminated UTF-8 encoded error string. This includes +/// compiler warnings, unless they were disabled in the compile arguments. +MACH_EXPORT char const* machDxcCompileErrorGetString(MachDxcCompileError err); + +/// Returns the length of the error string. +MACH_EXPORT size_t machDxcCompileErrorGetStringLength(MachDxcCompileError err); + +/// Deinitializes the error, calling Get methods after this is illegal. +MACH_EXPORT void machDxcCompileErrorDeinit(MachDxcCompileError err); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // MACH_DXC_H_ diff --git a/sidecar/vendor/mach-dxcompiler-rs/msvc_version.c b/sidecar/vendor/mach-dxcompiler-rs/msvc_version.c new file mode 100644 index 0000000..dcc593f --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/msvc_version.c @@ -0,0 +1 @@ +_MSC_VER \ No newline at end of file diff --git a/sidecar/vendor/mach-dxcompiler-rs/src/cbindings.rs b/sidecar/vendor/mach-dxcompiler-rs/src/cbindings.rs new file mode 100644 index 0000000..6887548 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/src/cbindings.rs @@ -0,0 +1,178 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] +include!(concat!(env!("OUT_DIR"), "/cbindings.rs")); + +// Originally based on https://github.com/hexops/mach-dxcompiler/blob/main/src/main.zig +#[cfg(test)] +mod tests { + #[test] + fn mach_compile() { + use super::*; + use std::{ffi::CStr, ptr, slice, str}; + + struct Compiler { + handle: MachDxcCompiler, + } + + impl Compiler { + fn new() -> Self { + Self { + handle: unsafe { machDxcInit() }, + } + } + + fn compile(&self, code: &[u8], args: I) -> CompileResult + where + T: AsRef, + I: IntoIterator, + { + let args = args + .into_iter() + .map(|s| s.as_ref().as_ptr()) + .collect::>(); + let mut options = MachDxcCompileOptions { + code: code.as_ptr().cast(), + code_len: code.len(), + args: args.as_ptr(), + args_len: args.len(), + include_callbacks: ptr::null_mut(), + }; + unsafe { machDxcCompile(self.handle, &mut options) }.into() + } + } + + impl Drop for Compiler { + fn drop(&mut self) { + unsafe { + machDxcDeinit(self.handle); + } + } + } + + struct CompileResult { + handle: MachDxcCompileResult, + } + + impl CompileResult { + fn error(&self) -> Option { + let handle = unsafe { machDxcCompileResultGetError(self.handle) }; + if handle.is_null() { + None + } else { + Some(handle.into()) + } + } + + fn object(&self) -> Option { + let handle = unsafe { machDxcCompileResultGetObject(self.handle) }; + if handle.is_null() { + None + } else { + Some(handle.into()) + } + } + } + + impl From for CompileResult { + fn from(handle: MachDxcCompileResult) -> Self { + Self { handle } + } + } + + impl Drop for CompileResult { + fn drop(&mut self) { + unsafe { + machDxcCompileResultDeinit(self.handle); + } + } + } + + struct CompileError { + handle: MachDxcCompileError, + } + + impl CompileError { + fn message(&self) -> &str { + let len = unsafe { machDxcCompileErrorGetStringLength(self.handle) }; + let ptr = unsafe { machDxcCompileErrorGetString(self.handle) }; + unsafe { + let bytes = slice::from_raw_parts(ptr.cast(), len); + str::from_utf8_unchecked(bytes) + } + } + } + + impl From for CompileError { + fn from(handle: MachDxcCompileError) -> Self { + Self { handle } + } + } + + impl Drop for CompileError { + fn drop(&mut self) { + unsafe { + machDxcCompileErrorDeinit(self.handle); + } + } + } + + struct CompileObject { + handle: MachDxcCompileObject, + } + + impl CompileObject { + fn bytes(&self) -> &[u8] { + let len = unsafe { machDxcCompileObjectGetBytesLength(self.handle) }; + let ptr = unsafe { machDxcCompileObjectGetBytes(self.handle) }; + unsafe { std::slice::from_raw_parts(ptr.cast(), len) } + } + } + + impl From for CompileObject { + fn from(handle: MachDxcCompileObject) -> Self { + Self { handle } + } + } + + impl Drop for CompileObject { + fn drop(&mut self) { + unsafe { + machDxcCompileObjectDeinit(self.handle); + } + } + } + let code = r#" + Texture1D tex[5] : register(t3); + SamplerState SS[3] : register(s2); + + [RootSignature("DescriptorTable(SRV(t3, numDescriptors=5)), DescriptorTable(Sampler(s2, numDescriptors=3))")] + float4 main(int i : A, float j : B) : SV_TARGET + { + float4 r = tex[NonUniformResourceIndex(i)].Sample(SS[NonUniformResourceIndex(i)], i); + r += tex[NonUniformResourceIndex(j)].Sample(SS[i], j+2); + return r; + };"#; + let args = &[ + CStr::from_bytes_until_nul(b"-E\0").unwrap(), + CStr::from_bytes_until_nul(b"main\0").unwrap(), + CStr::from_bytes_until_nul(b"-T\0").unwrap(), + CStr::from_bytes_until_nul(b"ps_6_0\0").unwrap(), + CStr::from_bytes_until_nul(b"-D\0").unwrap(), + CStr::from_bytes_until_nul(b"MYDEFINE=1\0").unwrap(), + CStr::from_bytes_until_nul(b"-Qstrip_debug\0").unwrap(), + CStr::from_bytes_until_nul(b"-Qstrip_reflect\0").unwrap(), + ]; + let compiler = Compiler::new(); + let result = compiler.compile(code.as_bytes(), args); + if let Some(error) = result.error() { + let msg = error.message(); + panic!("{msg}"); + } + let Some(obj) = result.object() else { + panic!("no object is produced"); + }; + assert_eq!(obj.bytes().len(), 2392); + } +} diff --git a/sidecar/vendor/mach-dxcompiler-rs/src/lib.rs b/sidecar/vendor/mach-dxcompiler-rs/src/lib.rs new file mode 100644 index 0000000..fc651b2 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/src/lib.rs @@ -0,0 +1,45 @@ +//! Provides a statically-linked copy of `DXC` and includes the ability to call the main exported function ([`DxcCreateInstance`]). +//! Normal COM APIs can be used to interact with DXC objects once they have been created. + +/// Provides C API bindings to the mach `dxcompiler` library. +#[cfg(feature = "cbindings")] +pub mod cbindings; + +use std::ffi::{c_long, c_void}; +use std::sync::*; + +/// Invokes the native [`DxcCreateInstance`](https://learn.microsoft.com/en-us/windows/win32/api/dxcapi/nf-dxcapi-dxccreateinstance) +/// funcion. Creates a new uninitialized object of the specified class. +/// +/// # Safety +/// +/// `rclsid` and `riid` must point to valid Windows `GUID`s. `ppv` must point to a writable +/// `*mut c_void` instance. +#[allow(non_snake_case)] +pub unsafe extern "system" fn DxcCreateInstance( + rclsid: *const c_void, + riid: *const c_void, + ppv: *mut *mut c_void, +) -> c_long { + /// Lock which ensures that the dxcompiler library has been initialized before use. + static MAIN_INITIALIZER: Once = Once::new(); + MAIN_INITIALIZER.call_once(|| unsafe { bindings::MachDxcompilerInvokeDllMain() }); + bindings::DxcCreateInstance(rclsid, riid, ppv) +} + +/// Provides bindings to the native `dxcompiler` library. +mod bindings { + use super::*; + + extern "system" { + /// Calls the [`DxcCreateInstance`](https://learn.microsoft.com/en-us/windows/win32/api/dxcapi/nf-dxcapi-dxccreateinstance) function. + pub fn DxcCreateInstance( + rclsid: *const c_void, + riid: *const c_void, + ppv: *mut *mut c_void, + ) -> c_long; + + /// Invokes the [`DllMain`](https://learn.microsoft.com/en-us/windows/win32/dlls/dllmain) entrypoint for the dxcompiler library. + pub fn MachDxcompilerInvokeDllMain(); + } +} diff --git a/sidecar/vendor/mach-dxcompiler-rs/targets.rs b/sidecar/vendor/mach-dxcompiler-rs/targets.rs new file mode 100644 index 0000000..d614fa8 --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/targets.rs @@ -0,0 +1,60 @@ +// This file is automatically generated for the latest release using the `internal_update_targets` feature + +use crate::Target; + +/// Release tag the prebuilt binaries are downloaded from. +pub const RELEASE_TAG: &str = "2026.09.16+48d5a66.1"; + +/// Every archive this crate can download, for [`RELEASE_TAG`]. +pub const AVAILABLE_TARGETS: &[Target] = &[ + Target { + name: "aarch64-linux-gnu", + static_crt: true, + sha256: "387d23b29d99e76cbccd4eb2a7385b24b9b5696c34d6070c99bc56fd82d004ba", + }, + Target { + name: "aarch64-linux-musl", + static_crt: true, + sha256: "1f933f0b0abc842f78115b981e3360627a40d365ddad5eeb6ae8f87f2d3981aa", + }, + Target { + name: "aarch64-macos-none", + static_crt: true, + sha256: "cfd4803e44d790fee2889245bc6e7c43390c436046bd07caa4fda35994c5ab36", + }, + Target { + name: "aarch64-windows-gnu", + static_crt: true, + sha256: "adcce028662bef80e6ee1661d9a1815034e414297b1cf5755f8a5857b83ef59b", + }, + Target { + name: "x86_64-linux-gnu", + static_crt: true, + sha256: "11a8719c294846bf490f84f5ad0abc20c648622ea111d1031ee6af8e14d04c3f", + }, + Target { + name: "x86_64-linux-musl", + static_crt: true, + sha256: "2af49586ef9756ebbcd901af90ef1bd78f0ebdd9802bf7bd2bf22ab01e84684b", + }, + Target { + name: "x86_64-macos-none", + static_crt: true, + sha256: "9cf6bdf17111a8abb829568396adc6ab3c49e63e9a1e5391720d1c7cd9bcfa0d", + }, + Target { + name: "x86_64-windows-gnu", + static_crt: true, + sha256: "6b660457ebf3f699d75a05c7ceb46eb215c04f1a4df310e7687ad964bcd03ccb", + }, + Target { + name: "x86_64-windows-msvc", + static_crt: true, + sha256: "60bdfab56d50679ef071489432fd716e5c2c73c7ec08c33a29d0c910ddca1b04", + }, + Target { + name: "x86_64-windows-msvc", + static_crt: false, + sha256: "2db15ab14e6eee9b9931d75d1690b4f7d81cad446fecccfe4e36e218dee25cce", + }, +]; diff --git a/sidecar/vendor/mach-dxcompiler-rs/update_targets.rs b/sidecar/vendor/mach-dxcompiler-rs/update_targets.rs new file mode 100644 index 0000000..0d404bb --- /dev/null +++ b/sidecar/vendor/mach-dxcompiler-rs/update_targets.rs @@ -0,0 +1,137 @@ +use super::{fetch_release_json, json_field, release_json_is_immutable}; +use super::{RELEASE_REPO_NAME, RELEASE_REPO_OWNER}; + +/// Regenerates `targets.rs` from the latest GitHub release and stops the build. +/// +/// Fetches the repository's latest release, verifies it is immutable, reads each +/// `*_ReleaseFast_{lib,Dynamic_lib}.tar.gz` asset's SHA-256 straight from the +/// GitHub-computed `digest` field, and rewrites `targets.rs` to match. +pub fn run() -> ! { + let body = fetch_release_json("latest"); + + if !release_json_is_immutable(&body) { + panic!( + "Refusing to pin a mutable GitHub release.\n\n\ + The latest release of `{RELEASE_REPO_OWNER}/{RELEASE_REPO_NAME}` is not an \ + immutable release, so its assets could be replaced after publication. Wait for an \ + immutable release before regenerating targets.rs.\n\ + https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases" + ); + } + + let tag = json_string_field(&body, "tag_name").expect("Release JSON had no `tag_name` field"); + let assets_body = + json_array_field(&body, "assets").expect("Release JSON had no `assets` array"); + + let mut targets: Vec<(&str, bool, &str)> = json_objects(assets_body) + .into_iter() + .filter_map(|asset| { + let name = json_string_field(asset, "name")?; + let (target_name, crt) = name.strip_suffix(".tar.gz")?.split_once("_ReleaseFast_")?; + let static_crt = match crt { + "lib" => true, + "Dynamic_lib" => false, + _ => return None, + }; + let digest = json_string_field(asset, "digest") + .unwrap_or_else(|| panic!("Asset `{name}` has no digest")); + let sha256 = digest + .strip_prefix("sha256:") + .unwrap_or_else(|| panic!("Asset `{name}` has a non-SHA-256 digest: {digest}")); + Some((target_name, static_crt, sha256)) + }) + .collect(); + targets.sort_by_key(|&(name, static_crt, _)| (name, std::cmp::Reverse(static_crt))); + + std::fs::write("targets.rs", render_targets_file(tag, &targets)) + .expect("Failed to write targets.rs"); + let _ = std::process::Command::new("rustfmt") + .arg("targets.rs") + .status(); + + panic!( + "Updated targets.rs for release `{tag}` with {} target(s). This always \"fails\" the \ + build; re-run `cargo build` without `--features internal_update_targets` to build \ + normally.", + targets.len() + ); +} + +/// Renders the contents of `targets.rs` for the given release tag and target list. +fn render_targets_file(tag: &str, targets: &[(&str, bool, &str)]) -> String { + use std::fmt::Write; + + let mut out = String::from( + "// This file is automatically generated for the latest release using the \ + `internal_update_targets` feature\n\n\ + use crate::Target;\n\n\ + /// Release tag the prebuilt binaries are downloaded from.\n", + ); + let _ = writeln!(out, "pub const RELEASE_TAG: &str = \"{tag}\";\n"); + out.push_str( + "/// Every archive this crate can download, for [`RELEASE_TAG`].\n\ + pub const AVAILABLE_TARGETS: &[Target] = &[\n", + ); + for (name, static_crt, sha256) in targets { + let _ = writeln!( + out, + "Target {{ name: \"{name}\", static_crt: {static_crt}, sha256: \"{sha256}\" }}," + ); + } + out.push_str("];\n"); + out +} + +/// Reads a top-level `"key": ""` field out of a JSON object. +fn json_string_field<'a>(json: &'a str, key: &str) -> Option<&'a str> { + let rest = json_field(json, key)?.strip_prefix('"')?; + Some(&rest[..rest.find('"')?]) +} + +/// Reads a top-level `"key": [...]` field out of a JSON object, returning the raw text +/// between the array's brackets. +fn json_array_field<'a>(json: &'a str, key: &str) -> Option<&'a str> { + let body = json_field(json, key)?.strip_prefix('[')?; + Some(&body[..find_closing_bracket(body)?]) +} + +/// Splits the body of a JSON array into the raw text of each top-level `{...}` object. +fn json_objects(array_body: &str) -> Vec<&str> { + let mut objects = Vec::new(); + let mut rest = array_body; + while let Some(start) = rest.find('{') { + let body = &rest[start + 1..]; + let Some(end) = find_closing_bracket(body) else { + break; + }; + objects.push(&body[..end]); + rest = &body[end + 1..]; + } + objects +} + +/// Given the text just after an opening `{` or `[`, returns the index of the matching +/// closing bracket, correctly skipping over nested brackets and quoted strings. +fn find_closing_bracket(s: &str) -> Option { + let mut depth = 0u32; + let mut in_string = false; + let mut escaped = false; + for (i, c) in s.char_indices() { + if in_string { + match c { + '\\' if !escaped => escaped = true, + '"' if !escaped => in_string = false, + _ => escaped = false, + } + continue; + } + match c { + '"' => in_string = true, + '[' | '{' => depth += 1, + ']' | '}' if depth == 0 => return Some(i), + ']' | '}' => depth -= 1, + _ => {} + } + } + None +}