diff --git a/Cargo.lock b/Cargo.lock index f354c008c..92db4fa5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -451,9 +451,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "5f0e0fee31ef5ed1ba1316088939cea399010ed7731dba877ed44aeb407a75ea" [[package]] name = "anymap2" @@ -482,7 +482,7 @@ dependencies = [ "image", "kurbo 0.12.0", "peniko", - "thiserror 2.0.17", + "thiserror 2.0.18", "usvg", ] @@ -680,11 +680,11 @@ checksum = "d67af77d68a931ecd5cbd8a3b5987d63a1d1d1278f7f6a60ae33db485cdebb69" [[package]] name = "ar_archive_writer" -version = "0.2.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0c269894b6fe5e9d7ada0cf69b5bf847ff35bc25fc271f08e1d080fce80339a" +checksum = "7eb93bbb63b9c227414f6eb3a0adfddca591a8ce1e9b60661bb08969b87e340b" dependencies = [ - "object 0.32.2", + "object 0.37.3", ] [[package]] @@ -715,9 +715,9 @@ dependencies = [ [[package]] name = "arc-swap" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e" +checksum = "9ded5f9a03ac8f24d1b8a25101ee812cd32cdc8c50a4c50237de2c4915850e73" dependencies = [ "rustversion", ] @@ -900,9 +900,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.37" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d10e4f991a553474232bc0a31799f6d24b034a84c0971d80d2e2f78b2e576e40" +checksum = "82da0ea54ea533ec09d949717c6386a1c34f2d9b51c1fcc7eef8b9ce0b690a3e" dependencies = [ "compression-codecs", "compression-core", @@ -1296,9 +1296,9 @@ dependencies = [ [[package]] name = "aws-lc-rs" -version = "1.15.3" +version = "1.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e84ce723ab67259cfeb9877c6a639ee9eb7a27b28123abd71db7f0d5d0cc9d86" +checksum = "7b7b6141e96a8c160799cc2d5adecd5cbbe5054cb8c7c4af53da0f83bb7ad256" dependencies = [ "aws-lc-sys", "zeroize", @@ -1306,9 +1306,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.36.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a442ece363113bd4bd4c8b18977a7798dd4d3c3383f34fb61936960e8f4ad8" +checksum = "5c34dda4df7017c8db52132f0f8a2e0f8161649d15723ed63fc00c82d0f2081a" dependencies = [ "cc", "cmake", @@ -1499,7 +1499,7 @@ dependencies = [ "serde", "serde_json", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower-layer", "tower-service", @@ -1702,7 +1702,7 @@ dependencies = [ "ron", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "thread_local", "tracing", "uuid", @@ -1758,7 +1758,7 @@ dependencies = [ "ctrlc", "downcast-rs 2.0.2", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", "variadics_please", "wasm-bindgen", "web-sys", @@ -1796,7 +1796,7 @@ dependencies = [ "ron", "serde", "stackfuture", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", "wasm-bindgen", @@ -1856,7 +1856,7 @@ dependencies = [ "downcast-rs 2.0.2", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-types 26.0.0", ] @@ -1872,7 +1872,7 @@ dependencies = [ "derive_more 2.1.1", "encase", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-types 26.0.0", ] @@ -1901,7 +1901,7 @@ dependencies = [ "nonmax", "radsort", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -1958,7 +1958,7 @@ dependencies = [ "serde", "slotmap", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "variadics_please", ] @@ -1996,7 +1996,7 @@ dependencies = [ "bevy_platform", "bevy_time", "gilrs", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2071,7 +2071,7 @@ dependencies = [ "serde", "serde_json", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2099,7 +2099,7 @@ dependencies = [ "rectangle-pack", "ruzstd 0.8.2", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "wgpu-types 26.0.0", ] @@ -2118,7 +2118,7 @@ dependencies = [ "derive_more 2.1.1", "log", "smol_str", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2135,7 +2135,7 @@ dependencies = [ "bevy_reflect", "bevy_window", "log", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2258,7 +2258,7 @@ dependencies = [ "rand_distr", "serde", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "variadics_please", ] @@ -2282,7 +2282,7 @@ dependencies = [ "bytemuck", "derive_more 2.1.1", "hexasphere", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "wgpu-types 26.0.0", ] @@ -2325,7 +2325,7 @@ dependencies = [ "offset-allocator", "smallvec", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2400,7 +2400,7 @@ dependencies = [ "nonmax", "radsort", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -2432,7 +2432,7 @@ dependencies = [ "serde", "smallvec", "smol_str", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", "variadics_please", "wgpu-types 26.0.0", @@ -2493,7 +2493,7 @@ dependencies = [ "offset-allocator", "send_wrapper", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "variadics_please", "wasm-bindgen", @@ -2530,7 +2530,7 @@ dependencies = [ "bevy_utils", "derive_more 2.1.1", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "uuid", ] @@ -2546,7 +2546,7 @@ dependencies = [ "naga 26.0.0", "naga_oil", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "wgpu-types 26.0.0", ] @@ -2675,7 +2675,7 @@ dependencies = [ "serde", "smallvec", "sys-locale", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "wgpu-types 26.0.0", ] @@ -2710,7 +2710,7 @@ dependencies = [ "bevy_utils", "derive_more 2.1.1", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -2741,7 +2741,7 @@ dependencies = [ "derive_more 2.1.1", "smallvec", "taffy 0.7.7", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", ] @@ -3207,7 +3207,7 @@ dependencies = [ "log", "serde", "serde-xml-rs 0.8.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -3340,7 +3340,7 @@ dependencies = [ "objc2-foundation 0.2.2", "once_cell", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "uuid", @@ -3428,9 +3428,9 @@ dependencies = [ [[package]] name = "bytemuck" -version = "1.24.0" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" dependencies = [ "bytemuck_derive", ] @@ -3460,9 +3460,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -3621,7 +3621,7 @@ dependencies = [ "semver", "serde", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "toml 0.8.23", "walkdir", @@ -3646,7 +3646,7 @@ dependencies = [ "serde", "serde-untagged", "serde-value", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.8.23", "unicode-xid", "url", @@ -3663,7 +3663,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3717,9 +3717,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.53" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "jobserver", @@ -3873,9 +3873,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6e6ff9dcd79cff5cd969a17a545d79e84ab086e444102a591e288a8aa3ce394" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -3883,9 +3883,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.54" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa42cf4d2b7a41bc8f663a7cab4031ebafa1bf3875705bfaf8466dc60ab52c00" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -3896,9 +3896,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck 0.5.0", "proc-macro2", @@ -3947,7 +3947,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5205,7 +5205,7 @@ dependencies = [ name = "depinfo" version = "0.7.3" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5409,7 +5409,7 @@ dependencies = [ "rand 0.9.2", "serde", "subsecond", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "warnings", @@ -5431,7 +5431,7 @@ dependencies = [ "ndk-sys 0.6.0+11769913", "percent-encoding", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "wasm-bindgen-futures", "web-sys", @@ -5477,6 +5477,7 @@ dependencies = [ "backtrace", "brotli", "built 0.8.0", + "bytes", "cargo-config2", "cargo-generate", "cargo_metadata", @@ -5565,7 +5566,7 @@ dependencies = [ "tauri-macos-sign", "tauri-utils", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "throbber-widgets-tui", "tokio", "tokio-stream", @@ -5668,7 +5669,7 @@ dependencies = [ name = "dioxus-component-manifest" version = "0.7.3" dependencies = [ - "schemars 1.2.0", + "schemars 1.2.1", "serde", "serde_json", ] @@ -5786,7 +5787,7 @@ dependencies = [ "slab", "subtle", "tao", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tray-icon", @@ -5808,7 +5809,7 @@ dependencies = [ "serde", "serde_json", "subsecond", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "tungstenite 0.27.0", @@ -5879,7 +5880,7 @@ dependencies = [ "serde", "serde_json", "sqlx", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tower-http", "uuid", @@ -5950,7 +5951,7 @@ dependencies = [ "serde_json", "serde_qs", "serde_urlencoded", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-tungstenite 0.27.0", @@ -5991,7 +5992,7 @@ dependencies = [ "parking_lot", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -6119,7 +6120,7 @@ dependencies = [ "serde", "serde_json", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -6433,7 +6434,7 @@ dependencies = [ "serde_json", "serde_qs", "subsecond", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-tungstenite 0.27.0", "tokio-util", @@ -6879,7 +6880,7 @@ dependencies = [ "const_panic", "encase_derive", "glam", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -7106,9 +7107,9 @@ dependencies = [ [[package]] name = "euclid" -version = "0.22.11" +version = "0.22.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9cdb4b747e485a12abb0e6566612956c7a1bafa3bdb8d682c5b6d403589e48" +checksum = "df61bf483e837f88d5c2291dcf55c67be7e676b3a51acc48db3a7b163b91ed63" dependencies = [ "num-traits", "serde", @@ -7316,21 +7317,20 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" @@ -7346,9 +7346,9 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b375d6465b98090a5f25b1c7703f3859783755aa9a80433b36e0379a3ec2f369" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -7633,7 +7633,7 @@ dependencies = [ "reqwest 0.12.28", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8065,9 +8065,9 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.3" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2b37e2f62729cdada11f0e6b3b6fe383c69c29fc619e391223e12856af308c" +checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" dependencies = [ "bitflags 2.9.4", "libc", @@ -8088,7 +8088,7 @@ dependencies = [ "gix-date", "gix-utils 0.2.0", "itoa", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow 0.7.14", ] @@ -8108,7 +8108,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-bom", "winnow 0.7.14", ] @@ -8123,7 +8123,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8135,7 +8135,7 @@ dependencies = [ "bstr", "itoa", "jiff", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8175,7 +8175,7 @@ dependencies = [ "gix-features 0.41.1", "gix-path", "gix-utils 0.2.0", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8189,7 +8189,7 @@ dependencies = [ "gix-features 0.42.1", "gix-path", "gix-utils 0.3.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8213,7 +8213,7 @@ dependencies = [ "faster-hex 0.9.0", "gix-features 0.41.1", "sha1-checked", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8225,7 +8225,7 @@ dependencies = [ "faster-hex 0.10.0", "gix-features 0.42.1", "sha1-checked", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8247,7 +8247,7 @@ checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" dependencies = [ "gix-tempfile", "gix-utils 0.3.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8267,7 +8267,7 @@ dependencies = [ "gix-validate 0.9.4", "itoa", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow 0.7.14", ] @@ -8280,7 +8280,7 @@ dependencies = [ "bstr", "gix-trace", "gix-validate 0.10.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8300,7 +8300,7 @@ dependencies = [ "gix-utils 0.2.0", "gix-validate 0.9.4", "memmap2", - "thiserror 2.0.17", + "thiserror 2.0.18", "winnow 0.7.14", ] @@ -8362,7 +8362,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" dependencies = [ "bstr", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8372,7 +8372,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b1e63a5b516e970a594f870ed4571a8fdcb8a344e7bd407a20db8bd61dbfde4" dependencies = [ "bstr", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -8462,7 +8462,7 @@ dependencies = [ "objc2 0.6.3", "objc2-app-kit 0.3.2", "once_cell", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.59.0", "x11rb", "xkeysym", @@ -8894,7 +8894,7 @@ dependencies = [ "pest_derive", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -9524,7 +9524,7 @@ dependencies = [ "tokio", "tokio-rustls 0.26.4", "tower-service", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -9558,14 +9558,13 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727805d60e7938b76b826a6ef209eb70eaa1812794f9424d4a4e2d740662df5f" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -9574,8 +9573,8 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", - "system-configuration 0.6.1", + "socket2 0.6.2", + "system-configuration 0.7.0", "tokio", "tower-layer", "tower-service", @@ -9585,9 +9584,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -10195,9 +10194,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67e8da4c49d6d9909fe03361f9b620f58898859f5c7aded68351e85e71ecf50" +checksum = "d89a5b5e10d5a9ad6e5d1f4bd58225f655d6fe9767575a5e8ac5a6fe64e04495" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -10210,9 +10209,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" +checksum = "ff7a39c8862fc1369215ccf0a8f12dd4598c7f6484704359f0351bd617034dbf" dependencies = [ "proc-macro2", "quote", @@ -10652,9 +10651,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" @@ -10860,9 +10859,9 @@ checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "local-ip-address" -version = "0.6.9" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92488bc8a0f99ee9f23577bdd06526d49657df8bd70504c61f812337cdad01ab" +checksum = "79ef8c257c92ade496781a32a581d43e3d512cf8ce714ecf04ea80f93ed0ff4a" dependencies = [ "libc", "neli", @@ -11367,7 +11366,7 @@ dependencies = [ "objc2-foundation 0.3.2", "once_cell", "png", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -11451,7 +11450,7 @@ dependencies = [ "pp-rs", "rustc-hash 1.1.0", "spirv", - "thiserror 2.0.17", + "thiserror 2.0.18", "unicode-ident", ] @@ -11467,7 +11466,7 @@ dependencies = [ "naga 26.0.0", "regex", "rustc-hash 1.1.0", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "unicode-ident", ] @@ -11483,9 +11482,9 @@ dependencies = [ [[package]] name = "nasm-rs" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34f676553b60ccbb76f41f9ae8f2428dac3f259ff8f1c2468a174778d06a1af9" +checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149" dependencies = [ "jobserver", "log", @@ -11607,9 +11606,9 @@ dependencies = [ [[package]] name = "neli" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e23bebbf3e157c402c4d5ee113233e5e0610cc27453b2f07eefce649c7365dcc" +checksum = "22f9786d56d972959e1408b6a93be6af13b9c1392036c5c1fafa08a1b0c6ee87" dependencies = [ "bitflags 2.9.4", "byteorder", @@ -11755,10 +11754,11 @@ dependencies = [ [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" dependencies = [ + "bitflags 2.9.4", "serde", ] @@ -11854,9 +11854,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-derive" @@ -12506,9 +12506,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-probe" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f50d9b3dabb09ecd771ad0aa242ca6894994c130308ca3d7684634df8037391" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -12925,9 +12925,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9eb05c21a464ea704b53158d358a31e6425db2f63a1a7312268b05fe2b75f7" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -12935,9 +12935,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f9dbced329c441fa79d80472764b1a2c7e57123553b8519b36663a2fb234ed" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -12945,9 +12945,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bb96d5051a78f44f43c8f712d8e810adb0ebf923fc9ed2655a7f66f63ba8ee5" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", @@ -12958,9 +12958,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.5" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "602113b5b5e8621770cfd490cfd90b9f84ab29bd2b0e49ad83eb6d186cef2365" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2", @@ -13197,7 +13197,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.1", + "siphasher 1.0.2", ] [[package]] @@ -13401,15 +13401,15 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "portable-atomic-util" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +checksum = "7a9db96d7fa8782dd8c15ce32ffe8680bbd1e978a43bf51a34d39483540495f5" dependencies = [ "portable-atomic", ] @@ -13610,9 +13610,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.105" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -13699,9 +13699,9 @@ checksum = "33cb294fe86a74cbcf50d4445b37da762029549ebeea341421c7c70370f86cac" [[package]] name = "psm" -version = "0.1.28" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11f2fedc3b7dafdc2851bc52f277377c5473d378859be234bc7ebb593144d01" +checksum = "1fa96cb91275ed31d6da3e983447320c4eb219ac180fa1679a0889ff32861e2d" dependencies = [ "ar_archive_writer", "cc", @@ -13813,8 +13813,8 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls 0.23.36", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -13835,7 +13835,7 @@ dependencies = [ "rustls 0.23.36", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -13850,16 +13850,16 @@ dependencies = [ "cfg_aliases 0.2.1", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.43" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -14242,7 +14242,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -14279,9 +14279,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -14290,9 +14290,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "regress" @@ -14438,7 +14438,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -14668,7 +14668,7 @@ dependencies = [ "pgp", "sha1", "sha2", - "thiserror 2.0.17", + "thiserror 2.0.18", "xz2", "zstd", ] @@ -14858,7 +14858,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ - "openssl-probe 0.2.0", + "openssl-probe 0.2.1", "rustls-pki-types", "schannel", "security-framework 3.5.1", @@ -15048,9 +15048,9 @@ dependencies = [ [[package]] name = "schemars" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54e910108742c57a770f492731f99be216a52fadd361b06c8fb59d74ccc267d2" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" dependencies = [ "dyn-clone", "ref-cast", @@ -15061,9 +15061,9 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4908ad288c5035a8eb12cfdf0d49270def0a268ee162b75eeee0f85d155a7c45" +checksum = "7d115b50f4aaeea07e79c1912f645c7513d81715d0420f8bc77a18c6260b307f" dependencies = [ "proc-macro2", "quote", @@ -15321,7 +15321,7 @@ dependencies = [ "rand 0.9.2", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "url", "uuid", @@ -15402,7 +15402,7 @@ checksum = "cc2215ce3e6a77550b80a1c37251b7d294febaf42e36e21b7b411e0bf54d540d" dependencies = [ "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "xml", ] @@ -15480,7 +15480,7 @@ checksum = "f3faaf9e727533a19351a43cc5a8de957372163c7d35cc48c90b75cdda13c352" dependencies = [ "percent-encoding", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -15536,7 +15536,7 @@ dependencies = [ "indexmap 1.9.3", "indexmap 2.13.0", "schemars 0.9.0", - "schemars 1.2.0", + "schemars 1.2.1", "serde", "serde_derive", "serde_json", @@ -15753,9 +15753,9 @@ checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "skrifa" @@ -15769,9 +15769,9 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "sledgehammer_bindgen" @@ -15907,9 +15907,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -16051,7 +16051,7 @@ dependencies = [ "serde_json", "sha2", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "tokio-stream", @@ -16139,7 +16139,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "uuid", @@ -16186,7 +16186,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "uuid", @@ -16213,7 +16213,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing", "url", @@ -16553,7 +16553,7 @@ dependencies = [ "memmap2", "serde", "subsecond-types", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", @@ -16622,7 +16622,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68c7541fff44b35860c1a7a47a7cadf3e4a304c457b58f9870d9706ece028afc" dependencies = [ "kurbo 0.11.3", - "siphasher 1.0.1", + "siphasher 1.0.2", ] [[package]] @@ -17194,9 +17194,9 @@ dependencies = [ [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags 2.9.4", "core-foundation 0.9.4", @@ -17384,7 +17384,7 @@ dependencies = [ "tauri-macos-sign", "tauri-utils", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "ureq", "url", @@ -17450,7 +17450,7 @@ dependencies = [ "serde-untagged", "serde_json", "serde_with", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.8.23", "url", "urlpattern", @@ -17528,11 +17528,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -17548,9 +17548,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -17589,9 +17589,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.45" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", @@ -17604,15 +17604,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e552d1249bf61ac2a52db88179fd0673def1e1ad8243a00d9ec9ed71fee3dd" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -17727,7 +17727,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "tracing", "windows-sys 0.61.2", @@ -18187,7 +18187,7 @@ dependencies = [ "objc2-foundation 0.3.2", "once_cell", "png", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -18209,9 +18209,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "trybuild" -version = "1.0.114" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17e807bff86d2a06b52bca4276746584a78375055b6e45843925ce2802b335" +checksum = "5f614c21bd3a61bad9501d75cbb7686f00386c806d7f456778432c25cf86948a" dependencies = [ "dissimilar", "glob", @@ -18281,7 +18281,7 @@ dependencies = [ "rand 0.9.2", "rustls 0.23.36", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] @@ -18298,7 +18298,7 @@ dependencies = [ "log", "rand 0.9.2", "sha1", - "thiserror 2.0.17", + "thiserror 2.0.18", "utf-8", ] @@ -18594,9 +18594,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "3.1.4" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +checksum = "fdc97a28575b85cfedf2a7e7d3cc64b3e11bd8ac766666318003abbacc7a21fc" dependencies = [ "base64 0.22.1", "log", @@ -18606,7 +18606,7 @@ dependencies = [ "socks", "ureq-proto", "utf-8", - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] @@ -18669,7 +18669,7 @@ dependencies = [ "roxmltree", "rustybuzz 0.20.1", "simplecss", - "siphasher 1.0.1", + "siphasher 1.0.2", "strict-num", "svgtypes", "tiny-skia-path", @@ -18705,9 +18705,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "ee48d38b119b0cd71fe4141b30f5ba9c7c5d9f4e7a3a8b4a674e4b6ef789976f" dependencies = [ "getrandom 0.3.4", "js-sys", @@ -18775,7 +18775,7 @@ dependencies = [ "png", "skrifa", "static_assertions", - "thiserror 2.0.17", + "thiserror 2.0.18", "vello_encoding", "vello_shaders", "wgpu 26.0.1", @@ -18828,7 +18828,7 @@ dependencies = [ "bytemuck", "log", "naga 26.0.0", - "thiserror 2.0.17", + "thiserror 2.0.18", "vello_encoding", ] @@ -19424,14 +19424,14 @@ version = "0.26.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" dependencies = [ - "webpki-roots 1.0.5", + "webpki-roots 1.0.6", ] [[package]] name = "webpki-roots" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] @@ -19467,7 +19467,7 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", "windows 0.61.3", "windows-core 0.61.2", ] @@ -19581,7 +19581,7 @@ dependencies = [ "raw-window-handle 0.6.2", "rustc-hash 1.1.0", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wgpu-core-deps-apple", "wgpu-core-deps-emscripten", "wgpu-core-deps-wasm", @@ -19711,7 +19711,7 @@ dependencies = [ "raw-window-handle 0.6.2", "renderdoc-sys", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "wasm-bindgen", "web-sys", "wgpu-types 26.0.0", @@ -19754,7 +19754,7 @@ dependencies = [ "js-sys", "log", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "web-sys", ] @@ -20625,7 +20625,7 @@ dependencies = [ "sha2", "soup3", "tao-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "webkit2gtk", "webkit2gtk-sys", @@ -20998,18 +20998,18 @@ checksum = "6df3dc4292935e51816d896edcd52aa30bc297907c26167fec31e2b0c6a32524" [[package]] name = "zerocopy" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.33" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -21116,7 +21116,7 @@ dependencies = [ "flate2", "indexmap 2.13.0", "memchr", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "zopfli", ] @@ -21154,20 +21154,20 @@ checksum = "dba6063ff82cdbd9a765add16d369abe81e520f836054e997c2db217ceca40c0" dependencies = [ "base64 0.22.1", "ed25519-dalek", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "zlib-rs" -version = "0.5.5" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40990edd51aae2c2b6907af74ffb635029d5788228222c4bb811e9351c0caad3" +checksum = "a7948af682ccbc3342b6e9420e8c51c1fe5d7bf7756002b4a3c6cabfe96a7e3c" [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" [[package]] name = "zopfli" diff --git a/Cargo.toml b/Cargo.toml index 3353ff0e3..541a52a00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -414,6 +414,13 @@ incremental = true [profile.dev.package.walrus] opt-level = 3 +# ensure we have adversarial setup for tls +[profile.dev.package.cross-tls-crate] +opt-level = 2 + +[profile.dev.package.cross-tls-crate-dylib] +opt-level = 2 + [profile.release-max-opt] inherits = "release" lto = true diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 793a5346f..28dd6ff2d 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -29,6 +29,7 @@ subsecond-types = { workspace = true } dioxus-cli-telemetry = { workspace = true } dioxus-component-manifest = { workspace = true } +bytes = { workspace = true } clap = { workspace = true, features = ["derive", "cargo"] } convert_case = { workspace = true } thiserror = { workspace = true } diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 79c2b9af2..3c93ebf5a 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,12 +1,14 @@ use crate::{ - serve::WebServer, verbosity_or_default, BuildArtifacts, BuildRequest, BuildStage, - BuilderUpdate, BundleFormat, ProgressRx, ProgressTx, Result, RustcArgs, StructuredOutput, + build::cache::ObjectCache, serve::WebServer, verbosity_or_default, BuildArtifacts, + BuildRequest, BuildStage, BuilderUpdate, BundleFormat, ProgressRx, ProgressTx, Result, + RustcArgs, StructuredOutput, }; use anyhow::{bail, Context, Error}; use dioxus_cli_opt::process_file_to; use futures_util::{future::OptionFuture, pin_mut, FutureExt}; use itertools::Itertools; use std::{ + collections::HashSet, env, time::{Duration, Instant, SystemTime}, }; @@ -103,6 +105,13 @@ pub(crate) struct AppBuilder { /// The debugger for the app - must be enabled with the `d` key pub(crate) pid: Option, + + /// Cumulative set of workspace crates modified since the last fat build. + /// Each patch includes objects from ALL crates in this set. + pub modified_crates: HashSet, + + /// Cache of the latest `.rcgu.o` files for each modified workspace crate. + pub object_cache: ObjectCache, } impl AppBuilder { @@ -159,6 +168,8 @@ impl AppBuilder { artifacts: None, patch_cache: None, pid: None, + modified_crates: HashSet::new(), + object_cache: ObjectCache::new(&request.session_cache_dir()), }) } @@ -319,7 +330,12 @@ impl AppBuilder { update } - pub(crate) fn patch_rebuild(&mut self, changed_files: Vec, build_id: BuildId) { + pub(crate) fn patch_rebuild( + &mut self, + changed_files: Vec, + changed_crates: Vec, + build_id: BuildId, + ) { // We need the rustc args from the original build to pass to the new build let Some(artifacts) = self.artifacts.as_ref().cloned() else { tracing::warn!( @@ -353,6 +369,38 @@ impl AppBuilder { .context("Failed to get patch cache") .unwrap(); + // Pre-compute the cumulative modified_crates set. Every patch includes objects from + // ALL crates modified since the fat build. We compute the full cascade closure here + // (while we have &mut self) so it doesn't need to be round-tripped through BuildArtifacts. + // + // Note: compile_workspace_deps() independently computes which crates to compile for THIS + // patch (starting from changed_crates + cascade). That serves a different purpose — it only + // compiles what changed now, not everything ever modified. Both use workspace_dependents_of + // for the BFS, so they stay in sync automatically. + let tip_crate_name = self.build.main_target.replace('-', "_"); + self.modified_crates.insert(tip_crate_name.clone()); + + // Add changed crates and their transitive workspace dependents (cascade). + let mut to_visit: Vec = changed_crates.clone(); + let mut visited = HashSet::new(); + while let Some(c) = to_visit.pop() { + if !visited.insert(c.clone()) { + continue; + } + self.modified_crates.insert(c.clone()); + for dep in self.build.workspace_dependents_of(&c) { + if dep != tip_crate_name && !visited.contains(&dep) { + to_visit.push(dep); + } + } + } + + tracing::debug!( + "Patch rebuild: changed_crates={:?}, modified_crates={:?}", + changed_crates, + self.modified_crates, + ); + // Abort all the ongoing builds, cleaning up any loose artifacts and waiting to cleanly exit self.abort_all(BuildStage::Restarting); self.build_task = tokio::spawn({ @@ -362,9 +410,12 @@ impl AppBuilder { tx: self.tx.clone(), mode: BuildMode::Thin { changed_files, - rustc_args: artifacts.direct_rustc, + changed_crates, + modified_crates: self.modified_crates.clone(), + workspace_rustc_args: artifacts.workspace_rustc_args, aslr_reference, cache, + object_cache: self.object_cache.clone(), }, }; async move { request.build(&ctx).await } @@ -378,6 +429,10 @@ impl AppBuilder { self.abort_all(BuildStage::Restarting); self.artifacts.take(); self.patch_cache.take(); + + // A full rebuild resets all accumulated hotpatch state — the fat binary is a clean baseline. + self.modified_crates.clear(); + self.object_cache = ObjectCache::new(&self.build.session_cache_dir()); self.build_task = tokio::spawn({ let request = self.build.clone(); let ctx = BuildContext { @@ -545,12 +600,21 @@ impl AppBuilder { )); } - // If there's any CARGO vars in the rustc_wrapper files, push those too - if let Ok(res) = std::fs::read_to_string(self.build.rustc_wrapper_args_file()) { - if let Ok(res) = serde_json::from_str::(&res) { - for (key, value) in res.envs { - if key.starts_with("CARGO_") { - envs.push((key, value)); + // If there's any CARGO vars in the rustc_wrapper files, push those too. + // Read from any per-crate args file in the directory (they all share the same CARGO_ envs). + if let Ok(entries) = std::fs::read_dir(self.build.rustc_wrapper_args_dir()) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_some_and(|e| e == "json") { + if let Ok(contents) = std::fs::read_to_string(&path) { + if let Ok(args) = serde_json::from_str::(&contents) { + for (key, value) in args.envs { + if key.starts_with("CARGO_") { + envs.push((key, value)); + } + } + break; // Only need one file for CARGO_ env vars + } } } } @@ -740,17 +804,21 @@ impl AppBuilder { tracing::info!( "Hot-patching: {NOTE_STYLE}{}{NOTE_STYLE:#} took {GLOW_STYLE}{:?}ms{GLOW_STYLE:#}", changed_file - .display() - .to_string() - .trim_start_matches(&self.build.crate_dir().display().to_string()), + .strip_prefix(self.build.workspace_dir()) + .unwrap_or(changed_file) + .display(), SystemTime::now() .duration_since(res.time_start) .unwrap() .as_millis() ); + // Commit this patch self.patches.push(jump_table.clone()); + // Sync the updated object cache back from the build artifacts. + self.object_cache = res.object_cache.clone(); + Ok(jump_table) } diff --git a/packages/cli/src/build/cache.rs b/packages/cli/src/build/cache.rs new file mode 100644 index 000000000..ac1cb9b06 --- /dev/null +++ b/packages/cli/src/build/cache.rs @@ -0,0 +1,119 @@ +//! Object file cache for workspace hotpatching. +//! +//! Maintains the latest `.rcgu.o` files for each crate in the cumulative `modified_crates` set. +//! Used for accumulated relinking: combining objects from all modified crates into a single +//! patch dylib. +//! +//! Objects are stored on disk under `session_cache_dir/object_cache/{crate_name}/` so they +//! persist across patches without holding file contents in memory. The session cache dir +//! lives in `/tmp/` and is cleaned up by the OS. +//! +//! - **Dep crates:** objects are extracted from their rlib in `target/deps/`. +//! - **Tip crate:** objects are copied from linker arg paths since incremental compilation +//! overwrites them in place. + +use std::collections::HashMap; +use std::io::Read; +use std::path::{Path, PathBuf}; + +/// Cache of compiled object files on disk, keyed by crate name. +/// +/// After each compilation, the cache is updated for the compiled crate by extracting +/// objects to `dir/{crate_name}/`. On relink, paths from all crates in `modified_crates` +/// are passed directly to the linker — no intermediate copy needed. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct ObjectCache { + /// Root directory: `session_cache_dir/object_cache/` + dir: PathBuf, + + /// crate_name -> object file paths on disk + objects: HashMap>, +} + +impl ObjectCache { + pub fn new(session_cache_dir: &Path) -> Self { + let dir = session_cache_dir.join("object_cache"); + Self { + dir, + objects: HashMap::new(), + } + } + + /// Extract `.rcgu.o` files from an rlib archive and write them to + /// `dir/{crate_name}/`. Replaces any previously cached objects for this crate. + pub fn cache_from_rlib(&mut self, crate_name: &str, rlib_path: &Path) -> anyhow::Result<()> { + let crate_dir = self.dir.join(crate_name); + // Clear previous objects for this crate + if crate_dir.exists() { + std::fs::remove_dir_all(&crate_dir)?; + } + std::fs::create_dir_all(&crate_dir)?; + + let rlib_contents = std::fs::read(rlib_path)?; + let mut reader = ar::Archive::new(std::io::Cursor::new(rlib_contents)); + let mut paths = Vec::new(); + + while let Some(Ok(mut entry)) = reader.next_entry() { + let name = std::str::from_utf8(entry.header().identifier()) + .unwrap_or_default() + .to_string(); + + // Skip rmeta and empty entries + if name.ends_with(".rmeta") || entry.header().size() == 0 { + continue; + } + + // Only keep object files + if !name.ends_with(".o") { + continue; + } + + let mut data = Vec::with_capacity(entry.header().size() as usize); + entry.read_to_end(&mut data)?; + + let obj_path = crate_dir.join(&name); + std::fs::write(&obj_path, &data)?; + paths.push(obj_path); + } + + self.objects.insert(crate_name.to_string(), paths); + Ok(()) + } + + /// Cache tip crate objects by copying them from their filesystem paths. + /// + /// Tip crate `.rcgu.o` files get overwritten on recompilation, so we copy + /// them into our cache directory for stable access. + pub fn cache_from_paths( + &mut self, + crate_name: &str, + object_paths: &[impl AsRef], + ) -> anyhow::Result<()> { + let crate_dir = self.dir.join(crate_name); + if crate_dir.exists() { + std::fs::remove_dir_all(&crate_dir)?; + } + std::fs::create_dir_all(&crate_dir)?; + + let mut paths = Vec::with_capacity(object_paths.len()); + + for path in object_paths { + let path = path.as_ref(); + let name = path + .file_name() + .and_then(|n| n.to_str()) + .unwrap_or_default(); + let dest = crate_dir.join(name); + std::fs::copy(path, &dest)?; + paths.push(dest); + } + + self.objects.insert(crate_name.to_string(), paths); + Ok(()) + } + + /// Get cached object file paths for a crate. + pub fn get(&self, crate_name: &str) -> Option<&Vec> { + self.objects.get(crate_name) + } +} diff --git a/packages/cli/src/build/mod.rs b/packages/cli/src/build/mod.rs index c1cf1fcfa..f2a89ec40 100644 --- a/packages/cli/src/build/mod.rs +++ b/packages/cli/src/build/mod.rs @@ -10,6 +10,7 @@ mod assets; mod builder; +mod cache; mod context; mod manifest; mod patch; @@ -19,6 +20,7 @@ mod tools; pub(crate) use assets::*; pub(crate) use builder::*; +pub(crate) use cache::*; pub(crate) use context::*; pub(crate) use manifest::*; pub(crate) use patch::*; diff --git a/packages/cli/src/build/patch.rs b/packages/cli/src/build/patch.rs index 53bad0305..608a63836 100644 --- a/packages/cli/src/build/patch.rs +++ b/packages/cli/src/build/patch.rs @@ -4,7 +4,7 @@ use object::{ macho::{self}, read::File, write::{MachOBuildVersion, SectionId, StandardSection, Symbol, SymbolId, SymbolSection}, - Endianness, Object, ObjectSymbol, SymbolFlags, SymbolKind, SymbolScope, + Endianness, Object, ObjectSection, ObjectSymbol, SymbolFlags, SymbolKind, SymbolScope, }; use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use std::{ @@ -76,6 +76,16 @@ pub struct HotpatchModuleCache { // ... native stuff pub symbol_table: HashMap, + + /// Contents of the .tdata section from the original binary (TLS initialization image). + /// Used to provide correct init data for TLS symbol stubs instead of garbage addresses. + pub tls_init_data: Vec, + + /// Map from `$tlv$init` symbol name to (offset_in_tdata, computed_size). + /// On macOS, Mach-O nlist doesn't carry symbol sizes, so we compute them from + /// adjacent symbol addresses in the `__thread_data` section. This lets us provide + /// correctly-sized TLS init data in stubs instead of defaulting to pointer_width. + pub tls_init_sizes: HashMap, } pub struct CachedSymbol { @@ -277,10 +287,55 @@ impl HotpatchModuleCache { )) }) .collect::>(); + + // Extract TLS initialization data and section metadata. + // This is used to correctly initialize TLS symbols in the stub + // instead of writing bogus absolute addresses into .tdata. + let tls_section = obj + .sections() + .find(|s| matches!(s.name(), Ok(".tdata" | "__thread_data"))); + + let tls_init_data = tls_section + .as_ref() + .and_then(|s| s.data().ok()) + .unwrap_or(&[]) + .to_vec(); + + // Build TLS init size map for macOS. Mach-O nlist doesn't carry symbol + // sizes, so we compute them from adjacent symbols in __thread_data. + // LLVM/rustc names init data symbols as `FOO$tlv$init` in __thread_data. + let tls_data_addr = tls_section.as_ref().map(|s| s.address()).unwrap_or(0); + let tls_data_size = tls_section.as_ref().map(|s| s.size()).unwrap_or(0); + let tls_section_index = tls_section.as_ref().map(|s| s.index()); + + let mut tls_init_syms: Vec<(u64, String)> = Vec::new(); + for sym in obj.symbols() { + if let (Some(section_idx), Ok(sname)) = (sym.section_index(), sym.name()) { + if Some(section_idx) == tls_section_index { + let offset = sym.address().saturating_sub(tls_data_addr); + tls_init_syms.push((offset, sname.to_string())); + } + } + } + tls_init_syms.sort_by_key(|(addr, _)| *addr); + tls_init_syms.dedup_by_key(|(addr, _)| *addr); + + let mut tls_init_sizes: HashMap = HashMap::new(); + for (i, (offset, sname)) in tls_init_syms.iter().enumerate() { + let size = if i + 1 < tls_init_syms.len() { + tls_init_syms[i + 1].0 - offset + } else { + tls_data_size.saturating_sub(*offset) + }; + tls_init_sizes.insert(sname.clone(), (*offset, size)); + } + HotpatchModuleCache { symbol_table, path: original.to_path_buf(), old_bytes, + tls_init_data, + tls_init_sizes, ..Default::default() } } @@ -1106,36 +1161,61 @@ pub fn create_undefined_symbol_stub( PointerWidth::U64 => 8, }; - let size = if sym.size == 0 { - pointer_width - } else { - sym.size - }; + // Resolve the TLS init data offset and size. + // + // On ELF: sym.address IS the TLS offset and sym.size is the data size. + // On Mach-O: sym.address points to __thread_vars (TLV descriptor), NOT + // __thread_data. Mach-O nlist has no size field (always 0). We look up + // the corresponding $tlv$init symbol (LLVM convention) to get the real + // offset and size within __thread_data. + // + // Note: each patch gets its own TLS copy (not shared with the main exe). + // TLS variables reset to their initial value on patch. + // Use the full name (with Mach-O `_` prefix) since tls_init_sizes + // keys come from the same symbol table and include the prefix. + let init_key = format!("{}$tlv$init", name); + let (tls_offset, size) = + if let Some(&(offset, size)) = cache.tls_init_sizes.get(&init_key) { + // macOS: found the $tlv$init symbol with correct offset and size + (offset, size) + } else if sym.size > 0 { + // ELF: sym.address is the TLS offset, sym.size is the data size + (sym.address, sym.size) + } else if !cache.tls_init_sizes.is_empty() { + // macOS fallback: $tlv$init not found but map isn't empty (binary + // might be partially stripped). Use entire tdata as upper bound. + (0, cache.tls_init_data.len() as u64) + } else { + // Last resort (ELF with size=0): use pointer width + (sym.address, pointer_width) + }; let align = size.min(pointer_width).next_power_of_two(); - let mut init = vec![0u8; size as usize]; - // write the contents of the symbol to the init vec - init.iter_mut() - .zip(match triple.endianness() { - Ok(target_lexicon::Endianness::Little) => abs_addr.to_le_bytes(), - Ok(target_lexicon::Endianness::Big) => abs_addr.to_be_bytes(), - _ => return Err(PatchError::UnsupportedPlatform(triple.to_string())), - }) - .for_each(|(b, v)| *b = v); + let start = tls_offset as usize; + let end = start + size as usize; + let init = if end <= cache.tls_init_data.len() { + cache.tls_init_data[start..end].to_vec() + } else { + // Beyond .tdata bounds (.tbss) or Mach-O fallback: zero-init + vec![0u8; size as usize] + }; - let offset = obj.append_section_data(tls_section, &init, align); - - obj.add_symbol(Symbol { + // Use add_symbol_data() so the object crate's Mach-O writer auto-creates + // __thread_vars TLV descriptors (via macho_add_thread_var). Without this, + // the symbol stays in __thread_data and the runtime misinterprets raw init + // bytes as a TLV descriptor — first 8 bytes become the thunk pointer. + let sym_id = obj.add_symbol(Symbol { name: name.as_bytes()[name_offset..].to_vec(), - value: offset, // offset inside .tdata - size, + value: 0, + size: 0, scope: SymbolScope::Linkage, kind: SymbolKind::Tls, weak: false, - section: SymbolSection::Section(tls_section), - flags: SymbolFlags::None, // ignore for these stubs + section: SymbolSection::Undefined, + flags: SymbolFlags::None, }); + obj.add_symbol_data(sym_id, tls_section, &init, align); } // We just assume all non-text symbols are data (globals, statics, etc) diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index ef073abf6..e50990ddc 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -321,8 +321,8 @@ use crate::{ AndroidTools, AppManifest, BuildContext, BuildId, BundleFormat, DioxusConfig, Error, - LinkAction, LinkerFlavor, Platform, Renderer, Result, RustcArgs, TargetArgs, TraceSrc, - WasmBindgen, WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR, + LinkAction, LinkerFlavor, ObjectCache, Platform, Renderer, Result, RustcArgs, TargetArgs, + TraceSrc, WasmBindgen, WasmOptConfig, Workspace, DX_RUSTC_WRAPPER_ENV_VAR, }; use anyhow::{bail, Context}; use cargo_metadata::diagnostic::Diagnostic; @@ -339,7 +339,7 @@ use rayon::prelude::{IntoParallelRefIterator, ParallelIterator}; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, ffi::OsString}; use std::{ - collections::{BTreeMap, HashSet}, + collections::{BTreeMap, HashMap, HashSet}, io::Write, path::{Path, PathBuf}, process::Stdio, @@ -419,6 +419,7 @@ pub(crate) struct BuildRequest { /// - Thin: A "thin" build that dynamically links against the dependencies produced by the "fat" build. /// This is generated by calling rustc *directly* and might be more fragile to construct, but /// generates *much* faster than a regular base or fat build. +#[allow(clippy::large_enum_variant)] #[derive(Clone, Debug, PartialEq)] pub enum BuildMode { /// A normal build generated using `cargo rustc` @@ -433,9 +434,26 @@ pub enum BuildMode { /// A "thin" build generated with `rustc` directly and dx as a custom linker Thin { - rustc_args: RustcArgs, + /// List of changed files causing this rebuild. Mostly used for diagnostics changed_files: Vec, + + /// Which workspace crates had source file changes in this edit. + changed_crates: Vec, + + /// The ASLR slide of the running program, used to hardcode symbol jumps aslr_reference: u64, + + /// The captured RustcArgs for every crate in the workspace, collected by RUSTC_WORKSPACE_WRAPPER + /// This is used for replaying rustc invocations for workspace hotpatching + workspace_rustc_args: HashMap, + + /// Cumulative set of all workspace crates modified since the fat build. + modified_crates: HashSet, + + /// Cache of compiled objects from previous thin builds, used by future re-linking + object_cache: ObjectCache, + + /// Cache of initial binary parsing which speeds up stub creation cache: Arc, }, } @@ -450,7 +468,7 @@ pub enum BuildMode { pub struct BuildArtifacts { pub(crate) root_dir: PathBuf, pub(crate) exe: PathBuf, - pub(crate) direct_rustc: RustcArgs, + pub(crate) workspace_rustc_args: HashMap, pub(crate) time_start: SystemTime, pub(crate) time_end: SystemTime, pub(crate) assets: AssetManifest, @@ -458,6 +476,7 @@ pub struct BuildArtifacts { pub(crate) patch_cache: Option>, pub(crate) depinfo: RustcDepInfo, pub(crate) build_id: BuildId, + pub(crate) object_cache: ObjectCache, } impl BuildRequest { @@ -1018,7 +1037,7 @@ impl BuildRequest { // Create the session cache directory let cache_dir = self.session_cache_dir(); _ = std::fs::create_dir_all(&cache_dir); - _ = std::fs::File::create_new(self.rustc_wrapper_args_file()); + _ = std::fs::create_dir_all(self.rustc_wrapper_args_dir()); _ = std::fs::File::create_new(self.link_err_file()); _ = std::fs::File::create_new(self.link_args_file()); _ = std::fs::File::create_new(self.windows_command_file()); @@ -1054,7 +1073,8 @@ impl BuildRequest { // We need to make sure to not react to this though, so the filemap must cache it _ = self.bust_fingerprint(ctx); - // Run the cargo build to produce our artifacts + // Run the cargo build to produce our artifacts. + // For thin builds this also pre-compiles workspace dep crates before the tip. let mut artifacts = self.cargo_build(ctx).await?; // Write the build artifacts to the bundle on the disk @@ -1062,10 +1082,10 @@ impl BuildRequest { BuildMode::Thin { aslr_reference, cache, - rustc_args, + modified_crates, .. } => { - self.write_patch(ctx, *aslr_reference, &mut artifacts, cache, rustc_args) + self.write_patch(ctx, *aslr_reference, &mut artifacts, cache, modified_crates) .await?; } @@ -1076,7 +1096,7 @@ impl BuildRequest { self.write_executable(ctx, &artifacts.exe, &mut artifacts.assets) .await .context("Failed to write executable")?; - self.write_frameworks(ctx, &artifacts.direct_rustc) + self.write_frameworks(ctx, &artifacts) .await .context("Failed to write frameworks")?; self.write_assets(ctx, &artifacts.assets) @@ -1125,7 +1145,6 @@ impl BuildRequest { Ok(artifacts) } - /// Run the cargo build by assembling the build command and executing it. /// /// This method needs to be very careful with processing output since errors being swallowed will @@ -1133,6 +1152,10 @@ impl BuildRequest { async fn cargo_build(&self, ctx: &BuildContext) -> Result { let time_start = SystemTime::now(); + // For thin builds, compile workspace dep crates before the tip. + // This updates dep rlibs on disk so cargo links the tip against fresh code. + let object_cache = self.compile_workspace_deps(ctx).await?; + // Extract the unit count of the crate graph so build_cargo has more accurate data // "Thin" builds only build the final exe, so we only need to build one crate let crate_count = match ctx.mode { @@ -1246,14 +1269,33 @@ impl BuildRequest { } } - // Accumulate the rustc args from the wrapper, if they exist and can be parsed. - let mut direct_rustc = RustcArgs::default(); - if let Ok(res) = std::fs::read_to_string(self.rustc_wrapper_args_file()) { - if let Ok(res) = serde_json::from_str(&res) { - direct_rustc = res; + // Load per-crate rustc args from the wrapper directory. + // Each workspace crate compiled through the wrapper has its own JSON file: + // "{crate_name}.lib.json" (key: "{crate_name}.lib") for lib targets and + // "{crate_name}.bin.json" (key: "{crate_name}.bin") for bin targets. + let mut workspace_rustc_args = HashMap::new(); + let args_dir = self.rustc_wrapper_args_dir(); + if let Ok(entries) = std::fs::read_dir(&args_dir) { + for entry in entries.flatten() { + let path = entry.path(); + if path.extension().is_some_and(|e| e == "json") { + if let Ok(contents) = std::fs::read_to_string(&path) { + if let Ok(args) = serde_json::from_str::(&contents) { + if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) { + workspace_rustc_args.insert(stem.to_string(), args); + } + } + } + } } } + tracing::trace!( + "Loaded workspace rustc args from {}: keys={:?}", + args_dir.display(), + workspace_rustc_args.keys().collect::>(), + ); + // If there's any warnings from the linker, we should print them out if let Ok(linker_warnings) = std::fs::read_to_string(self.link_err_file()) { if !linker_warnings.is_empty() { @@ -1265,12 +1307,16 @@ impl BuildRequest { } } - // Collect the linker args from the and update the rustc args - direct_rustc.link_args = std::fs::read_to_string(self.link_args_file()) - .context("Failed to read link args from file")? - .lines() - .map(|s| s.to_string()) - .collect::>(); + // Collect the linker args and attach them to the tip crate's bin entry + let tip_crate_name = self.tip_crate_name(); + let tip_bin_key = format!("{tip_crate_name}.bin"); + if let Some(tip_args) = workspace_rustc_args.get_mut(&tip_bin_key) { + tip_args.link_args = std::fs::read_to_string(self.link_args_file()) + .context("Failed to read link args from file")? + .lines() + .map(|s| s.to_string()) + .collect(); + } let exe = output_location.context("Cargo build failed - no output location. Toggle tracing mode (press `t`) for more information.")?; @@ -1278,7 +1324,14 @@ impl BuildRequest { if matches!(ctx.mode, BuildMode::Fat) { ctx.status_starting_link(); let link_start = SystemTime::now(); - self.run_fat_link(&exe, &direct_rustc).await?; + self.run_fat_link( + &exe, + &workspace_rustc_args + .get(&tip_bin_key) + .cloned() + .unwrap_or_default(), + ) + .await?; tracing::debug!( "Fat linking completed in {}us", SystemTime::now() @@ -1302,7 +1355,7 @@ impl BuildRequest { Ok(BuildArtifacts { time_end, exe, - direct_rustc, + workspace_rustc_args, time_start, assets, mode, @@ -1310,9 +1363,122 @@ impl BuildRequest { root_dir: self.root_dir(), patch_cache: None, build_id: ctx.build_id, + object_cache, }) } + /// For thin builds, compile workspace dep crates BEFORE the tip crate. + /// + /// This updates dep rlibs on disk so cargo links the tip against fresh code. Handles cascade + /// (recompiling workspace dependents for SVH consistency) and lib+bin tip targets. + /// + /// Returns the updated `ObjectCache` — defaulting to empty for non-thin builds. + async fn compile_workspace_deps(&self, ctx: &BuildContext) -> Result { + let BuildMode::Thin { + workspace_rustc_args, + changed_crates, + object_cache, + .. + } = &ctx.mode + else { + return Ok(ObjectCache::new(&self.session_cache_dir())); + }; + + let tip_name = self.tip_crate_name(); + let mut object_cache = object_cache.clone(); + + // Compile workspace dep crates with cascade. Start with the explicitly changed dep + // crates (already in leaf-first order from handle_file_change). As we compile each, + // add the crate's workspace dependents so their rlibs have consistent SVH references. + let mut crates_to_compile: Vec = changed_crates + .iter() + .filter(|c| *c != &tip_name) + .cloned() + .collect(); + let mut compiled = HashSet::new(); + let mut idx = 0; + + while idx < crates_to_compile.len() { + let crate_name = crates_to_compile[idx].clone(); + idx += 1; + + if !compiled.insert(crate_name.clone()) || crate_name == tip_name { + continue; + } + + let Some(rustc_args) = workspace_rustc_args.get(&format!("{crate_name}.lib")) else { + tracing::warn!("No captured rustc args for workspace crate {crate_name}, skipping"); + continue; + }; + + tracing::debug!("Compiling workspace dep crate: {crate_name}"); + self.compile_dep_crate(&crate_name, rustc_args) + .await + .with_context(|| format!("Failed to compile workspace dep crate '{crate_name}'"))?; + + if let Some(rlib_path) = self.find_rlib_for_crate(&crate_name, rustc_args) { + if let Err(e) = object_cache.cache_from_rlib(&crate_name, &rlib_path) { + tracing::warn!("Failed to cache objects from rlib for {crate_name}: {e}"); + } + } + + for dependent in self.workspace_dependents_of(&crate_name) { + if dependent != tip_name && !compiled.contains(&dependent) { + tracing::debug!( + "Cascade: recompiling {dependent} (depends on recompiled {crate_name})" + ); + crates_to_compile.push(dependent); + } + } + } + + // If the tip crate has a lib target (src/lib.rs + src/main.rs), compile it + // before the bin target so the bin links against the fresh lib rlib. + let lib_key = format!("{tip_name}.lib"); + if let Some(lib_args) = workspace_rustc_args.get(&lib_key) { + let rlib_pre = self.find_rlib_for_crate(&tip_name, lib_args); + let pre_modified = rlib_pre + .as_ref() + .and_then(|p| std::fs::metadata(p).ok()) + .and_then(|m| m.modified().ok()); + + tracing::info!("Compiling tip lib target: {lib_key}"); + if let Err(e) = self.compile_dep_crate(&tip_name, lib_args).await { + tracing::warn!("Failed to compile tip lib target: {e}"); + } else if let Some(rlib_path) = self.find_rlib_for_crate(&tip_name, lib_args) { + let post_modified = std::fs::metadata(&rlib_path) + .ok() + .and_then(|m| m.modified().ok()); + let rlib_changed = match (pre_modified, post_modified) { + (Some(pre), Some(post)) => post > pre, + _ => true, + }; + tracing::info!( + "Found lib rlib at: {} (modified={})", + rlib_path.display(), + rlib_changed, + ); + + match object_cache.cache_from_rlib(&lib_key, &rlib_path) { + Ok(()) => { + let count = object_cache.get(&lib_key).map(|v| v.len()).unwrap_or(0); + tracing::info!("Cached {count} objects from tip lib rlib"); + } + Err(e) => tracing::warn!("Failed to cache tip lib objects: {e}"), + } + } else { + tracing::warn!("Could not find rlib for tip lib target {tip_name}"); + } + } else { + tracing::debug!( + "No lib target for tip crate (key '{lib_key}' not in workspace_rustc_args, keys={:?})", + workspace_rustc_args.keys().collect::>() + ); + } + + Ok(object_cache) + } + /// Collect the assets from the final executable and modify the binary in place to point to the right /// hashed asset location. async fn collect_assets(&self, exe: &Path, ctx: &BuildContext) -> Result { @@ -1421,9 +1587,20 @@ impl BuildRequest { Ok(()) } - async fn write_frameworks(&self, _ctx: &BuildContext, direct_rustc: &RustcArgs) -> Result<()> { + async fn write_frameworks( + &self, + _ctx: &BuildContext, + artifacts: &BuildArtifacts, + ) -> Result<()> { let framework_dir = self.frameworks_folder(); + // We use the rustc for the tip crate `main.rs` because that's where the linking happens + let direct_rustc = artifacts + .workspace_rustc_args + .get(&format!("{}.bin", self.tip_crate_name())) + .cloned() + .unwrap_or_default(); + // We have some prebuilt stuff that needs to be copied into the framework dir let openssl_dir = AndroidTools::openssl_lib_dir(&self.triple); let openssl_dir_disp = openssl_dir.display().to_string(); @@ -1633,17 +1810,66 @@ impl BuildRequest { /// manually do any linking. /// /// We also run some post processing steps here, like extracting out any new assets. + /// + /// `extra_objects` contains additional object file paths from compiled workspace dep crates + /// that should be included in the patch dylib. These are combined with the tip crate's + /// `.rcgu.o` files extracted from linker args, creating a self-contained patch. async fn write_patch( &self, ctx: &BuildContext, aslr_reference: u64, artifacts: &mut BuildArtifacts, cache: &Arc, - rustc_args: &RustcArgs, + modified_crates: &HashSet, ) -> Result<()> { ctx.status_hotpatching(); - let args = artifacts.direct_rustc.link_args.clone(); + let tip_name = self.tip_crate_name(); + + // Cache tip crate objects from the FRESH linker args (from the just-completed + // thin build, not the stale ones from ctx.mode's fat build). + let tip_bin_key = format!("{tip_name}.bin"); + let args = artifacts + .workspace_rustc_args + .get(&tip_bin_key) + .cloned() + .with_context(|| { + format!( + "Missing rustc args for tip bin target '{tip_bin_key}' \ + (available keys: {:?})", + artifacts.workspace_rustc_args.keys().collect::>() + ) + })?; + + // Collect objs from tip and re-cache them in the obj cache map + let tip_object_paths: Vec = args + .link_args + .iter() + .filter(|arg| arg.ends_with(".rcgu.o")) + .map(PathBuf::from) + .collect(); + if !tip_object_paths.is_empty() { + artifacts + .object_cache + .cache_from_paths(&tip_name, &tip_object_paths) + .context("Failed to cache objs during patch")?; + } + + // Collect cached object paths from all modified dep crates. + // Objects are already on disk in the object cache directory. + // These must NOT be deleted after linking — they persist across patches. + let mut cached_objects: Vec = Vec::new(); + for dep_name in modified_crates.iter().filter(|c| *c != &tip_name) { + if let Some(paths) = artifacts.object_cache.get(dep_name) { + cached_objects.extend(paths.iter().cloned()); + } + } + + // If the tip has a lib target (lib+bin crate), include its cached objects too. + let lib_key = format!("{tip_name}.lib"); + if let Some(paths) = artifacts.object_cache.get(&lib_key) { + cached_objects.extend(paths.iter().cloned()); + } // Extract out the incremental object files. // @@ -1698,12 +1924,21 @@ impl BuildRequest { // -Wl,-all_load // ``` let mut dylibs = vec![]; - let mut object_files = args + + // Tip objects from link_args are temps — safe to delete after linking. + let temp_objects: Vec = args + .link_args .iter() .filter(|arg| arg.ends_with(".rcgu.o")) .sorted() .map(PathBuf::from) - .collect::>(); + .collect(); + + // Merge both sets for the linker. + let mut object_files: Vec = + Vec::with_capacity(cached_objects.len() + temp_objects.len()); + object_files.append(&mut cached_objects); + object_files.extend(temp_objects.iter().cloned()); // On non-wasm platforms, we generate a special shim object file which converts symbols from // fat binary into direct addresses from the running process. @@ -1734,7 +1969,7 @@ impl BuildRequest { // Add the dylibs/sos to the linker args // Make sure to use the one in the bundle, not the ones in the target dir or system. - for arg in &rustc_args.link_args { + for arg in &args.link_args { if arg.ends_with(".dylib") || arg.ends_with(".so") { let path = PathBuf::from(arg); dylibs.push(self.frameworks_folder().join(path.file_name().unwrap())); @@ -1755,7 +1990,7 @@ impl BuildRequest { let mut out_args: Vec = vec![]; out_args.extend(object_files.iter().map(Into::into)); out_args.extend(dylibs.iter().map(Into::into)); - out_args.extend(self.thin_link_args(&args)?.iter().map(Into::into)); + out_args.extend(self.thin_link_args(&args.link_args)?.iter().map(Into::into)); out_args.extend(out_arg.iter().map(Into::into)); if cfg!(windows) { @@ -1769,7 +2004,7 @@ impl BuildRequest { } // Add more search paths for the linker - let mut command_envs = rustc_args.envs.clone(); + let mut command_envs = args.envs.clone(); // On linux, we need to set a more complete PATH for the linker to find its libraries if cfg!(target_os = "linux") { @@ -1808,8 +2043,8 @@ impl BuildRequest { // // Fortunately, this binary exists in two places - the deps dir and the target out dir. We // can just remove the one in the deps dir and the problem goes away. - if let Some(idx) = args.iter().position(|arg| *arg == "-o") { - _ = std::fs::remove_file(PathBuf::from(args[idx + 1].as_str())); + if let Some(idx) = args.link_args.iter().position(|arg| *arg == "-o") { + _ = std::fs::remove_file(PathBuf::from(args.link_args[idx + 1].as_str())); } // Now extract the assets from the fat binary @@ -1821,9 +2056,9 @@ impl BuildRequest { self.write_index_html(&artifacts.assets) .context("Failed to write index.html")?; - // Clean up the temps manually - // todo: we might want to keep them around for debugging purposes - for file in object_files { + // Clean up temp object files (tip incremental objects + stub.o). + // Cached dep objects in object_cache/ are NOT deleted — they persist across patches. + for file in &temp_objects { _ = std::fs::remove_file(file); } @@ -2509,7 +2744,14 @@ impl BuildRequest { // ends up doing some recursive nonsense and dx is trying to link instead of compiling. // // todo: maybe rustc needs to be found on the FS instead of using the one in the path? - BuildMode::Thin { rustc_args, .. } => { + BuildMode::Thin { + workspace_rustc_args, + .. + } => { + let rustc_args = workspace_rustc_args + .get(&format!("{}.bin", self.tip_crate_name())) + .context("Missing rustc args for tip crate")?; + let mut cmd = Command::new("rustc"); cmd.current_dir(self.workspace_dir()); cmd.env_clear(); @@ -2533,13 +2775,11 @@ impl BuildRequest { Ok(cmd) } - // For Base and Fat builds, we use a regular cargo setup, but we might need to intercept - // rustc itself in case we're hot-patching and need a reliable rustc environment to - // continuously recompile the top-level crate with. + // For Base and Fat builds, we use a regular cargo setup, but we intercept rustc for + // workspace member crates to capture their args/envs for hot-patching. // - // In the future, when we support hot-patching *all* workspace crates, we will need to - // make use of the RUSTC_WORKSPACE_WRAPPER environment variable instead of RUSTC_WRAPPER - // and then keep track of env and args on a per-crate basis. + // We use RUSTC_WORKSPACE_WRAPPER which wraps only workspace member crates, letting us + // capture per-crate args without interfering with external dependency compilation. // // We've also had a number of issues with incorrect canonicalization when passing paths // through envs on windows, hence the frequent use of dunce::canonicalize. @@ -2566,15 +2806,18 @@ impl BuildRequest { .envs(env.iter().map(|(k, v)| (k.as_ref(), v))); if matches!(build_mode, BuildMode::Fat | BuildMode::Base { run: true }) { + let args_dir = self.rustc_wrapper_args_dir(); + std::fs::create_dir_all(&args_dir) + .context("Failed to create rustc wrapper args directory")?; cmd.env( DX_RUSTC_WRAPPER_ENV_VAR, - dunce::canonicalize(self.rustc_wrapper_args_file()) - .context("Failed to canonicalize rustc wrapper args file")? + dunce::canonicalize(&args_dir) + .context("Failed to canonicalize rustc wrapper args dir")? .display() .to_string(), ); cmd.env( - "RUSTC_WRAPPER", + "RUSTC_WORKSPACE_WRAPPER", Workspace::path_to_dx()?.display().to_string(), ); } @@ -3488,8 +3731,13 @@ impl BuildRequest { self.session_cache_dir.join(self.bundle.to_string()) } - pub(crate) fn rustc_wrapper_args_file(&self) -> PathBuf { - self.session_cache_dir().join("rustc_wrapper_args.txt") + pub(crate) fn rustc_wrapper_args_dir(&self) -> PathBuf { + self.session_cache_dir().join("rustc_wrapper_args") + } + + /// The crate name that rustc uses for the tip crate (hyphens replaced with underscores). + fn tip_crate_name(&self) -> String { + self.main_target.replace('-', "_") } fn link_err_file(&self) -> PathBuf { @@ -4772,38 +5020,202 @@ __wbg_init({{module_or_path: "/{}/{wasm_path}"}}).then((wasm) => {{ /// Blow away the fingerprint for this package, forcing rustc to recompile it. /// /// This prevents rustc from using the cached version of the binary, which can cause issues + /// Find workspace crates that directly depend on the given crate. + /// + /// Returns underscore-normalized crate names of workspace members that have `crate_name` + /// as a dependency. Used for cascade detection — when a dep's public symbols change, + /// its dependents need recompilation too. + pub(crate) fn workspace_dependents_of(&self, crate_name: &str) -> Vec { + let krates = &self.workspace.krates; + + // Find the NodeId for the target crate + let target_nid = krates.workspace_members().find_map(|member| { + if let krates::Node::Krate { id, krate, .. } = member { + if krate.name.replace('-', "_") == crate_name { + return krates.nid_for_kid(id); + } + } + None + }); + + let Some(target_nid) = target_nid else { + return Vec::new(); + }; + + // Use krates' direct_dependents to find reverse deps, filter to workspace members + let workspace_names: HashSet = krates + .workspace_members() + .filter_map(|m| { + if let krates::Node::Krate { krate, .. } = m { + Some(krate.name.replace('-', "_")) + } else { + None + } + }) + .collect(); + + krates + .direct_dependents(target_nid) + .into_iter() + .filter_map(|dep| { + let name = dep.krate.name.replace('-', "_"); + if workspace_names.contains(&name) { + Some(name) + } else { + None + } + }) + .collect() + } + + /// Compile a workspace dependency crate directly with `rustc` using its captured args. + /// + /// This produces an updated rlib at the same path cargo originally wrote to. + /// Used during thin builds to recompile changed workspace deps before the tip crate. + async fn compile_dep_crate(&self, crate_name: &str, rustc_args: &RustcArgs) -> Result<()> { + let mut cmd = Command::new("rustc"); + cmd.current_dir(self.workspace_dir()); + cmd.env_clear(); + + // Skip args[0] which is the rustc binary path captured by the wrapper + cmd.args(rustc_args.args[1..].iter()); + + // Restore the captured environment, filtering out wrapper env vars and + // stale cargo jobserver vars to prevent recursive invocation and warnings. + let filtered_env_keys = [ + "RUSTC_WORKSPACE_WRAPPER", + "RUSTC_WRAPPER", + DX_RUSTC_WRAPPER_ENV_VAR, + "CARGO_MAKEFLAGS", + "MAKEFLAGS", + ]; + cmd.envs( + rustc_args + .envs + .iter() + .filter(|(k, _)| !filtered_env_keys.contains(&k.as_str())) + .cloned(), + ); + + let output = cmd.output().await?; + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr); + bail!("Failed to compile workspace dep crate '{crate_name}':\n{stderr}"); + } + + Ok(()) + } + + /// Find the rlib path for a workspace crate from its captured rustc args. + /// + /// Extracts `--out-dir` and `-C extra-filename` from the args to construct the exact + /// rlib filename. This is important because multiple rlibs for the same crate can coexist + /// in the deps directory (e.g., from different dx builds that produce different `-C metadata`), + /// and globbing would return an arbitrary one. + fn find_rlib_for_crate(&self, crate_name: &str, rustc_args: &RustcArgs) -> Option { + // Extract --out-dir from the captured args + let out_dir = rustc_args + .args + .iter() + .zip(rustc_args.args.iter().skip(1)) + .find(|(flag, _)| *flag == "--out-dir") + .map(|(_, dir)| PathBuf::from(dir))?; + + // Extract -C extra-filename from captured args. + // Cargo passes this to rustc to disambiguate output filenames via metadata hash. + // Handle all forms: `-Cextra-filename=X`, `-C extra-filename=X`, and `-C` `extra-filename=X`. + let extra_filename = rustc_args.args.iter().enumerate().find_map(|(i, arg)| { + arg.strip_prefix("-Cextra-filename=") + .map(|s| s.to_string()) + .or_else(|| { + // Handle `-C` followed by `extra-filename=X` as separate args + if arg == "-C" { + rustc_args.args.get(i + 1).and_then(|next| { + next.strip_prefix("extra-filename=").map(|s| s.to_string()) + }) + } else { + None + } + }) + }); + + // If we have an exact extra-filename, construct the precise rlib path. + if let Some(extra) = &extra_filename { + let exact = out_dir.join(format!("lib{crate_name}{extra}.rlib")); + if exact.exists() { + return Some(exact); + } + } + + // Fallback: glob for lib-.rlib in the output directory. + // This handles cases where -C extra-filename isn't in the captured args. + // Prefer the most recently modified rlib to avoid picking up stale artifacts. + let prefix = format!("lib{crate_name}-"); + let entries = std::fs::read_dir(&out_dir).ok()?; + let mut best: Option<(PathBuf, std::time::SystemTime)> = None; + for entry in entries.flatten() { + if let Some(name) = entry.file_name().to_str() { + if name.starts_with(&prefix) && name.ends_with(".rlib") { + let mtime = entry.metadata().ok()?.modified().ok()?; + if best.as_ref().map_or(true, |(_, t)| mtime > *t) { + best = Some((entry.path(), mtime)); + } + } + } + } + if let Some((path, _)) = best { + return Some(path); + } + + None + } + /// with our hotpatching setup since it uses linker interception. /// /// This is sadly a hack. I think there might be other ways of busting the fingerprint (rustc wrapper?) /// but that would require relying on cargo internals. /// /// This might stop working if/when cargo stabilizes contents-based fingerprinting. + /// + /// `dx` compiles everything with `--target` which ends up with a structure like: + /// `target///.fingerprint/-` + /// + /// Normally you can't rely on this structure (ie with `cargo build`) but the explicit + /// target arg guarantees this will work. fn bust_fingerprint(&self, ctx: &BuildContext) -> Result<()> { if matches!(ctx.mode, BuildMode::Fat) { - // `dx` compiles everything with `--target` which ends up with a structure like: - // target///.fingerprint/- - // - // normally you can't rely on this structure (ie with `cargo build`) but the explicit - // target arg guarantees this will work. let fingerprint_dir = self .target_dir .join(self.triple.to_string()) .join(&self.profile) .join(".fingerprint"); + // Bust fingerprints for ALL workspace member crates during Fat builds. + // This ensures cargo recompiles them through RUSTC_WORKSPACE_WRAPPER + // so we capture their rustc args for later thin builds. + let mut busted = HashSet::new(); + for member in self.workspace.krates.workspace_members() { + if let krates::Node::Krate { krate, .. } = member { + busted.insert(krate.name.as_str()); + } + } + // split at the last `-` used to separate the hash from the name // This causes to more aggressively bust hashes for all combinations of features // and fingerprints for this package since we're just ignoring the hash - for entry in std::fs::read_dir(&fingerprint_dir)?.flatten() { - if let Some(fname) = entry.file_name().to_str() { - if let Some((name, _)) = fname.rsplit_once('-') { - if name == self.package().name { - _ = std::fs::remove_dir_all(entry.path()); + if let Ok(entries) = std::fs::read_dir(&fingerprint_dir) { + for entry in entries.flatten() { + if let Some(fname) = entry.file_name().to_str() { + if let Some((name, _)) = fname.rsplit_once('-') { + if busted.contains(name) { + _ = std::fs::remove_dir_all(entry.path()); + } } } } } } + Ok(()) } diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 68789ed84..b95ecb7c4 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -209,12 +209,24 @@ impl CommandWithPlatformOverrides { impl BuildArtifacts { pub(crate) fn into_structured_output(self) -> StructuredBuildArtifacts { + // Extract the tip crate's args for the structured output. + // The tip crate is identified by replacing hyphens with underscores in the target name, + // but since we don't have the BuildRequest here, we look for the entry with link_args + // (only the tip crate has link_args attached) or fall back to any entry. + let tip_args = self + .workspace_rustc_args + .values() + .find(|a| !a.link_args.is_empty()) + .or_else(|| self.workspace_rustc_args.values().next()) + .cloned() + .unwrap_or_default(); + StructuredBuildArtifacts { path: self.root_dir, exe: self.exe, - rustc_args: self.direct_rustc.args, - rustc_envs: self.direct_rustc.envs, - link_args: self.direct_rustc.link_args, + rustc_args: tip_args.args, + rustc_envs: tip_args.envs, + link_args: tip_args.link_args, assets: self.assets.unique_assets().cloned().collect(), } } diff --git a/packages/cli/src/cli/hotpatch.rs b/packages/cli/src/cli/hotpatch.rs index fe47d87c0..d68eeae31 100644 --- a/packages/cli/src/cli/hotpatch.rs +++ b/packages/cli/src/cli/hotpatch.rs @@ -5,8 +5,8 @@ use crate::{ use anyhow::Context; use clap::Parser; use dioxus_dx_wire_format::StructuredBuildArtifacts; -use std::io::Read; use std::sync::Arc; +use std::{collections::HashMap, io::Read}; const HELP_HEADING: &str = "Hotpatching a binary"; @@ -70,15 +70,25 @@ impl HotpatchTip { // consider a shared-mem approach or a binary serializer? something like arrow / parquet / bincode? let cache = Arc::new(HotpatchModuleCache::new(&exe, &request.triple)?); - let mode = BuildMode::Thin { - rustc_args: crate::RustcArgs { + let tip_crate_name = request.main_target.replace('-', "_"); + let mut workspace_rustc_args = HashMap::new(); + workspace_rustc_args.insert( + format!("{tip_crate_name}.bin"), + crate::RustcArgs { args: rustc_args, envs: rustc_envs, link_args, }, + ); + + let mode = BuildMode::Thin { + workspace_rustc_args, changed_files: vec![], + changed_crates: vec![], + modified_crates: std::collections::HashSet::new(), aslr_reference: self.aslr_reference, cache: cache.clone(), + object_cache: crate::ObjectCache::new(&request.session_cache_dir()), }; let artifacts = AppBuilder::started(request, mode, build_id)? diff --git a/packages/cli/src/rustcwrapper.rs b/packages/cli/src/rustcwrapper.rs index aa30a312b..2cc9f4cc1 100644 --- a/packages/cli/src/rustcwrapper.rs +++ b/packages/cli/src/rustcwrapper.rs @@ -5,9 +5,10 @@ use std::{ process::ExitCode, }; -/// The environment variable indicating where the args file is located. +/// The environment variable indicating where the args directory is located. /// -/// When `dx-rustc` runs, it writes its arguments to this file. +/// When `dx-rustc` runs, it writes each workspace crate's arguments to a +/// separate file in this directory: `{dir}/{crate_name}.json`. pub const DX_RUSTC_WRAPPER_ENV_VAR: &str = "DX_RUSTC"; /// Is `dx` being used as a rustc wrapper? @@ -64,7 +65,7 @@ fn has_linking_args() -> bool { false } -/// Run rustc directly, but output the result to a file. +/// Run rustc directly, but output the result to a per-crate file in the args directory. /// /// pub fn run_rustc() -> ExitCode { @@ -75,11 +76,11 @@ pub fn run_rustc() -> ExitCode { .run_link(); } - let var_file: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR) + let args_dir: PathBuf = std::env::var(DX_RUSTC_WRAPPER_ENV_VAR) .expect("DX_RUSTC env var must be set") .into(); - // Cargo invokes a wrapper like: `wrapper-name rustc [args...]` + // Cargo invokes a workspace wrapper like: `wrapper-name rustc [args...]` // We skip our own executable name (`wrapper-name`) to get the args passed to us. let captured_args = args().skip(1).collect::>(); @@ -89,32 +90,51 @@ pub fn run_rustc() -> ExitCode { link_args: Default::default(), }; - // Another terrible hack to avoid caching non-sensical args when - // a build is completely fresh (rustc is invoked with --crate-name ___) - if rustc_args + // Extract the crate name from the args to use as the filename. + // Skip non-sensical args when a build is completely fresh (rustc is invoked with --crate-name ___) + let crate_name = rustc_args .args .iter() .skip_while(|arg| *arg != "--crate-name") - .nth(1) - .is_some_and(|name| name != "___") - { - let parent_dir = var_file - .parent() - .expect("Args file path has no parent directory"); - std::fs::create_dir_all(parent_dir) - .expect("Failed to create parent directory for args file"); + .nth(1); - let serialized_args = - serde_json::to_string(&rustc_args).expect("Failed to serialize rustc args"); + if let Some(crate_name) = crate_name { + if crate_name != "___" { + std::fs::create_dir_all(&args_dir) + .expect("Failed to create args directory for rustc wrapper"); - std::fs::write(&var_file, serialized_args).expect("Failed to write rustc args to file"); + let crate_type = rustc_args + .args + .iter() + .skip_while(|arg| *arg != "--crate-type") + .nth(1) + .map(|s| s.as_str()); + + let serialized_args = + serde_json::to_string(&rustc_args).expect("Failed to serialize rustc args"); + + // Write args with an explicit target suffix: {crate_name}.lib.json or + // {crate_name}.bin.json. This avoids the ambiguity of a bare {crate_name}.json + // and ensures lib+bin crates don't overwrite each other. + let suffix = match crate_type { + Some("lib" | "rlib") => "lib", + Some("bin") => "bin", + _ => "bin", // proc-macro, cdylib, etc. — treat as bin + }; + + std::fs::write( + args_dir.join(format!("{crate_name}.{suffix}.json")), + &serialized_args, + ) + .expect("Failed to write rustc args to file"); + } } // Run the actual rustc command. // We want all stdout/stderr to be inherited, so the user sees the compiler output. let mut cmd = std::process::Command::new("rustc"); - // The first argument in `captured_args` is "rustc", which we need to skip + // The first argument in `captured_args` is the rustc path, which we need to skip // when passing arguments to the `rustc` command we are spawning. cmd.args(captured_args.iter().skip(1)); cmd.envs(rustc_args.envs); diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 016d3c48d..c405823a0 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -22,9 +22,9 @@ use notify::{ Config, EventKind, RecursiveMode, Watcher as NotifyWatcher, }; use std::{ - collections::{HashMap, HashSet}, + collections::{HashMap, HashSet, VecDeque}, net::{IpAddr, TcpListener}, - path::PathBuf, + path::{Path, PathBuf}, sync::Arc, time::Duration, }; @@ -511,9 +511,13 @@ impl AppServer { // A full rebuild is required when the user modifies static initializers which we haven't wired up yet. if needs_full_rebuild && self.automatic_rebuilds { if self.use_hotpatch_engine { - self.client.patch_rebuild(files.to_vec(), BuildId::PRIMARY); + let changed_crates = self.order_changed_crates(files); + + self.client + .patch_rebuild(files.to_vec(), changed_crates.clone(), BuildId::PRIMARY); + if let Some(server) = self.server.as_mut() { - server.patch_rebuild(files.to_vec(), BuildId::SECONDARY); + server.patch_rebuild(files.to_vec(), changed_crates, BuildId::SECONDARY); } self.clear_hot_reload_changes(); self.clear_cached_rsx(); @@ -1163,6 +1167,152 @@ impl AppServer { krates } + /// Compute the ordered compilation chain from a changed workspace crate to the tip crate. + /// + /// Returns crate names (underscore-normalized) in compilation order: the changed crate first, + /// then each intermediate workspace crate that depends on it, ending with the tip crate. + /// + /// Uses BFS from the tip crate through its workspace dependencies to find the path. + /// If the changed crate IS the tip crate, returns just `[tip]`. + pub(crate) fn workspace_dep_chain(&self, changed_crate: &str) -> Vec { + let tip_name = self.client.build.main_target.replace('-', "_"); + + // If the changed crate is the tip, no chain needed + if changed_crate == tip_name { + return vec![tip_name]; + } + + // Build a map of workspace crate names to their krates NodeIds + let mut name_to_node: HashMap = HashMap::new(); + for member in self.workspace.krates.workspace_members() { + if let krates::Node::Krate { id, krate, .. } = member { + let normalized = krate.name.replace('-', "_"); + name_to_node.insert(normalized, self.workspace.krates.nid_for_kid(id).unwrap()); + } + } + + // BFS/DFS from tip through workspace deps to find path to changed crate. + // We walk the dependency edges (tip → its deps → their deps → ...) looking for changed_crate. + let Some(&tip_node) = name_to_node.get(&tip_name) else { + return vec![changed_crate.to_string()]; + }; + + // parent[node] = the workspace crate that depends on it (closer to tip) + let mut parent: HashMap> = HashMap::new(); + parent.insert(tip_node, None); + let mut queue = VecDeque::new(); + queue.push_back(tip_node); + + let mut target_node = None; + + while let Some(current) = queue.pop_front() { + for (dep, _edge) in self.workspace.krates.get_deps(current) { + let (dep_name, dep_nid) = match dep { + krates::Node::Krate { id, krate, .. } => { + let normalized = krate.name.replace('-', "_"); + let nid = self.workspace.krates.nid_for_kid(id).unwrap(); + (normalized, nid) + } + _ => continue, + }; + + // Only traverse workspace members + if !name_to_node.contains_key(&dep_name) { + continue; + } + + if parent.contains_key(&dep_nid) { + continue; // already visited + } + + parent.insert(dep_nid, Some(current)); + + if dep_name == changed_crate { + target_node = Some(dep_nid); + break; + } + + queue.push_back(dep_nid); + } + + if target_node.is_some() { + break; + } + } + + // Reconstruct the path from changed_crate → ... → tip + let Some(target) = target_node else { + // Changed crate not found in workspace dep graph — just compile it alone + return vec![changed_crate.to_string()]; + }; + + let mut chain = vec![]; + let mut node = target; + loop { + // Find the crate name for this node + let krate = &self.workspace.krates[node]; + chain.push(krate.name.replace('-', "_")); + + match parent.get(&node) { + Some(Some(parent_node)) => node = *parent_node, + _ => break, + } + } + + chain + } + + /// Order a set of changed workspace crates so that deeper dependencies compile first. + /// + /// Uses `workspace_dep_chain` to determine the depth of each crate in the dependency graph, + /// then sorts so that leaves (deepest deps) compile before crates closer to the tip. + fn order_changed_crates(&self, files: &[PathBuf]) -> Vec { + // Determine which workspace crates changed based on the file paths. + // Order them so deeper deps compile first (leaves before dependents). + let changed_set: HashSet = files + .iter() + .filter_map(|f| self.file_to_workspace_crate(f)) + .collect(); + + let mut crates_with_depth: Vec<_> = changed_set + .iter() + .map(|c| (c.clone(), self.workspace_dep_chain(c).len())) + .collect(); + + // Longer chain = deeper in dep tree = should compile first + crates_with_depth.sort_by(|a, b| b.1.cmp(&a.1)); + crates_with_depth.into_iter().map(|(c, _)| c).collect() + } + + /// Map a changed file path to the workspace crate it belongs to. + /// + /// Returns the crate name in rustc convention (hyphens → underscores), matching the + /// `--crate-name` arg used by rustc and the keys in `workspace_rustc_args`. + /// + /// Finds the workspace member whose crate directory is the longest prefix of the file path. + fn file_to_workspace_crate(&self, file: &Path) -> Option { + let mut best_match: Option<(String, usize)> = None; + + for member in self.workspace.krates.workspace_members() { + if let krates::Node::Krate { krate, .. } = member { + let Some(crate_dir) = krate.manifest_path.parent() else { + continue; + }; + if let Ok(relative) = file.strip_prefix(crate_dir.as_std_path()) { + let depth = relative.components().count(); + let is_better = best_match + .as_ref() + .map_or(true, |(_, best_depth)| depth < *best_depth); + if is_better { + best_match = Some((krate.name.replace('-', "_"), depth)); + } + } + } + } + + best_match.map(|(name, _)| name) + } + /// Check if this is a fullstack build. This means that there is an additional build with the `server` platform. pub(crate) fn is_fullstack(&self) -> bool { self.fullstack