From e9280747cd16c37ed23c16ec48ccd128cbf3c6ac Mon Sep 17 00:00:00 2001 From: Jon-Carlos Rivera Date: Thu, 15 Oct 2026 18:40:07 -0700 Subject: [PATCH] sidecar: DXC's release check authenticates with GITHUB_TOKEN mach-dxcompiler-rs, which links DXC into the x86_64 MSVC sidecar, asks the GitHub API whether its release is immutable before it downloads, and makes that request without authentication. On a shared runner the unauthenticated rate limit can be used up; the answer then has no "immutable" field and the build fails as though the release were mutable. That failed the 0.30.1 Windows x86_64 wheel once. The crate is vendored at the version the lock resolved and patched in. Its build script now sends Authorization: Bearer with GITHUB_TOKEN when that is set, on the API request only, passing the header to curl on stdin. The archive download, the immutability check and the checksum check are as upstream. The release workflow's wheel build passes the workflow token. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/release.yml | 5 + sidecar/Cargo.lock | 2 - sidecar/Cargo.toml | 5 + .../mach-dxcompiler-rs/.cargo_vcs_info.json | 6 + sidecar/vendor/mach-dxcompiler-rs/.gitignore | 1 + sidecar/vendor/mach-dxcompiler-rs/Cargo.lock | 285 ++++++++++++++ sidecar/vendor/mach-dxcompiler-rs/Cargo.toml | 55 +++ .../vendor/mach-dxcompiler-rs/Cargo.toml.orig | 30 ++ sidecar/vendor/mach-dxcompiler-rs/LICENSE | 25 ++ sidecar/vendor/mach-dxcompiler-rs/README.md | 36 ++ sidecar/vendor/mach-dxcompiler-rs/build.rs | 366 ++++++++++++++++++ sidecar/vendor/mach-dxcompiler-rs/mach_dxc.h | 132 +++++++ .../vendor/mach-dxcompiler-rs/msvc_version.c | 1 + .../mach-dxcompiler-rs/src/cbindings.rs | 178 +++++++++ sidecar/vendor/mach-dxcompiler-rs/src/lib.rs | 45 +++ sidecar/vendor/mach-dxcompiler-rs/targets.rs | 60 +++ .../mach-dxcompiler-rs/update_targets.rs | 137 +++++++ 17 files changed, 1367 insertions(+), 2 deletions(-) create mode 100644 sidecar/vendor/mach-dxcompiler-rs/.cargo_vcs_info.json create mode 100644 sidecar/vendor/mach-dxcompiler-rs/.gitignore create mode 100644 sidecar/vendor/mach-dxcompiler-rs/Cargo.lock create mode 100644 sidecar/vendor/mach-dxcompiler-rs/Cargo.toml create mode 100644 sidecar/vendor/mach-dxcompiler-rs/Cargo.toml.orig create mode 100644 sidecar/vendor/mach-dxcompiler-rs/LICENSE create mode 100644 sidecar/vendor/mach-dxcompiler-rs/README.md create mode 100644 sidecar/vendor/mach-dxcompiler-rs/build.rs create mode 100644 sidecar/vendor/mach-dxcompiler-rs/mach_dxc.h create mode 100644 sidecar/vendor/mach-dxcompiler-rs/msvc_version.c create mode 100644 sidecar/vendor/mach-dxcompiler-rs/src/cbindings.rs create mode 100644 sidecar/vendor/mach-dxcompiler-rs/src/lib.rs create mode 100644 sidecar/vendor/mach-dxcompiler-rs/targets.rs create mode 100644 sidecar/vendor/mach-dxcompiler-rs/update_targets.rs 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 +}