From 9daf7d22ca707c2b2f40860bf2b2035d8cc4deef Mon Sep 17 00:00:00 2001 From: Benjamin Carlsson Date: Sun, 6 Sep 2026 15:49:17 +0000 Subject: [PATCH] Add bounded GStreamer playback components to the voice host (#43244) ## What changed - Add a `GstAudioSink` subclass backed by a bounded mono `F32LE` playback writer, with partial writes, cancellation on speaker epoch changes, and delay accounting for queued samples and pending device output. - Recreate the CPAL output stream and discard buffered audio when speaker controls change the epoch, preventing stale audio from surviving suppression. - Add GStreamer dependencies, adjust Windows Bazel native-tool selection, and allow the helper handshake time to load startup-linked libraries. - Separate installed-client tests from protocol lifecycle tests and supply native startup libraries in test packages. ## Testing Add unit coverage for partial writes, sample accounting, suppression cancellation, stale writers, device delay, invalid samples, and stalled consumption. GitOrigin-RevId: c48c48d2fee00659cf6f8a4039a6745385cb77cd --- MODULE.bazel.lock | 23 +- codex-rs/Cargo.lock | 286 +++++++++++++- codex-rs/realtime-webrtc/src/client.rs | 3 +- codex-rs/voice-host/Cargo.toml | 9 + codex-rs/voice-host/src/audio_sink.rs | 110 ++++++ codex-rs/voice-host/src/device_buffers.rs | 32 +- .../voice-host/src/device_buffers_tests.rs | 16 +- codex-rs/voice-host/src/devices.rs | 85 +++- codex-rs/voice-host/src/devices_tests.rs | 3 +- codex-rs/voice-host/src/playback.rs | 114 ++++++ codex-rs/voice-host/src/playback_tests.rs | 88 +++++ codex-rs/voice-host/tests/installed_client.rs | 363 ++++++++++++++++++ codex-rs/voice-host/tests/lifecycle.rs | 304 +-------------- third_party/voice/BUILD.bazel | 38 +- 14 files changed, 1132 insertions(+), 342 deletions(-) create mode 100644 codex-rs/voice-host/src/audio_sink.rs create mode 100644 codex-rs/voice-host/src/playback.rs create mode 100644 codex-rs/voice-host/src/playback_tests.rs create mode 100644 codex-rs/voice-host/tests/installed_client.rs diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 321a1b57ff..fe0127ec16 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -729,6 +729,7 @@ "atomic-waker_1.1.2": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"cargo_bench_support\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3.5\"},{\"default_features\":false,\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.7.0\"}],\"features\":{}}", "atomic_0.5.3": "{\"dependencies\":[],\"features\":{\"default\":[\"fallback\"],\"fallback\":[],\"nightly\":[],\"std\":[]}}", "atomic_0.6.1": "{\"dependencies\":[{\"name\":\"bytemuck\",\"req\":\"^1.13.1\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"bytemuck\",\"req\":\"^1.13.1\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.219\"},{\"default_features\":false,\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.219\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.140\"}],\"features\":{\"default\":[\"fallback\"],\"fallback\":[],\"nightly\":[],\"serde\":[\"dep:serde\"],\"std\":[]}}", + "atomic_refcell_0.1.14": "{\"dependencies\":[{\"default_features\":false,\"features\":[\"require-cas\"],\"name\":\"portable-atomic\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[],\"portable-atomic\":[\"dep:portable-atomic\"],\"serde\":[\"dep:serde\"]}}", "audio-codec-algorithms_0.8.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1.37\"}],\"features\":{\"internal-no-panic\":[\"dep:no-panic\"]}}", "audioadapter-buffers_5.2.0": "{\"dependencies\":[{\"name\":\"audioadapter\",\"req\":\"^5.0.0\"},{\"features\":[\"test-utils\"],\"kind\":\"dev\",\"name\":\"audioadapter\",\"req\":\"^5.0.0\"},{\"default_features\":false,\"name\":\"audioadapter-sample\",\"req\":\"^5.2.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.6\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.15\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"audioadapter-sample/std\",\"num-traits/std\",\"alloc\"]}}", "audioadapter-sample_5.2.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"audio-codec-algorithms\",\"req\":\"^0.8.1\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.15\"}],\"features\":{\"default\":[\"std\"],\"std\":[\"num-traits/std\"]}}", @@ -821,6 +822,7 @@ "cc_1.2.62": "{\"dependencies\":[{\"name\":\"find-msvc-tools\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"name\":\"jobserver\",\"optional\":true,\"req\":\"^0.1.30\"},{\"default_features\":false,\"name\":\"libc\",\"optional\":true,\"req\":\"^0.2.62\",\"target\":\"cfg(unix)\"},{\"name\":\"shlex\",\"req\":\"^1.3.0\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"jobserver\":[],\"parallel\":[\"dep:libc\",\"dep:jobserver\"]}}", "ccm_0.5.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"aead\",\"req\":\"^0.5\"},{\"default_features\":false,\"features\":[\"dev\"],\"kind\":\"dev\",\"name\":\"aead\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"aes\",\"req\":\"^0.8.1\"},{\"default_features\":false,\"name\":\"cipher\",\"req\":\"^0.4.3\"},{\"default_features\":false,\"name\":\"ctr\",\"req\":\"^0.9.1\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.3.4\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2\"}],\"features\":{\"alloc\":[\"aead/alloc\"],\"default\":[\"alloc\",\"getrandom\"],\"getrandom\":[\"aead/getrandom\"],\"heapless\":[\"aead/heapless\"],\"std\":[\"aead/std\",\"alloc\"],\"stream\":[\"aead/stream\"]}}", "cexpr_0.6.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"clang-sys\",\"req\":\">=0.13.0, <0.29.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"nom\",\"req\":\"^7\"}],\"features\":{}}", + "cfg-expr_0.20.9": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"similar-asserts\",\"req\":\"^1.7\"},{\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"name\":\"target-lexicon\",\"optional\":true,\"req\":\"=0.13.5\"}],\"features\":{\"default\":[],\"targets\":[\"target-lexicon\"]}}", "cfg-if_1.0.4": "{\"dependencies\":[{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"}],\"features\":{\"rustc-dep-of-std\":[\"core\"]}}", "cfg_aliases_0.1.1": "{\"dependencies\":[],\"features\":{}}", "cfg_aliases_0.2.1": "{\"dependencies\":[],\"features\":{}}", @@ -1078,6 +1080,7 @@ "gif_0.14.1": "{\"dependencies\":[{\"name\":\"color_quant\",\"optional\":true,\"req\":\"^1.1\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.7.0\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"png\",\"req\":\"^0.18.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.10.0\"},{\"name\":\"weezl\",\"req\":\"^0.1.10\"}],\"features\":{\"color_quant\":[\"dep:color_quant\"],\"default\":[\"raii_no_panic\",\"std\",\"color_quant\"],\"raii_no_panic\":[],\"std\":[]}}", "gimli_0.26.2": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"compiler_builtins\",\"optional\":true,\"req\":\"^0.1.2\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"crossbeam\",\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.2.0\"},{\"kind\":\"dev\",\"name\":\"getopts\",\"req\":\"^0.2\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^1.0.2\"},{\"kind\":\"dev\",\"name\":\"memmap2\",\"req\":\"^0.5.5\"},{\"kind\":\"dev\",\"name\":\"num_cpus\",\"req\":\"^1\"},{\"features\":[\"wasm\"],\"kind\":\"dev\",\"name\":\"object\",\"req\":\"^0.29.0\"},{\"kind\":\"dev\",\"name\":\"rayon\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"regex\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"},{\"kind\":\"dev\",\"name\":\"typed-arena\",\"req\":\"^2\"}],\"features\":{\"default\":[\"read\",\"write\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"endian-reader\":[\"read\",\"stable_deref_trait\"],\"read\":[\"read-core\"],\"read-core\":[],\"rustc-dep-of-std\":[\"core\",\"alloc\",\"compiler_builtins\"],\"std\":[\"fallible-iterator/std\",\"stable_deref_trait/std\"],\"write\":[\"indexmap\"]}}", "gimli_0.32.3": "{\"dependencies\":[{\"name\":\"alloc\",\"optional\":true,\"package\":\"rustc-std-workspace-alloc\",\"req\":\"^1.0.0\"},{\"name\":\"core\",\"optional\":true,\"package\":\"rustc-std-workspace-core\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"fallible-iterator\",\"optional\":true,\"req\":\"^0.3.0\"},{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.0.0\"},{\"default_features\":false,\"name\":\"stable_deref_trait\",\"optional\":true,\"req\":\"^1.1.0\"},{\"kind\":\"dev\",\"name\":\"test-assembler\",\"req\":\"^0.1.3\"}],\"features\":{\"default\":[\"read-all\",\"write\"],\"endian-reader\":[\"read\",\"dep:stable_deref_trait\"],\"fallible-iterator\":[\"dep:fallible-iterator\"],\"read\":[\"read-core\"],\"read-all\":[\"read\",\"std\",\"fallible-iterator\",\"endian-reader\"],\"read-core\":[],\"rustc-dep-of-std\":[\"dep:core\",\"dep:alloc\"],\"std\":[\"fallible-iterator?/std\",\"stable_deref_trait?/std\"],\"write\":[\"dep:indexmap\"]}}", + "gio-sys_0.22.8": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gobject-sys\",\"req\":\"^0.22.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"features\":[\"Win32_Networking_WinSock\"],\"name\":\"windows-sys\",\"req\":\">=0.52, <=0.61\",\"target\":\"cfg(windows)\"}],\"features\":{\"v2_58\":[],\"v2_60\":[\"v2_58\"],\"v2_62\":[\"v2_60\"],\"v2_64\":[\"v2_62\"],\"v2_66\":[\"v2_64\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_76\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"],\"v2_88\":[\"v2_86\"]}}", "git+https://github.com/dzbarsky/rules_rust?rev=b56cbaa8465e74127f1ea216f813cd377295ad81#b56cbaa8465e74127f1ea216f813cd377295ad81_runfiles": "{\"dependencies\":[],\"features\":{},\"strip_prefix\":\"\"}", "git+https://github.com/helix-editor/nucleo.git?rev=4253de9faabb4e5c6d81d946a5e35a90f87347ee#4253de9faabb4e5c6d81d946a5e35a90f87347ee_nucleo": "{\"dependencies\":[{\"default_features\":true,\"features\":[],\"name\":\"nucleo-matcher\",\"optional\":false},{\"default_features\":true,\"features\":[\"send_guard\",\"arc_lock\"],\"name\":\"parking_lot\",\"optional\":false},{\"name\":\"rayon\"}],\"features\":{},\"strip_prefix\":\"\"}", "git+https://github.com/helix-editor/nucleo.git?rev=4253de9faabb4e5c6d81d946a5e35a90f87347ee#4253de9faabb4e5c6d81d946a5e35a90f87347ee_nucleo-matcher": "{\"dependencies\":[{\"name\":\"memchr\"},{\"default_features\":true,\"features\":[],\"name\":\"unicode-segmentation\",\"optional\":true}],\"features\":{\"default\":[\"unicode-normalization\",\"unicode-casefold\",\"unicode-segmentation\"],\"unicode-casefold\":[],\"unicode-normalization\":[],\"unicode-segmentation\":[\"dep:unicode-segmentation\"]},\"strip_prefix\":\"matcher\"}", @@ -1147,10 +1150,20 @@ "gix-worktree-stream_0.30.0": "{\"dependencies\":[{\"name\":\"gix-attributes\",\"req\":\"^0.31.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"features\":[\"progress\",\"io-pipe\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"name\":\"parking_lot\",\"req\":\"^0.12.4\"}],\"features\":{\"sha1\":[\"gix-filter/sha1\",\"gix-hash/sha1\",\"gix-object/sha1\",\"gix-traverse/sha1\"]}}", "gix-worktree_0.50.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"bstr\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-attributes\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-ignore\",\"req\":\"^0.19.1\"},{\"name\":\"gix-index\",\"req\":\"^0.49.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-validate\",\"optional\":true,\"req\":\"^0.11.0\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"}],\"features\":{\"attributes\":[\"dep:gix-attributes\",\"dep:gix-validate\"],\"default\":[\"attributes\"],\"serde\":[\"dep:serde\",\"bstr/serde\",\"gix-index/serde\",\"gix-hash/serde\",\"gix-object/serde\",\"gix-attributes?/serde\",\"gix-ignore/serde\"],\"sha1\":[\"gix-hash/sha1\"]}}", "gix_0.81.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1\"},{\"name\":\"async-std\",\"optional\":true,\"req\":\"^1.12.0\"},{\"features\":[\"attributes\"],\"kind\":\"dev\",\"name\":\"async-std\",\"req\":\"^1.12.0\"},{\"name\":\"document-features\",\"optional\":true,\"req\":\"^0.2.0\"},{\"name\":\"gix-actor\",\"req\":\"^0.40.0\"},{\"default_features\":false,\"name\":\"gix-archive\",\"optional\":true,\"req\":\"^0.30.0\"},{\"name\":\"gix-attributes\",\"optional\":true,\"req\":\"^0.31.0\"},{\"name\":\"gix-blame\",\"optional\":true,\"req\":\"^0.11.0\"},{\"name\":\"gix-command\",\"optional\":true,\"req\":\"^0.8.0\"},{\"name\":\"gix-commitgraph\",\"req\":\"^0.35.0\"},{\"name\":\"gix-config\",\"req\":\"^0.54.0\"},{\"name\":\"gix-credentials\",\"optional\":true,\"req\":\"^0.37.1\"},{\"name\":\"gix-date\",\"req\":\"^0.15.1\"},{\"default_features\":false,\"name\":\"gix-diff\",\"req\":\"^0.61.0\"},{\"name\":\"gix-dir\",\"optional\":true,\"req\":\"^0.23.0\"},{\"name\":\"gix-discover\",\"req\":\"^0.49.0\"},{\"name\":\"gix-error\",\"req\":\"^0.2.1\"},{\"features\":[\"progress\",\"once_cell\"],\"name\":\"gix-features\",\"req\":\"^0.46.2\"},{\"name\":\"gix-filter\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-fs\",\"req\":\"^0.19.2\"},{\"name\":\"gix-glob\",\"req\":\"^0.24.0\"},{\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"features\":[\"sha256\"],\"kind\":\"dev\",\"name\":\"gix-hash\",\"req\":\"^0.23.0\"},{\"name\":\"gix-hashtable\",\"req\":\"^0.13.0\"},{\"name\":\"gix-ignore\",\"optional\":true,\"req\":\"^0.19.1\"},{\"name\":\"gix-index\",\"optional\":true,\"req\":\"^0.49.0\"},{\"name\":\"gix-lock\",\"req\":\"^21.0.0\"},{\"name\":\"gix-mailmap\",\"optional\":true,\"req\":\"^0.32.0\"},{\"default_features\":false,\"name\":\"gix-merge\",\"optional\":true,\"req\":\"^0.14.0\"},{\"name\":\"gix-negotiate\",\"optional\":true,\"req\":\"^0.29.0\"},{\"name\":\"gix-object\",\"req\":\"^0.58.0\"},{\"name\":\"gix-odb\",\"req\":\"^0.78.0\"},{\"default_features\":false,\"features\":[\"object-cache-dynamic\"],\"name\":\"gix-pack\",\"req\":\"^0.68.0\"},{\"name\":\"gix-path\",\"req\":\"^0.11.2\"},{\"name\":\"gix-pathspec\",\"optional\":true,\"req\":\"^0.16.1\"},{\"name\":\"gix-prompt\",\"optional\":true,\"req\":\"^0.14.1\"},{\"name\":\"gix-protocol\",\"req\":\"^0.59.0\"},{\"name\":\"gix-ref\",\"req\":\"^0.61.0\"},{\"name\":\"gix-refspec\",\"req\":\"^0.39.0\"},{\"default_features\":false,\"name\":\"gix-revision\",\"req\":\"^0.43.0\"},{\"name\":\"gix-revwalk\",\"req\":\"^0.29.0\"},{\"name\":\"gix-sec\",\"req\":\"^0.13.2\"},{\"name\":\"gix-shallow\",\"req\":\"^0.10.0\"},{\"features\":[\"worktree-rewrites\"],\"name\":\"gix-status\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-submodule\",\"optional\":true,\"req\":\"^0.28.0\"},{\"default_features\":false,\"name\":\"gix-tempfile\",\"req\":\"^21.0.0\"},{\"name\":\"gix-trace\",\"req\":\"^0.1.18\"},{\"name\":\"gix-transport\",\"optional\":true,\"req\":\"^0.55.1\"},{\"name\":\"gix-traverse\",\"req\":\"^0.55.0\"},{\"name\":\"gix-url\",\"req\":\"^0.35.2\"},{\"name\":\"gix-utils\",\"req\":\"^0.3.1\"},{\"name\":\"gix-validate\",\"req\":\"^0.11.0\"},{\"default_features\":false,\"name\":\"gix-worktree\",\"optional\":true,\"req\":\"^0.50.0\"},{\"name\":\"gix-worktree-state\",\"optional\":true,\"req\":\"^0.28.0\"},{\"name\":\"gix-worktree-stream\",\"optional\":true,\"req\":\"^0.30.0\"},{\"kind\":\"dev\",\"name\":\"insta\",\"req\":\"^1.46.3\"},{\"kind\":\"dev\",\"name\":\"is_ci\",\"req\":\"^1.1.1\"},{\"name\":\"nonempty\",\"req\":\"^0.12.0\"},{\"name\":\"parking_lot\",\"optional\":true,\"req\":\"^0.12.4\"},{\"kind\":\"dev\",\"name\":\"pretty_assertions\",\"req\":\"^1.4.0\"},{\"features\":[\"progress-tree\"],\"name\":\"prodash\",\"optional\":true,\"req\":\"^31.0.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex\",\"optional\":true,\"req\":\"^1.12.3\"},{\"default_features\":false,\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.114\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"serial_test\",\"req\":\"^3.4.0\"},{\"default_features\":false,\"name\":\"signal-hook\",\"optional\":true,\"req\":\"^0.4.3\"},{\"name\":\"smallvec\",\"req\":\"^1.15.1\"},{\"kind\":\"dev\",\"name\":\"termtree\",\"req\":\"^1.0.0\"},{\"name\":\"thiserror\",\"req\":\"^2.0.18\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"}],\"features\":{\"async-network-client\":[\"gix-protocol/async-client\",\"gix-pack/streaming-input\",\"dep:gix-transport\",\"attributes\",\"credentials\"],\"async-network-client-async-std\":[\"async-std\",\"async-network-client\",\"gix-transport/async-std\"],\"attributes\":[\"excludes\",\"dep:gix-filter\",\"dep:gix-pathspec\",\"dep:gix-attributes\",\"dep:gix-submodule\",\"gix-worktree?/attributes\",\"command\"],\"auto-chain-error\":[\"gix-error/auto-chain-error\"],\"basic\":[\"blob-diff\",\"revision\",\"index\"],\"blame\":[\"dep:gix-blame\",\"blob-diff\"],\"blob-diff\":[\"gix-diff/blob\",\"attributes\"],\"blocking-http-transport-curl\":[\"blocking-network-client\",\"gix-transport/http-client-curl\"],\"blocking-http-transport-curl-openssl\":[\"blocking-http-transport-curl\",\"gix-transport/http-client-curl-openssl\"],\"blocking-http-transport-curl-rustls\":[\"blocking-http-transport-curl\",\"gix-transport/http-client-curl-rust-tls\"],\"blocking-http-transport-reqwest\":[\"blocking-network-client\",\"gix-transport/http-client-reqwest\"],\"blocking-http-transport-reqwest-native-tls\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-native-tls\"],\"blocking-http-transport-reqwest-rust-tls\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-rust-tls\"],\"blocking-http-transport-reqwest-rust-tls-trust-dns\":[\"blocking-http-transport-reqwest\",\"gix-transport/http-client-reqwest-rust-tls-trust-dns\"],\"blocking-network-client\":[\"gix-protocol/blocking-client\",\"gix-pack/streaming-input\",\"dep:gix-transport\",\"attributes\",\"credentials\"],\"cache-efficiency-debug\":[\"gix-features/cache-efficiency-debug\"],\"comfort\":[\"gix-features/progress-unit-bytes\",\"gix-features/progress-unit-human-numbers\"],\"command\":[\"dep:gix-command\"],\"credentials\":[\"dep:gix-credentials\",\"dep:gix-prompt\",\"dep:gix-negotiate\"],\"default\":[\"max-performance-safe\",\"comfort\",\"basic\",\"extras\",\"auto-chain-error\",\"sha1\"],\"dirwalk\":[\"dep:gix-dir\",\"attributes\",\"excludes\"],\"excludes\":[\"dep:gix-ignore\",\"dep:gix-worktree\",\"index\"],\"extras\":[\"worktree-stream\",\"worktree-archive\",\"revparse-regex\",\"mailmap\",\"excludes\",\"attributes\",\"worktree-mutation\",\"credentials\",\"interrupt\",\"status\",\"dirwalk\",\"blame\"],\"hp-tempfile-registry\":[\"gix-tempfile/hp-hashmap\"],\"index\":[\"dep:gix-index\"],\"interrupt\":[\"dep:signal-hook\",\"gix-tempfile/signals\",\"dep:parking_lot\"],\"mailmap\":[\"dep:gix-mailmap\",\"revision\"],\"max-control\":[\"parallel\",\"pack-cache-lru-static\",\"pack-cache-lru-dynamic\"],\"max-performance\":[\"max-performance-safe\"],\"max-performance-safe\":[\"max-control\"],\"merge\":[\"tree-editor\",\"blob-diff\",\"dep:gix-merge\",\"attributes\"],\"need-more-recent-msrv\":[\"merge\",\"tree-editor\"],\"pack-cache-lru-dynamic\":[\"gix-pack/pack-cache-lru-dynamic\"],\"pack-cache-lru-static\":[\"gix-pack/pack-cache-lru-static\"],\"parallel\":[\"gix-features/parallel\"],\"progress-tree\":[\"prodash/progress-tree\"],\"revision\":[\"gix-revision/describe\",\"gix-revision/merge_base\",\"index\"],\"revparse-regex\":[\"regex\",\"revision\"],\"serde\":[\"dep:serde\",\"gix-pack/serde\",\"gix-object/serde\",\"gix-protocol/serde\",\"gix-transport?/serde\",\"gix-ref/serde\",\"gix-odb/serde\",\"gix-index?/serde\",\"gix-mailmap?/serde\",\"gix-url/serde\",\"gix-attributes?/serde\",\"gix-ignore?/serde\",\"gix-revision/serde\",\"gix-worktree?/serde\",\"gix-commitgraph/serde\",\"gix-credentials?/serde\"],\"sha1\":[\"gix-archive?/sha1\",\"gix-blame?/sha1\",\"gix-commitgraph/sha1\",\"gix-config/sha1\",\"gix-diff/sha1\",\"gix-dir?/sha1\",\"gix-discover/sha1\",\"gix-filter?/sha1\",\"gix-hash/sha1\",\"gix-hashtable/sha1\",\"gix-index?/sha1\",\"gix-merge?/sha1\",\"gix-negotiate?/sha1\",\"gix-object/sha1\",\"gix-odb/sha1\",\"gix-pack/sha1\",\"gix-protocol/sha1\",\"gix-ref/sha1\",\"gix-refspec/sha1\",\"gix-revision/sha1\",\"gix-revwalk/sha1\",\"gix-shallow/sha1\",\"gix-status?/sha1\",\"gix-submodule?/sha1\",\"gix-traverse/sha1\",\"gix-worktree?/sha1\",\"gix-worktree-state?/sha1\",\"gix-worktree-stream?/sha1\"],\"status\":[\"gix-status\",\"dirwalk\",\"index\",\"blob-diff\",\"gix-diff/index\"],\"tracing\":[\"gix-features/tracing\"],\"tracing-detail\":[\"gix-features/tracing-detail\",\"tracing\"],\"tree-editor\":[],\"tree-error\":[\"gix-error/tree-error\"],\"verbose-object-parsing-errors\":[\"gix-object/verbose-object-parsing-errors\"],\"worktree-archive\":[\"gix-archive\",\"worktree-stream\",\"attributes\"],\"worktree-mutation\":[\"attributes\",\"dep:gix-worktree-state\"],\"worktree-stream\":[\"gix-worktree-stream\",\"attributes\"],\"zlib-ng\":[\"gix-features/zlib\"],\"zlib-ng-compat\":[\"gix-features/zlib\"],\"zlib-rs\":[\"gix-features/zlib\"],\"zlib-stock\":[\"gix-features/zlib\"]}}", + "glib-macros_0.22.6": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"glib\",\"req\":\"^0.22.0\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"name\":\"proc-macro-crate\",\"optional\":true,\"req\":\"^3.3\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.113\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{\"default\":[\"proc_macro_crate\"],\"proc_macro_crate\":[\"proc-macro-crate\"]}}", + "glib-sys_0.22.8": "{\"dependencies\":[{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v2_58\":[],\"v2_60\":[\"v2_58\"],\"v2_62\":[\"v2_60\"],\"v2_64\":[\"v2_62\"],\"v2_66\":[\"v2_64\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_76\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"],\"v2_88\":[\"v2_86\"],\"v2_90\":[\"v2_88\"]}}", + "glib_0.22.8": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2.10\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8.2\"},{\"name\":\"futures-channel\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-task\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"gio-sys\",\"optional\":true,\"req\":\"^0.22.0\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"glib-macros\",\"req\":\"^0.22.0\"},{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gobject-sys\",\"req\":\"^0.22.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"memchr\",\"req\":\"^2.8.0\"},{\"name\":\"rs-log\",\"optional\":true,\"package\":\"log\",\"req\":\"^0.4\"},{\"features\":[\"union\",\"const_generics\",\"const_new\"],\"name\":\"smallvec\",\"req\":\"^1.15\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{\"compiletests\":[],\"default\":[\"gio\",\"futures\"],\"futures\":[\"futures-core\",\"futures-task\",\"futures-executor\",\"futures-channel\",\"futures-util\"],\"gio\":[\"gio-sys\",\"futures\"],\"log\":[\"rs-log\"],\"log_kv\":[\"log\",\"rs-log/kv\"],\"log_macros\":[\"log\"],\"v2_58\":[\"glib-sys/v2_58\",\"gobject-sys/v2_58\"],\"v2_60\":[\"v2_58\",\"glib-sys/v2_60\"],\"v2_62\":[\"v2_60\",\"glib-sys/v2_62\",\"gobject-sys/v2_62\"],\"v2_64\":[\"v2_62\",\"glib-sys/v2_64\"],\"v2_66\":[\"v2_64\",\"glib-sys/v2_66\",\"gobject-sys/v2_66\"],\"v2_68\":[\"v2_66\",\"glib-sys/v2_68\",\"gobject-sys/v2_68\"],\"v2_70\":[\"v2_68\",\"glib-sys/v2_70\",\"gobject-sys/v2_70\"],\"v2_72\":[\"v2_70\",\"glib-sys/v2_72\",\"gobject-sys/v2_72\"],\"v2_74\":[\"v2_72\",\"glib-sys/v2_74\",\"gobject-sys/v2_74\"],\"v2_76\":[\"v2_74\",\"glib-sys/v2_76\",\"gobject-sys/v2_76\"],\"v2_78\":[\"v2_76\",\"glib-sys/v2_78\",\"gobject-sys/v2_78\"],\"v2_80\":[\"v2_78\",\"glib-sys/v2_80\",\"gobject-sys/v2_80\"],\"v2_82\":[\"v2_80\",\"glib-sys/v2_82\",\"gobject-sys/v2_82\"],\"v2_84\":[\"v2_82\",\"glib-sys/v2_84\",\"gobject-sys/v2_84\"],\"v2_86\":[\"v2_84\",\"glib-sys/v2_86\",\"gobject-sys/v2_86\"],\"v2_88\":[\"v2_86\",\"glib-sys/v2_88\"],\"v2_90\":[\"v2_88\",\"glib-sys/v2_90\"]}}", "glob_0.3.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"doc-comment\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3\"}],\"features\":{}}", "globset_0.4.18": "{\"dependencies\":[{\"name\":\"aho-corasick\",\"req\":\"^1.1.1\"},{\"features\":[\"derive\"],\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.3.2\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"bstr\",\"req\":\"^1.6.2\"},{\"kind\":\"dev\",\"name\":\"glob\",\"req\":\"^0.3.1\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4.20\"},{\"default_features\":false,\"features\":[\"std\",\"perf\",\"syntax\",\"meta\",\"nfa\",\"hybrid\"],\"name\":\"regex-automata\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"regex-syntax\",\"req\":\"^0.8.0\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0.188\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.107\"}],\"features\":{\"arbitrary\":[\"dep:arbitrary\"],\"default\":[\"log\"],\"serde1\":[\"serde\"],\"simd-accel\":[]}}", + "gobject-sys_0.22.6": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v2_58\":[],\"v2_62\":[\"v2_58\"],\"v2_66\":[\"v2_62\"],\"v2_68\":[\"v2_66\"],\"v2_70\":[\"v2_68\"],\"v2_72\":[\"v2_70\"],\"v2_74\":[\"v2_72\"],\"v2_76\":[\"v2_74\"],\"v2_78\":[\"v2_74\"],\"v2_80\":[\"v2_78\"],\"v2_82\":[\"v2_80\"],\"v2_84\":[\"v2_82\"],\"v2_86\":[\"v2_84\"]}}", "goblin_0.10.5": "{\"dependencies\":[{\"default_features\":false,\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"plain\",\"req\":\"^0.2.3\"},{\"default_features\":false,\"name\":\"scroll\",\"req\":\"^0.13\"},{\"kind\":\"dev\",\"name\":\"stderrlog\",\"req\":\"^0.6.0\"}],\"features\":{\"alloc\":[\"scroll/derive\",\"log\"],\"archive\":[\"alloc\"],\"default\":[\"std\",\"elf32\",\"elf64\",\"mach32\",\"mach64\",\"pe32\",\"pe64\",\"te\",\"archive\",\"endian_fd\"],\"elf32\":[],\"elf64\":[],\"endian_fd\":[\"alloc\"],\"mach32\":[\"alloc\",\"endian_fd\",\"archive\"],\"mach64\":[\"alloc\",\"endian_fd\",\"archive\"],\"pe32\":[\"alloc\",\"endian_fd\"],\"pe64\":[\"alloc\",\"endian_fd\"],\"std\":[\"alloc\",\"scroll/std\"],\"te\":[\"alloc\",\"endian_fd\"]}}", "group_0.13.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"ff\",\"req\":\"^0.13\"},{\"name\":\"memuse\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8\"},{\"default_features\":false,\"name\":\"rand_core\",\"req\":\"^0.6\"},{\"name\":\"rand_xorshift\",\"optional\":true,\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"subtle\",\"req\":\"^2.2.1\"}],\"features\":{\"alloc\":[],\"default\":[\"alloc\"],\"tests\":[\"alloc\",\"rand\",\"rand_xorshift\"],\"wnaf-memuse\":[\"alloc\",\"memuse\"]}}", + "gstreamer-audio-sys_0.25.3": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gobject-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gstreamer-base-sys\",\"req\":\"^0.25.0\"},{\"name\":\"gstreamer-sys\",\"req\":\"^0.25.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v1_16\":[],\"v1_18\":[\"v1_16\"],\"v1_20\":[\"v1_18\"],\"v1_22\":[\"v1_20\"],\"v1_24\":[\"v1_22\"],\"v1_26\":[\"v1_24\"],\"v1_28\":[\"v1_26\"],\"v1_30\":[\"v1_28\"]}}", + "gstreamer-audio_0.25.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"byte-slice-cast\",\"req\":\"^1.0\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"name\":\"glib\",\"req\":\"^0.22.0\"},{\"name\":\"gst\",\"package\":\"gstreamer\",\"req\":\"^0.25.0\"},{\"name\":\"gst-base\",\"package\":\"gstreamer-base\",\"req\":\"^0.25.0\"},{\"name\":\"gstreamer-audio-sys\",\"req\":\"^0.25.0\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.15\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"name\":\"smallvec\",\"req\":\"^1.0\"}],\"features\":{\"default\":[],\"serde\":[\"dep:serde\",\"gst/serde\"],\"v1_16\":[\"gst/v1_16\",\"gst-base/v1_16\",\"gstreamer-audio-sys/v1_16\"],\"v1_18\":[\"gst/v1_18\",\"gst-base/v1_18\",\"gstreamer-audio-sys/v1_18\",\"v1_16\"],\"v1_20\":[\"gst/v1_20\",\"gst-base/v1_20\",\"gstreamer-audio-sys/v1_20\",\"v1_18\"],\"v1_22\":[\"gst/v1_22\",\"gst-base/v1_22\",\"gstreamer-audio-sys/v1_22\",\"v1_20\"],\"v1_24\":[\"gst/v1_24\",\"gst-base/v1_24\",\"gstreamer-audio-sys/v1_24\",\"v1_22\"],\"v1_26\":[\"gst/v1_26\",\"gst-base/v1_26\",\"gstreamer-audio-sys/v1_26\",\"v1_24\"],\"v1_28\":[\"gst/v1_28\",\"gst-base/v1_28\",\"gstreamer-audio-sys/v1_28\",\"v1_26\"],\"v1_30\":[\"gst/v1_30\",\"gst-base/v1_30\",\"gstreamer-audio-sys/v1_30\",\"v1_28\"]}}", + "gstreamer-base-sys_0.25.3": "{\"dependencies\":[{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gobject-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gstreamer-sys\",\"req\":\"^0.25.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v1_14_1\":[],\"v1_14_3\":[\"v1_14_1\"],\"v1_16\":[\"v1_14_3\"],\"v1_18\":[\"v1_16\"],\"v1_20\":[\"v1_18\"],\"v1_22\":[\"v1_20\"],\"v1_24\":[\"v1_22\"],\"v1_26\":[\"v1_24\"],\"v1_28\":[\"v1_26\"],\"v1_30\":[\"v1_28\"]}}", + "gstreamer-base_0.25.3": "{\"dependencies\":[{\"name\":\"atomic_refcell\",\"req\":\"^0.1\"},{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"name\":\"glib\",\"req\":\"^0.22.0\"},{\"name\":\"gst\",\"package\":\"gstreamer\",\"req\":\"^0.25.0\"},{\"name\":\"gstreamer-base-sys\",\"req\":\"^0.25.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"}],\"features\":{\"default\":[],\"v1_14_1\":[\"gstreamer-base-sys/v1_14_1\"],\"v1_16\":[\"gst/v1_16\",\"gstreamer-base-sys/v1_16\",\"v1_14_1\"],\"v1_18\":[\"gst/v1_18\",\"gstreamer-base-sys/v1_18\",\"v1_16\"],\"v1_20\":[\"gst/v1_20\",\"gstreamer-base-sys/v1_20\",\"v1_18\"],\"v1_22\":[\"gst/v1_22\",\"gstreamer-base-sys/v1_22\",\"v1_20\"],\"v1_24\":[\"gst/v1_24\",\"gstreamer-base-sys/v1_24\",\"v1_22\"],\"v1_26\":[\"gst/v1_26\",\"gstreamer-base-sys/v1_26\",\"v1_24\"],\"v1_28\":[\"gst/v1_28\",\"gstreamer-base-sys/v1_28\",\"v1_26\"],\"v1_30\":[\"gst/v1_30\",\"gstreamer-base-sys/v1_30\",\"v1_28\"]}}", + "gstreamer-sys_0.25.2": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"glib-sys\",\"req\":\"^0.22.0\"},{\"name\":\"gobject-sys\",\"req\":\"^0.22.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"shell-words\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"system-deps\",\"req\":\"^7\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"}],\"features\":{\"v1_16\":[],\"v1_18\":[\"v1_16\"],\"v1_18_3\":[\"v1_18\"],\"v1_20\":[\"v1_18_3\"],\"v1_20_4\":[\"v1_20\"],\"v1_22\":[\"v1_20_4\"],\"v1_24\":[\"v1_22\"],\"v1_26\":[\"v1_24\"],\"v1_28\":[\"v1_26\"],\"v1_30\":[\"v1_28\"]}}", + "gstreamer_0.25.3": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"futures-executor\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"gir-format-check\",\"req\":\"^0.1\"},{\"name\":\"glib\",\"req\":\"^0.22.0\"},{\"name\":\"gstreamer-sys\",\"req\":\"^0.25.0\"},{\"name\":\"itertools\",\"req\":\"^0.15\"},{\"name\":\"kstring\",\"req\":\"^2.0\"},{\"name\":\"libc\",\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"muldiv\",\"req\":\"^1\"},{\"default_features\":false,\"name\":\"num-integer\",\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"num-rational\",\"req\":\"^0.4\"},{\"name\":\"opt-ops\",\"package\":\"option-operations\",\"req\":\"^0.6\"},{\"name\":\"pastey\",\"req\":\"^0.2\"},{\"name\":\"pin-project-lite\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"ron\",\"req\":\"^0.12\"},{\"features\":[\"derive\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_bytes\",\"optional\":true,\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"},{\"features\":[\"write\"],\"name\":\"smallvec\",\"req\":\"^1.0\"},{\"name\":\"thiserror\",\"req\":\"^2\"}],\"features\":{\"default\":[],\"log\":[\"dep:log\"],\"serde\":[\"num-rational/serde\",\"dep:serde\",\"serde_bytes\"],\"v1_16\":[\"gstreamer-sys/v1_16\"],\"v1_18\":[\"gstreamer-sys/v1_18\",\"v1_16\"],\"v1_20\":[\"gstreamer-sys/v1_20\",\"v1_18\"],\"v1_22\":[\"gstreamer-sys/v1_22\",\"v1_20\"],\"v1_24\":[\"gstreamer-sys/v1_24\",\"v1_22\"],\"v1_26\":[\"gstreamer-sys/v1_26\",\"v1_24\"],\"v1_28\":[\"gstreamer-sys/v1_28\",\"v1_26\"],\"v1_30\":[\"gstreamer-sys/v1_30\",\"v1_28\"]}}", "gzip-header_1.0.0": "{\"dependencies\":[{\"name\":\"crc32fast\",\"req\":\"^1.2.1\"}],\"features\":{}}", "h2_0.4.13": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", "h2_0.4.16": "{\"dependencies\":[{\"name\":\"atomic-waker\",\"req\":\"^1.0.0\"},{\"name\":\"bytes\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10\"},{\"name\":\"fnv\",\"req\":\"^1.0.5\"},{\"default_features\":false,\"name\":\"futures-core\",\"req\":\"^0.3\"},{\"default_features\":false,\"name\":\"futures-sink\",\"req\":\"^0.3\"},{\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"name\":\"http\",\"req\":\"^1.1\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1.0.3\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.4\"},{\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.0\"},{\"name\":\"slab\",\"req\":\"^0.4.2\"},{\"features\":[\"io-util\"],\"name\":\"tokio\",\"req\":\"^1\"},{\"features\":[\"rt-multi-thread\",\"macros\",\"sync\",\"net\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tokio-rustls\",\"req\":\"^0.26\"},{\"features\":[\"codec\",\"io\"],\"name\":\"tokio-util\",\"req\":\"^0.7.1\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"tracing\",\"req\":\"^0.1.35\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.3.2\"},{\"kind\":\"dev\",\"name\":\"webpki-roots\",\"req\":\"^1\"}],\"features\":{\"stream\":[],\"unstable\":[]}}", @@ -1260,6 +1273,7 @@ "itertools_0.12.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.13.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itertools_0.14.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", + "itertools_0.15.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.4.0\"},{\"default_features\":false,\"name\":\"either\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"paste\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"permutohedron\",\"req\":\"^0.2\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.7\"}],\"features\":{\"default\":[\"use_std\"],\"use_alloc\":[],\"use_std\":[\"use_alloc\",\"either/use_std\"]}}", "itoa_1.0.17": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "itoa_1.0.18": "{\"dependencies\":[{\"default_features\":false,\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.8\",\"target\":\"cfg(not(miri))\"},{\"name\":\"no-panic\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{}}", "ixdtf_0.6.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.0\",\"target\":\"cfg(not(target_arch = \\\"wasm32\\\"))\"},{\"default_features\":false,\"features\":[\"std\"],\"kind\":\"dev\",\"name\":\"serde-json-core\",\"req\":\"^0.6.0\"}],\"features\":{\"default\":[\"duration\"],\"duration\":[]}}", @@ -1353,6 +1367,7 @@ "ml-kem_0.2.3": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"features\":[\"rand_core\"],\"kind\":\"dev\",\"name\":\"crypto-common\",\"req\":\"^0.1.6\"},{\"features\":[\"serde\"],\"kind\":\"dev\",\"name\":\"hex\",\"req\":\"^0.4.3\"},{\"kind\":\"dev\",\"name\":\"hex-literal\",\"req\":\"^0.4.1\"},{\"features\":[\"extra-sizes\"],\"name\":\"hybrid-array\",\"req\":\"^0.2.0-rc.9\"},{\"name\":\"kem\",\"req\":\"=0.3.0-pre.0\"},{\"default_features\":false,\"features\":[\"num-bigint\"],\"kind\":\"dev\",\"name\":\"num-rational\",\"req\":\"^0.4.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"name\":\"rand_core\",\"req\":\"^0.6.4\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.208\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.125\"},{\"default_features\":false,\"name\":\"sha3\",\"req\":\"^0.10.8\"},{\"default_features\":false,\"name\":\"zeroize\",\"optional\":true,\"req\":\"^1.8.1\"}],\"features\":{\"default\":[\"std\"],\"deterministic\":[],\"std\":[\"sha3/std\"],\"zeroize\":[\"dep:zeroize\"]}}", "moka_0.12.13": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"actix-rt\",\"req\":\"^2.8\"},{\"kind\":\"dev\",\"name\":\"ahash\",\"req\":\"^0.8.3\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.19\"},{\"name\":\"async-lock\",\"optional\":true,\"req\":\"^3.3\"},{\"name\":\"crossbeam-channel\",\"req\":\"^0.5.15\"},{\"name\":\"crossbeam-epoch\",\"req\":\"^0.9.18\"},{\"name\":\"crossbeam-utils\",\"req\":\"^0.8.21\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.10.0\"},{\"name\":\"equivalent\",\"req\":\"^1.0\"},{\"name\":\"event-listener\",\"optional\":true,\"req\":\"^5.3\"},{\"name\":\"futures-util\",\"optional\":true,\"req\":\"^0.3.17\"},{\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2\"},{\"name\":\"log\",\"optional\":true,\"req\":\"^0.4\"},{\"kind\":\"dev\",\"name\":\"loom\",\"req\":\"^0.7\",\"target\":\"cfg(moka_loom)\"},{\"kind\":\"dev\",\"name\":\"once_cell\",\"req\":\"^1.7\"},{\"name\":\"parking_lot\",\"req\":\"^0.12\"},{\"name\":\"portable-atomic\",\"req\":\"^1.6\"},{\"name\":\"quanta\",\"optional\":true,\"req\":\"^0.12.2\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"default_features\":false,\"features\":[\"rustls-tls\"],\"kind\":\"dev\",\"name\":\"reqwest\",\"req\":\"^0.12\"},{\"name\":\"smallvec\",\"req\":\"^1.8\"},{\"name\":\"tagptr\",\"req\":\"^0.2\"},{\"features\":[\"fs\",\"io-util\",\"macros\",\"rt-multi-thread\",\"sync\",\"time\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.19\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0\",\"target\":\"cfg(trybuild)\"},{\"features\":[\"v4\"],\"name\":\"uuid\",\"req\":\"^1.1\"}],\"features\":{\"atomic64\":[],\"default\":[],\"future\":[\"async-lock\",\"event-listener\",\"futures-util\"],\"logging\":[\"log\"],\"quanta\":[\"dep:quanta\"],\"sync\":[],\"unstable-debug-counters\":[\"future\"]}}", "moxcms_0.7.11": "{\"dependencies\":[{\"name\":\"num-traits\",\"req\":\"^0.2\"},{\"name\":\"pxfm\",\"req\":\"^0.1.1\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"}],\"features\":{\"avx\":[],\"avx512\":[],\"default\":[\"avx\",\"sse\",\"neon\"],\"neon\":[],\"options\":[],\"sse\":[]}}", + "muldiv_1.0.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"quickcheck\",\"req\":\"^1\"}],\"features\":{}}", "multimap_0.10.1": "{\"dependencies\":[{\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"}],\"features\":{\"default\":[\"serde_impl\"],\"serde_impl\":[\"serde\"]}}", "multipart_0.18.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"buf_redux\",\"optional\":true,\"req\":\"^0.8\"},{\"name\":\"clippy\",\"optional\":true,\"req\":\">=0.0, <0.1\"},{\"kind\":\"dev\",\"name\":\"env_logger\",\"req\":\"^0.5\"},{\"name\":\"httparse\",\"optional\":true,\"req\":\"^1.2\"},{\"default_features\":false,\"name\":\"hyper\",\"optional\":true,\"req\":\">=0.9, <0.11\"},{\"name\":\"iron\",\"optional\":true,\"req\":\">=0.4, <0.7\"},{\"name\":\"lazy_static\",\"optional\":true,\"req\":\"^1.2.0\"},{\"name\":\"log\",\"req\":\"^0.4\"},{\"name\":\"mime\",\"req\":\"^0.3.14\"},{\"name\":\"mime_guess\",\"req\":\"^2.0.1\"},{\"name\":\"nickel\",\"optional\":true,\"req\":\">=0.10.1\"},{\"name\":\"quick-error\",\"optional\":true,\"req\":\"^1.2\"},{\"name\":\"rand\",\"req\":\"^0.8\"},{\"name\":\"rocket\",\"optional\":true,\"req\":\"^0.4\"},{\"name\":\"safemem\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"tiny_http\",\"optional\":true,\"req\":\"^0.6\"},{\"name\":\"twoway\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"bench\":[],\"client\":[],\"default\":[\"client\",\"hyper\",\"iron\",\"mock\",\"nickel\",\"server\",\"tiny_http\"],\"mock\":[],\"nightly\":[],\"server\":[\"buf_redux\",\"httparse\",\"quick-error\",\"safemem\",\"twoway\"]}}", "munge_0.4.7": "{\"dependencies\":[{\"default_features\":false,\"name\":\"munge_macro\",\"req\":\"=0.4.7\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"default_features\":false,\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1\"}],\"features\":{}}", @@ -1431,6 +1446,7 @@ "opentelemetry_0.31.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"futures\",\"req\":\"^0.3\"},{\"name\":\"futures-core\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"futures-sink\",\"optional\":true,\"req\":\"^0.3\"},{\"name\":\"js-sys\",\"req\":\"^0.3.63\",\"target\":\"cfg(all(target_arch = \\\"wasm32\\\", not(target_os = \\\"wasi\\\")))\"},{\"name\":\"pin-project-lite\",\"optional\":true,\"req\":\"^0.2\"},{\"default_features\":false,\"features\":[\"os_rng\",\"thread_rng\"],\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.9\"},{\"default_features\":false,\"name\":\"thiserror\",\"optional\":true,\"req\":\"^2\"},{\"features\":[\"full\"],\"kind\":\"dev\",\"name\":\"tokio\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"tracing\",\"optional\":true,\"req\":\">=0.1.40\"}],\"features\":{\"default\":[\"trace\",\"metrics\",\"logs\",\"internal-logs\",\"futures\"],\"futures\":[\"futures-core\",\"futures-sink\",\"pin-project-lite\"],\"internal-logs\":[\"tracing\"],\"logs\":[],\"metrics\":[],\"spec_unstable_logs_enabled\":[\"logs\"],\"testing\":[\"trace\"],\"trace\":[\"futures\",\"thiserror\"]}}", "opentelemetry_sdk_0.31.0": "{\"dependencies\":[{\"features\":[\"html_reports\"],\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5\"},{\"name\":\"futures-channel\",\"req\":\"^0.3\"},{\"name\":\"futures-executor\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\",\"sink\",\"async-await-macro\"],\"name\":\"futures-util\",\"req\":\"^0.3\"},{\"default_features\":false,\"features\":[\"std\"],\"name\":\"http\",\"optional\":true,\"req\":\"^1.1\"},{\"default_features\":false,\"name\":\"opentelemetry\",\"req\":\"^0.31\"},{\"default_features\":false,\"name\":\"opentelemetry-http\",\"optional\":true,\"req\":\"^0.31\"},{\"name\":\"percent-encoding\",\"optional\":true,\"req\":\"^2.0\"},{\"features\":[\"flamegraph\",\"criterion\"],\"kind\":\"dev\",\"name\":\"pprof\",\"req\":\"^0.14\",\"target\":\"cfg(not(target_os = \\\"windows\\\"))\"},{\"default_features\":false,\"features\":[\"std\",\"std_rng\",\"small_rng\",\"os_rng\",\"thread_rng\"],\"name\":\"rand\",\"optional\":true,\"req\":\"^0.9\"},{\"kind\":\"dev\",\"name\":\"rstest\",\"req\":\"^0.23.0\"},{\"default_features\":false,\"features\":[\"derive\",\"rc\"],\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde_json\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"temp-env\",\"req\":\"^0.3.6\"},{\"default_features\":false,\"name\":\"thiserror\",\"req\":\"^2\"},{\"default_features\":false,\"name\":\"tokio\",\"optional\":true,\"req\":\"^1\"},{\"name\":\"tokio-stream\",\"optional\":true,\"req\":\"^0.1\"},{\"default_features\":false,\"name\":\"url\",\"optional\":true,\"req\":\"^2.5\"}],\"features\":{\"default\":[\"trace\",\"metrics\",\"logs\",\"internal-logs\"],\"experimental_async_runtime\":[],\"experimental_logs_batch_log_processor_with_async_runtime\":[\"logs\",\"experimental_async_runtime\"],\"experimental_logs_concurrent_log_processor\":[\"logs\"],\"experimental_metrics_custom_reader\":[\"metrics\"],\"experimental_metrics_disable_name_validation\":[\"metrics\"],\"experimental_metrics_periodicreader_with_async_runtime\":[\"metrics\",\"experimental_async_runtime\"],\"experimental_trace_batch_span_processor_with_async_runtime\":[\"tokio/sync\",\"trace\",\"experimental_async_runtime\"],\"internal-logs\":[\"opentelemetry/internal-logs\"],\"jaeger_remote_sampler\":[\"trace\",\"opentelemetry-http\",\"http\",\"serde\",\"serde_json\",\"url\",\"experimental_async_runtime\"],\"logs\":[\"opentelemetry/logs\"],\"metrics\":[\"opentelemetry/metrics\"],\"rt-tokio\":[\"tokio/rt\",\"tokio/time\",\"tokio-stream\",\"experimental_async_runtime\"],\"rt-tokio-current-thread\":[\"tokio/rt\",\"tokio/time\",\"tokio-stream\",\"experimental_async_runtime\"],\"spec_unstable_logs_enabled\":[\"logs\",\"opentelemetry/spec_unstable_logs_enabled\"],\"spec_unstable_metrics_views\":[\"metrics\"],\"testing\":[\"opentelemetry/testing\",\"trace\",\"metrics\",\"logs\",\"rt-tokio\",\"rt-tokio-current-thread\",\"tokio/macros\",\"tokio/rt-multi-thread\"],\"trace\":[\"opentelemetry/trace\",\"rand\",\"percent-encoding\"]}}", "option-ext_0.2.0": "{\"dependencies\":[],\"features\":{}}", + "option-operations_0.6.1": "{\"dependencies\":[{\"name\":\"pastey\",\"req\":\"^0.2.0\"}],\"features\":{\"default\":[\"std\"],\"std\":[]}}", "opus_0.4.0": "{\"dependencies\":[{\"name\":\"opusic-sys\",\"req\":\"^0.7.3\"}],\"features\":{}}", "opusic-sys_0.7.5": "{\"dependencies\":[{\"kind\":\"build\",\"name\":\"bindgen\",\"optional\":true,\"req\":\"^0.72\"},{\"kind\":\"build\",\"name\":\"cmake\",\"optional\":true,\"req\":\"^0.1\"}],\"features\":{\"build-bindgen\":[\"bindgen\"],\"bundled\":[\"dep:cmake\"],\"default\":[\"bundled\"],\"dred\":[],\"fixed-point\":[],\"no-fortify-source\":[],\"no-hardening\":[],\"no-runtime-feature-detection\":[],\"no-simd\":[],\"no-stack-protector\":[],\"osce\":[]}}", "ordered-float_4.6.0": "{\"dependencies\":[{\"name\":\"arbitrary\",\"optional\":true,\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"borsh\",\"optional\":true,\"req\":\"^1.2.0\"},{\"default_features\":false,\"name\":\"bytemuck\",\"optional\":true,\"req\":\"^1.12.2\"},{\"name\":\"derive-visitor\",\"optional\":true,\"req\":\"^0.4.0\"},{\"name\":\"num-cmp\",\"optional\":true,\"req\":\"^0.1.0\"},{\"default_features\":false,\"name\":\"num-traits\",\"req\":\"^0.2.9\"},{\"name\":\"proptest\",\"optional\":true,\"req\":\"^1.0.0\"},{\"default_features\":false,\"name\":\"rand\",\"optional\":true,\"req\":\"^0.8.3\"},{\"default_features\":false,\"features\":[\"rend\"],\"name\":\"rkyv\",\"optional\":true,\"req\":\"^0.7.41\"},{\"name\":\"schemars\",\"optional\":true,\"req\":\"^0.8.8\"},{\"default_features\":false,\"name\":\"serde\",\"optional\":true,\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_test\",\"req\":\"^1.0\"},{\"default_features\":false,\"name\":\"speedy\",\"optional\":true,\"req\":\"^0.8.3\"}],\"features\":{\"default\":[\"std\"],\"libm\":[\"num-traits/libm\"],\"randtest\":[\"rand/std\",\"rand/std_rng\"],\"rkyv\":[\"rkyv_32\"],\"rkyv_16\":[\"dep:rkyv\",\"rkyv?/size_16\"],\"rkyv_32\":[\"dep:rkyv\",\"rkyv?/size_32\"],\"rkyv_64\":[\"dep:rkyv\",\"rkyv?/size_64\"],\"rkyv_ck\":[\"rkyv?/validation\"],\"serde\":[\"dep:serde\",\"rand?/serde1\"],\"std\":[\"num-traits/std\"]}}", @@ -1796,6 +1812,7 @@ "sys-locale_0.3.2": "{\"dependencies\":[{\"name\":\"js-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(target_os = \\\"android\\\")\"},{\"name\":\"wasm-bindgen\",\"optional\":true,\"req\":\"^0.2\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"},{\"features\":[\"Window\",\"WorkerGlobalScope\",\"Navigator\",\"WorkerNavigator\"],\"name\":\"web-sys\",\"optional\":true,\"req\":\"^0.3\",\"target\":\"cfg(all(target_family = \\\"wasm\\\", not(unix)))\"}],\"features\":{\"js\":[\"js-sys\",\"wasm-bindgen\",\"web-sys\"]}}", "system-configuration-sys_0.6.0": "{\"dependencies\":[{\"name\":\"core-foundation-sys\",\"req\":\"^0.8\"},{\"name\":\"libc\",\"req\":\"^0.2.149\"}],\"features\":{}}", "system-configuration_0.7.0": "{\"dependencies\":[{\"name\":\"bitflags\",\"req\":\"^2\"},{\"name\":\"core-foundation\",\"req\":\"^0.9\"},{\"name\":\"system-configuration-sys\",\"req\":\"^0.6\"}],\"features\":{}}", + "system-deps_7.0.8": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"assert_matches\",\"req\":\"^1.5\"},{\"features\":[\"targets\"],\"name\":\"cfg-expr\",\"req\":\">=0.17, <0.21\"},{\"name\":\"heck\",\"req\":\"^0.5\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14\"},{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"name\":\"pkg-config\",\"req\":\"^0.3.25\"},{\"default_features\":false,\"features\":[\"parse\",\"std\"],\"name\":\"toml\",\"req\":\"^1\"},{\"name\":\"version-compare\",\"req\":\"^0.2\"}],\"features\":{}}", "tagptr_0.2.0": "{\"dependencies\":[],\"features\":{}}", "take_mut_0.2.2": "{\"dependencies\":[],\"features\":{}}", "tar_0.4.44": "{\"dependencies\":[{\"name\":\"filetime\",\"req\":\"^0.2.8\"},{\"name\":\"libc\",\"req\":\"^0.2\",\"target\":\"cfg(unix)\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"^3\"},{\"name\":\"xattr\",\"optional\":true,\"req\":\"^1.1.3\",\"target\":\"cfg(unix)\"}],\"features\":{\"default\":[\"xattr\"]}}", @@ -1855,11 +1872,13 @@ "toml_0.5.11": "{\"dependencies\":[{\"name\":\"indexmap\",\"optional\":true,\"req\":\"^1.0\"},{\"name\":\"serde\",\"req\":\"^1.0.97\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0\"}],\"features\":{\"default\":[],\"preserve_order\":[\"indexmap\"]}}", "toml_0.9.11+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", "toml_0.9.12+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.7\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", + "toml_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"default_features\":false,\"name\":\"foldhash\",\"optional\":true,\"req\":\"^0.2.0\"},{\"default_features\":false,\"name\":\"indexmap\",\"optional\":true,\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"itertools\",\"req\":\"^0.14.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"serde_spanned\",\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_datetime\",\"req\":\"^1.0.0\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.9\"},{\"default_features\":false,\"features\":[\"alloc\"],\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"default_features\":false,\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"std\",\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\",\"serde\",\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"fast_hash\":[\"preserve_order\",\"dep:foldhash\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"preserve_order\":[\"dep:indexmap\",\"std\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"serde_spanned/serde\"],\"std\":[\"indexmap?/std\",\"serde_core?/std\",\"toml_parser?/std\",\"toml_writer?/std\",\"toml_datetime/std\",\"serde_spanned/std\"],\"unbounded\":[]}}", "toml_datetime_0.7.5+spec-1.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"alloc\",\"serde_core?/std\"]}}", + "toml_datetime_1.1.1+spec-1.1.0": "{\"dependencies\":[{\"default_features\":false,\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.228\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^1.1.0\"}],\"features\":{\"alloc\":[\"serde_core?/alloc\"],\"default\":[\"std\"],\"serde\":[\"dep:serde_core\"],\"std\":[\"alloc\",\"serde_core?/std\"]}}", "toml_edit_0.23.10+spec-1.0.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.4\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.5\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.5\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", "toml_edit_0.24.0+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"features\":[\"std\"],\"name\":\"indexmap\",\"req\":\"^2.11.4\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"features\":[\"derive\"],\"kind\":\"dev\",\"name\":\"serde\",\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde-untagged\",\"req\":\"^0.1.9\"},{\"name\":\"serde_core\",\"optional\":true,\"req\":\"^1.0.225\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1.0.145\"},{\"features\":[\"serde\"],\"name\":\"serde_spanned\",\"optional\":true,\"req\":\"^1.0.4\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml-test-data\",\"req\":\"^2.3.3\"},{\"features\":[\"snapshot\"],\"kind\":\"dev\",\"name\":\"toml-test-harness\",\"req\":\"^1.3.3\"},{\"name\":\"toml_datetime\",\"req\":\"^0.7.5\"},{\"name\":\"toml_parser\",\"optional\":true,\"req\":\"^1.0.6\"},{\"name\":\"toml_writer\",\"optional\":true,\"req\":\"^1.0.6\"},{\"kind\":\"dev\",\"name\":\"walkdir\",\"req\":\"^2.5.0\"},{\"name\":\"winnow\",\"optional\":true,\"req\":\"^0.7.13\"}],\"features\":{\"debug\":[\"toml_parser?/debug\",\"dep:anstream\",\"dep:anstyle\",\"display\"],\"default\":[\"parse\",\"display\"],\"display\":[\"dep:toml_writer\"],\"parse\":[\"dep:toml_parser\",\"dep:winnow\"],\"serde\":[\"dep:serde_core\",\"toml_datetime/serde\",\"dep:serde_spanned\"],\"unbounded\":[]}}", - "toml_parser_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^0.7.13\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", "toml_parser_1.0.9+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.20\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^0.6.20\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.11\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^0.7.13\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", + "toml_parser_1.1.3+spec-1.1.0": "{\"dependencies\":[{\"name\":\"anstream\",\"optional\":true,\"req\":\"^1.0.0\"},{\"features\":[\"test\"],\"kind\":\"dev\",\"name\":\"anstream\",\"req\":\"^1.0.0\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.14\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^1.1.0\"},{\"default_features\":false,\"name\":\"winnow\",\"req\":\"^1.0.0\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\"],\"default\":[\"std\"],\"simd\":[\"winnow/simd\"],\"std\":[\"alloc\"],\"unsafe\":[]}}", "toml_writer_1.0.6+spec-1.1.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.7.0\"},{\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"toml_old\",\"package\":\"toml\",\"req\":\"^0.5.11\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"std\":[\"alloc\"]}}", "tonic-build_0.14.3": "{\"dependencies\":[{\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"default\":[\"transport\"],\"transport\":[]}}", "tonic-build_0.14.5": "{\"dependencies\":[{\"name\":\"prettyplease\",\"req\":\"^0.2\"},{\"name\":\"proc-macro2\",\"req\":\"^1.0\"},{\"name\":\"quote\",\"req\":\"^1.0\"},{\"name\":\"syn\",\"req\":\"^2.0\"}],\"features\":{\"default\":[\"transport\"],\"transport\":[]}}", @@ -1936,6 +1955,7 @@ "v8_150.4.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"align-data\",\"req\":\"^0.1.0\"},{\"kind\":\"build\",\"name\":\"bindgen\",\"req\":\"^0.72\"},{\"kind\":\"dev\",\"name\":\"bindgen\",\"req\":\"^0.72\"},{\"name\":\"bitflags\",\"req\":\"^2.5\"},{\"kind\":\"dev\",\"name\":\"bytes\",\"req\":\"^1\"},{\"kind\":\"build\",\"name\":\"fslock\",\"req\":\"^0.2\"},{\"kind\":\"dev\",\"name\":\"fslock\",\"req\":\"^0.2\"},{\"kind\":\"build\",\"name\":\"gzip-header\",\"req\":\"^1.0.0\"},{\"kind\":\"dev\",\"name\":\"gzip-header\",\"req\":\"^1.0.0\"},{\"kind\":\"build\",\"name\":\"home\",\"req\":\"^0\"},{\"kind\":\"dev\",\"name\":\"home\",\"req\":\"^0\"},{\"kind\":\"build\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.8\"},{\"kind\":\"dev\",\"name\":\"miniz_oxide\",\"req\":\"^0.8.8\"},{\"name\":\"paste\",\"req\":\"^1.0\"},{\"kind\":\"dev\",\"name\":\"rustversion\",\"req\":\"^1\"},{\"features\":[\"zoneinfo64\"],\"name\":\"temporal_capi\",\"req\":\"^0.2.3\"},{\"kind\":\"dev\",\"name\":\"trybuild\",\"req\":\"^1.0.96\"},{\"kind\":\"build\",\"name\":\"which\",\"req\":\"^6\"},{\"kind\":\"dev\",\"name\":\"which\",\"req\":\"^6\"}],\"features\":{\"default\":[\"use_custom_libcxx\"],\"simdutf\":[],\"use_custom_libcxx\":[],\"v8_enable_pointer_compression\":[],\"v8_enable_sandbox\":[\"v8_enable_pointer_compression\"],\"v8_enable_v8_checks\":[]}}", "valuable_0.1.1": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.3\"},{\"name\":\"valuable-derive\",\"optional\":true,\"req\":\"=0.1.1\"}],\"features\":{\"alloc\":[],\"default\":[\"std\"],\"derive\":[\"valuable-derive\"],\"std\":[\"alloc\"]}}", "vcpkg_0.2.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"lazy_static\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempdir\",\"req\":\"^0.3.7\"}],\"features\":{}}", + "version-compare_0.2.1": "{\"dependencies\":[],\"features\":{}}", "version_check_0.9.5": "{\"dependencies\":[],\"features\":{}}", "visibility_0.1.1": "{\"dependencies\":[{\"name\":\"proc-macro2\",\"req\":\"^1.0.60\"},{\"name\":\"quote\",\"req\":\"^1.0.26\"},{\"features\":[\"full\"],\"name\":\"syn\",\"req\":\"^2.0.0\"}],\"features\":{\"nightly\":[]}}", "vsimd_0.8.0": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"const-str\",\"req\":\"^0.5.3\"},{\"features\":[\"js\"],\"kind\":\"dev\",\"name\":\"getrandom\",\"req\":\"^0.2.8\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.8.5\"},{\"kind\":\"dev\",\"name\":\"wasm-bindgen-test\",\"req\":\"^0.3.33\",\"target\":\"cfg(target_arch = \\\"wasm32\\\")\"}],\"features\":{\"alloc\":[],\"detect\":[\"std\"],\"std\":[\"alloc\"],\"unstable\":[]}}", @@ -2059,6 +2079,7 @@ "windows_x86_64_msvc_0.53.1": "{\"dependencies\":[],\"features\":{}}", "winnow_0.7.14": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", "winnow_0.7.15": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\"],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[]}}", + "winnow_1.0.4": "{\"dependencies\":[{\"kind\":\"dev\",\"name\":\"annotate-snippets\",\"req\":\"^0.11.4\"},{\"name\":\"anstream\",\"optional\":true,\"req\":\"^0.6.15\"},{\"name\":\"anstyle\",\"optional\":true,\"req\":\"^1.0.8\"},{\"kind\":\"dev\",\"name\":\"anyhow\",\"req\":\"^1.0.100\"},{\"kind\":\"dev\",\"name\":\"automod\",\"req\":\"^1.0.15\"},{\"kind\":\"dev\",\"name\":\"circular\",\"req\":\"^0.3.0\"},{\"kind\":\"dev\",\"name\":\"criterion\",\"req\":\"^0.5.1\"},{\"name\":\"is_terminal_polyfill\",\"optional\":true,\"req\":\"^1.48.1\"},{\"kind\":\"dev\",\"name\":\"lexopt\",\"req\":\"^0.3.1\"},{\"default_features\":false,\"name\":\"memchr\",\"optional\":true,\"req\":\"^2.7\"},{\"kind\":\"dev\",\"name\":\"proptest\",\"req\":\"^1.6.0\"},{\"kind\":\"dev\",\"name\":\"rustc-hash\",\"req\":\"^2.1.1\"},{\"features\":[\"examples\"],\"kind\":\"dev\",\"name\":\"snapbox\",\"req\":\"^0.6.21\"},{\"kind\":\"dev\",\"name\":\"term-transcript\",\"req\":\"^0.2.0\"},{\"name\":\"terminal_size\",\"optional\":true,\"req\":\"^0.4.3\"}],\"features\":{\"alloc\":[],\"ascii\":[\"parser\"],\"binary\":[\"parser\"],\"debug\":[\"std\",\"dep:anstream\",\"dep:anstyle\",\"dep:is_terminal_polyfill\",\"dep:terminal_size\"],\"default\":[\"std\",\"ascii\",\"binary\"],\"parser\":[],\"simd\":[\"dep:memchr\"],\"std\":[\"alloc\",\"memchr?/std\"],\"unstable-doc\":[\"alloc\",\"std\",\"ascii\",\"binary\",\"simd\",\"unstable-recover\"],\"unstable-recover\":[\"parser\"]}}", "winreg_0.10.1": "{\"dependencies\":[{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"impl-default\",\"impl-debug\",\"minwindef\",\"minwinbase\",\"timezoneapi\",\"winerror\",\"winnt\",\"winreg\",\"handleapi\"],\"name\":\"winapi\",\"req\":\"^0.3.9\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[\"winapi/ktmw32\"]}}", "winreg_0.50.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Time\",\"Win32_System_Registry\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\"^0.48.0\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[]}}", "winreg_0.55.0": "{\"dependencies\":[{\"name\":\"cfg-if\",\"req\":\"^1.0\"},{\"name\":\"chrono\",\"optional\":true,\"req\":\"^0.4.6\"},{\"kind\":\"dev\",\"name\":\"rand\",\"req\":\"^0.3\"},{\"name\":\"serde\",\"optional\":true,\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde-transcode\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_bytes\",\"req\":\"^0.11\"},{\"kind\":\"dev\",\"name\":\"serde_derive\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"serde_json\",\"req\":\"^1\"},{\"kind\":\"dev\",\"name\":\"tempfile\",\"req\":\"~3.0\"},{\"features\":[\"Win32_Foundation\",\"Win32_System_Time\",\"Win32_System_Registry\",\"Win32_Security\",\"Win32_Storage_FileSystem\",\"Win32_System_Diagnostics_Debug\"],\"name\":\"windows-sys\",\"req\":\"^0.59\"}],\"features\":{\"serialization-serde\":[\"transactions\",\"serde\"],\"transactions\":[]}}", diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index f5db14657d..c485a3997e 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -844,6 +844,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic_refcell" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e4227379beff4205943696e6c3e0cd809bacdf3f0edd6e3dd153e2269571a4" + [[package]] name = "audio-codec-algorithms" version = "0.8.1" @@ -1794,6 +1800,16 @@ dependencies = [ "nom 7.1.3", ] +[[package]] +name = "cfg-expr" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4ece8474b5f766c63426647e7b4b316b67431ade1036a8313cee24a03ae917" +dependencies = [ + "smallvec", + "target-lexicon", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -5033,6 +5049,8 @@ dependencies = [ "cpal", "crossbeam-queue", "futures", + "gstreamer", + "gstreamer-audio", "libloading", "opus", "pretty_assertions", @@ -7241,6 +7259,19 @@ version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" +[[package]] +name = "gio-sys" +version = "0.22.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353fdc7da7cd16da916104b1e0e4e7de380ec9c8aaa20d4d742d66310ab4b0d5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "windows-sys 0.61.2", +] + [[package]] name = "gix" version = "0.81.0" @@ -7303,7 +7334,7 @@ dependencies = [ "bstr", "gix-date", "gix-error", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -7418,7 +7449,7 @@ dependencies = [ "smallvec", "thiserror 2.0.18", "unicode-bom", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -7712,7 +7743,7 @@ dependencies = [ "itoa", "smallvec", "thiserror 2.0.18", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -7810,7 +7841,7 @@ dependencies = [ "maybe-async", "nonempty", "thiserror 2.0.18", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -7842,7 +7873,7 @@ dependencies = [ "gix-validate", "memmap2", "thiserror 2.0.18", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -8096,6 +8127,49 @@ dependencies = [ "parking_lot", ] +[[package]] +name = "glib" +version = "0.22.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddbcf514bd1881fc1b960e4e52b4e82873f4da3bceddbd58d42827b508888100" +dependencies = [ + "bitflags 2.13.1", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "smallvec", +] + +[[package]] +name = "glib-macros" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "506d23499707c7142898429757e8d9a3871d965239a2cb66dfa05052be6d6f19" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.22.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "030967459f9f676851872c6304adea7825c6d462ec9b72554c733cf0c5952233" +dependencies = [ + "libc", + "system-deps", +] + [[package]] name = "glob" version = "0.3.3" @@ -8115,6 +8189,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "gobject-sys" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a861859b887a79cf461359c192c97a57d8fb0229dd291232e57aa11f6fa72c" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + [[package]] name = "group" version = "0.13.0" @@ -8126,6 +8211,100 @@ dependencies = [ "subtle", ] +[[package]] +name = "gstreamer" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4527e1b9bae8d29ce137bde5b8eec8ae8f78f13ad00fc6e70cbe227d6ad027" +dependencies = [ + "cfg-if", + "futures-channel", + "futures-core", + "futures-util", + "glib", + "gstreamer-sys", + "itertools 0.15.0", + "kstring", + "libc", + "muldiv", + "num-integer", + "num-rational", + "option-operations", + "pastey", + "pin-project-lite", + "smallvec", + "thiserror 2.0.18", +] + +[[package]] +name = "gstreamer-audio" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97532c3e21287a6e94563a328cce89367324acf8a4489291b40ede2e39163a0" +dependencies = [ + "cfg-if", + "glib", + "gstreamer", + "gstreamer-audio-sys", + "gstreamer-base", + "libc", + "smallvec", +] + +[[package]] +name = "gstreamer-audio-sys" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2abfb81a073c5c8fb115a3639f47b6430f669ee9fa29123bb0116c9ef59d7d16" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-base-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-base" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91c94a4d3047d05dd6e1f6d91c74f61f56384c7ea1c9d0c1051572eeeb0138d" +dependencies = [ + "atomic_refcell", + "cfg-if", + "glib", + "gstreamer", + "gstreamer-base-sys", + "libc", +] + +[[package]] +name = "gstreamer-base-sys" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fbbc623dc066908ba10c43d629c21096508dea04796592a206c4edd864e37" +dependencies = [ + "glib-sys", + "gobject-sys", + "gstreamer-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gstreamer-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533fa8d28fc830eafccbcfcfddb390563ea5d3a351af2c3aab99e197e5f5b1ba" +dependencies = [ + "cfg-if", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + [[package]] name = "gzip-header" version = "1.0.0" @@ -9179,6 +9358,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.17" @@ -9907,6 +10095,12 @@ dependencies = [ "pxfm", ] +[[package]] +name = "muldiv" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956787520e75e9bd233246045d19f42fb73242759cc57fba9611d940ae96d4b0" + [[package]] name = "multimap" version = "0.10.1" @@ -10762,6 +10956,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "option-operations" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca39cf52b03268400c16eeb9b56382ea3c3353409309b63f5c8f0b1faf42754" +dependencies = [ + "pastey", +] + [[package]] name = "opus" version = "0.4.0" @@ -14783,6 +14986,19 @@ dependencies = [ "libc", ] +[[package]] +name = "system-deps" +version = "7.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396a35feb67335377e0251fcbc1092fc85c484bd4e3a7a54319399da127796e7" +dependencies = [ + "cfg-expr", + "heck", + "pkg-config", + "toml 1.0.6+spec-1.1.0", + "version-compare", +] + [[package]] name = "tagptr" version = "0.2.0" @@ -14805,6 +15021,12 @@ dependencies = [ "libc", ] +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "tempfile" version = "3.27.0" @@ -15357,10 +15579,25 @@ dependencies = [ "indexmap 2.14.0", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.14", +] + +[[package]] +name = "toml" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "399b1124a3c9e16766831c6bba21e50192572cdd98706ea114f9502509686ffc" +dependencies = [ + "indexmap 2.14.0", + "serde_core", + "serde_spanned", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", ] [[package]] @@ -15372,6 +15609,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -15379,9 +15625,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ "indexmap 2.14.0", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", - "winnow", + "winnow 0.7.14", ] [[package]] @@ -15391,19 +15637,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c740b185920170a6d9191122cafef7010bd6270a3824594bff6784c04d7f09e" dependencies = [ "indexmap 2.14.0", - "toml_datetime", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow", + "winnow 0.7.14", ] [[package]] name = "toml_parser" -version = "1.0.6+spec-1.1.0" +version = "1.1.3+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" dependencies = [ - "winnow", + "winnow 1.0.4", ] [[package]] @@ -16097,6 +16343,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + [[package]] name = "version_check" version = "0.9.5" @@ -17073,6 +17325,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + [[package]] name = "winreg" version = "0.10.1" diff --git a/codex-rs/realtime-webrtc/src/client.rs b/codex-rs/realtime-webrtc/src/client.rs index dc35f137a5..371c51bdfc 100644 --- a/codex-rs/realtime-webrtc/src/client.rs +++ b/codex-rs/realtime-webrtc/src/client.rs @@ -151,7 +151,8 @@ impl VoiceHost { build_commit: build_commit.to_owned(), }, Message::Ready {}, - DEADLINE, + // Startup-linked native libraries load before the helper can acknowledge Hello. + RUNTIME_INITIALIZATION_DEADLINE, ) .await?; Ok(host) diff --git a/codex-rs/voice-host/Cargo.toml b/codex-rs/voice-host/Cargo.toml index e612f17c02..9cebfef44d 100644 --- a/codex-rs/voice-host/Cargo.toml +++ b/codex-rs/voice-host/Cargo.toml @@ -8,6 +8,13 @@ license.workspace = true name = "codex-voice-host" path = "src/main.rs" +# These tests need a correctly linked helper and a matching prepared runtime. +# Bazel supplies declared runtime inputs and runs this target automatically. +# Cargo runs it explicitly with --test installed_client. +[[test]] +name = "installed_client" +test = false + [lints] workspace = true @@ -24,6 +31,8 @@ webrtc = "=0.20.3" [target.'cfg(any(target_os = "macos", all(target_os = "linux", target_env = "gnu"), all(windows, target_env = "msvc")))'.dependencies] cpal = "=0.18.2" crossbeam-queue = "0.3.12" +gstreamer = { version = "=0.25.3", features = ["v1_28"] } +gstreamer-audio = { version = "=0.25.3", features = ["v1_28"] } opus = "=0.4.0" rubato = { version = "=5.0.0", default-features = false } sonora = { version = "=0.2.0", default-features = false } diff --git a/codex-rs/voice-host/src/audio_sink.rs b/codex-rs/voice-host/src/audio_sink.rs new file mode 100644 index 0000000000..6d43fdc081 --- /dev/null +++ b/codex-rs/voice-host/src/audio_sink.rs @@ -0,0 +1,110 @@ +//! Stock GstAudioSink scheduling with a bounded, epoch-owned CPAL writer. +//! Native resets outside an owner-initiated teardown fail the session closed. + +use super::playback::PlaybackWriter; +use audio::subclass::prelude::*; +use gst::glib; +use gstreamer as gst; +use gstreamer_audio as audio; +use std::sync::OnceLock; + +mod imp { + use super::*; + #[derive(Default)] + pub struct Sink { + pub(super) writer: OnceLock, + } + #[glib::object_subclass] + impl ObjectSubclass for Sink { + const NAME: &'static str = "CodexPrivateAudioSink"; + type Type = super::Sink; + type ParentType = audio::AudioSink; + } + impl ObjectImpl for Sink {} + impl GstObjectImpl for Sink {} + impl ElementImpl for Sink { + fn metadata() -> Option<&'static gst::subclass::ElementMetadata> { + static META: OnceLock = OnceLock::new(); + Some(META.get_or_init(|| { + gst::subclass::ElementMetadata::new( + "Private audio", + "Sink/Audio", + "Owned speaker output", + "OpenAI", + ) + })) + } + fn pad_templates() -> &'static [gst::PadTemplate] { + static PADS: OnceLock> = OnceLock::new(); + PADS.get_or_init(|| { + gst::PadTemplate::new( + "sink", + gst::PadDirection::Sink, + gst::PadPresence::Always, + &gst::Caps::builder("audio/x-raw") + .field("format", "F32LE") + .field("layout", "interleaved") + .field("channels", 1i32) + .field( + "rate", + gst::IntRange::::new(/*min*/ 8000, /*max*/ 384000), + ) + .build(), + ) + .into_iter() + .collect() + }) + } + } + impl BaseSinkImpl for Sink {} + impl AudioBaseSinkImpl for Sink {} + impl AudioSinkImpl for Sink { + fn prepare(&self, spec: &mut audio::AudioRingBufferSpec) -> Result<(), gst::LoggableError> { + let writer = self + .writer + .get() + .ok_or_else(|| gst::loggable_error!(gst::CAT_RUST, "speaker not bound"))?; + let info = spec.audio_info(); + if info.rate() != writer.rate() + || info.channels() != 1 + || info.format() != audio::AudioFormat::F32le + || info.layout() != audio::AudioLayout::Interleaved + { + return Err(gst::loggable_error!( + gst::CAT_RUST, + "unsupported speaker format" + )); + } + spec.set_segsize((writer.rate() / 100 * 4) as i32); + spec.set_segtotal(4); + spec.set_latency_time(10_000); + spec.set_buffer_time(40_000); + Ok(()) + } + fn unprepare(&self) -> Result<(), gst::LoggableError> { + Ok(()) + } + fn write(&self, bytes: &[u8]) -> Result { + let writer = self + .writer + .get() + .ok_or_else(|| gst::loggable_error!(gst::CAT_RUST, "speaker not bound"))?; + writer.write(bytes).map(|n| n as i32).map_err(|_| { + writer.fail_if_current(); + gst::loggable_error!(gst::CAT_RUST, "speaker write failed") + }) + } + fn delay(&self) -> u32 { + self.writer.get().map_or(0, PlaybackWriter::delay) + } + fn reset(&self) { + if let Some(writer) = self.writer.get() { + writer.fail_if_current(); + } + } + } +} + +glib::wrapper! { + pub struct Sink(ObjectSubclass) @extends audio::AudioSink, audio::AudioBaseSink, audio::gst_base::BaseSink, gst::Element, gst::Object; +} diff --git a/codex-rs/voice-host/src/device_buffers.rs b/codex-rs/voice-host/src/device_buffers.rs index c6112c0480..b65b1cedc4 100644 --- a/codex-rs/voice-host/src/device_buffers.rs +++ b/codex-rs/voice-host/src/device_buffers.rs @@ -5,6 +5,7 @@ use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicU16; +use std::sync::atomic::AtomicU32; use std::sync::atomic::AtomicU64; use std::sync::atomic::Ordering; use std::time::Duration; @@ -96,6 +97,9 @@ pub(super) struct Buffers { pub(super) failed: AtomicBool, pub(super) microphone_peak: AtomicU16, pub(super) speaker_peak: AtomicU16, + pub(super) queued: AtomicU32, + pub(super) last_dac_ns: AtomicU64, + pub(super) clock: Instant, } impl Buffers { @@ -129,9 +133,20 @@ impl Buffers { failed: AtomicBool::new(false), microphone_peak: AtomicU16::new(/*v*/ 0), speaker_peak: AtomicU16::new(/*v*/ 0), + queued: AtomicU32::new(/*v*/ 0), + last_dac_ns: AtomicU64::new(/*v*/ 0), + clock: Instant::now(), } } + pub(super) fn push_playback(&self, frame: Frame) -> Result<(), ()> { + let len = frame.len as u32; + self.queued.fetch_add(len, Ordering::AcqRel); + self.playback.push(frame).map_err(|_| { + self.queued.fetch_sub(len, Ordering::AcqRel); + }) + } + // One control worker writes each epoch. Odd epochs are disabled; every // transition advances the epoch so disable/re-enable cannot replay old audio. pub(super) fn set_disabled(epoch: &AtomicU64, disabled: bool) -> std::io::Result<()> { @@ -190,13 +205,19 @@ pub(super) struct Playback { } impl Playback { - pub(super) fn next(&mut self, buffers: &Buffers) -> f32 { + pub(super) fn next(&mut self, buffers: &Buffers) -> Option { let epoch = buffers.speaker.load(Ordering::Acquire); if self .frame .as_ref() .is_some_and(|frame| frame.generation != epoch || self.offset == frame.len) { + if let Some(frame) = &self.frame { + buffers.queued.fetch_sub( + frame.len.saturating_sub(self.offset) as u32, + Ordering::AcqRel, + ); + } self.frame = None; } // Bound stale-frame work even if a producer keeps writing during a mute. @@ -214,17 +235,20 @@ impl Playback { { self.frame = Some(frame); self.offset = 0; + } else { + buffers.queued.fetch_sub(frame.len as u32, Ordering::AcqRel); } } let Some(frame) = &self.frame else { - return 0.0; + return None; }; let sample = frame.samples[self.offset]; self.offset += 1; + buffers.queued.fetch_sub(/*val*/ 1, Ordering::AcqRel); if epoch % 2 == 1 || !sample.is_finite() { - 0.0 + Some(0.0) } else { - sample.clamp(-1.0, 1.0) + Some(sample.clamp(-1.0, 1.0)) } } } diff --git a/codex-rs/voice-host/src/device_buffers_tests.rs b/codex-rs/voice-host/src/device_buffers_tests.rs index c1244d7d45..ab6b295d0a 100644 --- a/codex-rs/voice-host/src/device_buffers_tests.rs +++ b/codex-rs/voice-host/src/device_buffers_tests.rs @@ -48,8 +48,7 @@ fn suppression_discards_partial_and_queued_previous_generations() { for _ in 0..2 { assert!( buffers - .playback - .push(Frame { + .push_playback(Frame { samples: [0.5; BLOCK], len: BLOCK, at: Instant::now(), @@ -58,11 +57,11 @@ fn suppression_discards_partial_and_queued_previous_generations() { .is_ok() ); } - assert_eq!(playback.next(&buffers), 0.5); + assert_eq!(playback.next(&buffers).unwrap_or(0.0), 0.5); Buffers::set_disabled(&buffers.speaker, /*disabled*/ true).unwrap(); - assert_eq!(playback.next(&buffers), 0.0); + assert_eq!(playback.next(&buffers).unwrap_or(0.0), 0.0); Buffers::set_disabled(&buffers.speaker, /*disabled*/ false).unwrap(); - assert_eq!(playback.next(&buffers), 0.0); + assert_eq!(playback.next(&buffers).unwrap_or(0.0), 0.0); assert!(buffers.playback.is_empty()); } @@ -75,8 +74,7 @@ fn output_is_finite_and_bounded_and_underflow_is_silence() { samples[..4].copy_from_slice(&[f32::NAN, f32::INFINITY, -2.0, 2.0]); assert!( buffers - .playback - .push(Frame { + .push_playback(Frame { samples, len: 4, at: Instant::now(), @@ -85,7 +83,9 @@ fn output_is_finite_and_bounded_and_underflow_is_silence() { .is_ok() ); let mut playback = Playback::default(); - let output: Vec<_> = (0..5).map(|_| playback.next(&buffers)).collect(); + let output: Vec<_> = (0..5) + .map(|_| playback.next(&buffers).unwrap_or(0.0)) + .collect(); assert_eq!(output, [0.0, 0.0, -1.0, 1.0, 0.0]); } diff --git a/codex-rs/voice-host/src/devices.rs b/codex-rs/voice-host/src/devices.rs index 6143271e5c..ff5a09055c 100644 --- a/codex-rs/voice-host/src/devices.rs +++ b/codex-rs/voice-host/src/devices.rs @@ -3,10 +3,14 @@ //! Capture and actual rendered output carry device timing. //! References start with worker service; unmute rejects earlier device capture buffers. +#[path = "audio_sink.rs"] +mod audio_sink; #[path = "device_buffers.rs"] mod buffers; #[path = "capture_worker.rs"] mod capture_worker; +#[path = "playback.rs"] +mod playback; #[path = "processing.rs"] mod processing; @@ -33,12 +37,17 @@ use buffers::FramePacker; use buffers::MAX_CALLBACK_FRAMES; use buffers::Playback; use buffers::QUEUE_CAPACITY; +use playback::PlaybackPort; const MAX_CAPTURE_AGE: Duration = Duration::from_secs(/*secs*/ 1); pub(super) struct Devices { _input: cpal::Stream, - _output: cpal::Stream, + output: Option, + output_device: cpal::Device, + output_config: cpal::SupportedStreamConfig, + output_stream_config: cpal::StreamConfig, + playback: PlaybackPort, worker: capture_worker::CaptureWorker, } @@ -93,6 +102,7 @@ impl Devices { input_config.sample_rate(), output_config.sample_rate(), )); + let playback = PlaybackPort::new(buffers.clone(), output_config.sample_rate()); let processor = processing::Processor::new(input_config.sample_rate(), output_config.sample_rate()) .map_err(io::Error::other)?; @@ -113,7 +123,7 @@ impl Devices { buffers.clone() ) .map_err(|_| io::Error::other("failed to open microphone"))?; - let output = stream!( + let output_stream = stream!( output_config.sample_format(), build_output, &output, @@ -121,7 +131,7 @@ impl Devices { buffers.clone() ) .map_err(|_| io::Error::other("failed to open speaker"))?; - output + output_stream .play() .map_err(|_| io::Error::other("failed to start speaker"))?; input @@ -129,7 +139,11 @@ impl Devices { .map_err(|_| io::Error::other("failed to start microphone"))?; Ok(Self { _input: input, - _output: output, + output: Some(output_stream), + output_device: output, + output_config, + output_stream_config, + playback, worker: capture_worker::CaptureWorker { buffers, processor, @@ -142,7 +156,37 @@ impl Devices { &mut self, controls: codex_realtime_webrtc::AudioControls, ) -> io::Result<()> { - self.worker.set_controls(controls) + let buffers = self.worker.buffers.clone(); + let previous = buffers.speaker.load(Ordering::Acquire); + self.worker.set_controls(controls)?; + if previous != buffers.speaker.load(Ordering::Acquire) { + // Invalidate writers first, then wait off the callback for any in-flight write. + let _producer = self + .playback + .0 + .producer + .lock() + .map_err(|_| io::Error::other("speaker writer failed"))?; + // Dropping the stream stops its callbacks and discards device-owned audio. + drop(self.output.take()); + while buffers.playback.pop().is_some() {} + while buffers.rendered.pop().is_some() {} + buffers.queued.store(/*val*/ 0, Ordering::Release); + buffers.last_dac_ns.store(/*val*/ 0, Ordering::Release); + let output = stream!( + self.output_config.sample_format(), + build_output, + &self.output_device, + &self.output_stream_config, + buffers + ) + .map_err(|_| io::Error::other("failed to reset speaker"))?; + output + .play() + .map_err(|_| io::Error::other("failed to restart speaker"))?; + self.output = Some(output); + } + Ok(()) } pub(super) async fn service( @@ -153,6 +197,14 @@ impl Devices { } } +impl Drop for Devices { + fn drop(&mut self) { + self.worker.buffers.failed.store(true, Ordering::Release); + let _producer = self.playback.0.producer.lock().ok(); + drop(self.output.take()); + } +} + fn bounded_stream_config( supported: &cpal::SupportedStreamConfig, ) -> io::Result { @@ -322,6 +374,7 @@ fn render_output( data.fill(T::from_sample(0.0)); return; } + let mut delivered_until = None; for (index, chunk) in data.chunks_mut(BLOCK * channels).enumerate() { let mut reference = Frame { samples: [0.0; BLOCK], @@ -329,8 +382,18 @@ fn render_output( at: start + Duration::from_secs_f64((index * BLOCK) as f64 / rate), generation: buffers.speaker.load(Ordering::Acquire), }; - for (frame, sample) in chunk.chunks_mut(channels).zip(&mut reference.samples) { - let rendered = T::from_sample(output.playback.next(buffers)); + for (offset, (frame, sample)) in chunk + .chunks_mut(channels) + .zip(&mut reference.samples) + .enumerate() + { + let next = output.playback.next(buffers); + if next.is_some() { + delivered_until = Some( + start + Duration::from_secs_f64((index * BLOCK + offset + 1) as f64 / rate), + ); + } + let rendered = T::from_sample(next.unwrap_or(0.0)); frame.fill(rendered); *sample = f32::from_sample(rendered); record_peak(&buffers.speaker_peak, *sample); @@ -339,6 +402,14 @@ fn render_output( buffers.failed.store(true, Ordering::Release); } } + if let Some(end) = delivered_until { + buffers.last_dac_ns.store( + end.saturating_duration_since(buffers.clock) + .as_nanos() + .min(u128::from(u64::MAX)) as u64, + Ordering::Release, + ); + } } #[cfg(test)] diff --git a/codex-rs/voice-host/src/devices_tests.rs b/codex-rs/voice-host/src/devices_tests.rs index a8a868fe04..f313fe61f1 100644 --- a/codex-rs/voice-host/src/devices_tests.rs +++ b/codex-rs/voice-host/src/devices_tests.rs @@ -235,8 +235,7 @@ fn tiny_output_callbacks_pack_references_without_delaying_rendering() { for _ in 0..8 { assert!( buffers - .playback - .push(Frame { + .push_playback(Frame { samples: [0.5; BLOCK], len: BLOCK, at: start, diff --git a/codex-rs/voice-host/src/playback.rs b/codex-rs/voice-host/src/playback.rs new file mode 100644 index 0000000000..618a6bc865 --- /dev/null +++ b/codex-rs/voice-host/src/playback.rs @@ -0,0 +1,114 @@ +//! Bounded mono F32LE writes for the native sink, never called from a device callback. +//! Each writer belongs to one speaker epoch. Reset cancels it before locking the producer. + +use super::buffers::BLOCK; +use super::buffers::Buffers; +use super::buffers::Frame; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::atomic::Ordering; +use std::time::Duration; +use std::time::Instant; + +pub(super) struct PlaybackState { + pub(super) producer: Mutex<()>, + buffers: Arc, + rate: u32, +} + +pub(super) struct PlaybackPort(pub(super) Arc); + +pub(crate) struct PlaybackWriter { + state: Arc, + epoch: u64, +} + +impl PlaybackPort { + pub(super) fn new(buffers: Arc, rate: u32) -> Self { + Self(Arc::new(PlaybackState { + producer: Mutex::new(()), + buffers, + rate, + })) + } +} + +impl PlaybackWriter { + pub(crate) fn fail_if_current(&self) { + if self.state.buffers.speaker.load(Ordering::Acquire) == self.epoch { + self.state.buffers.failed.store(true, Ordering::Release); + } + } + + pub(crate) fn rate(&self) -> u32 { + self.state.rate + } + + pub(crate) fn write(&self, bytes: &[u8]) -> Result { + if bytes.is_empty() || !bytes.len().is_multiple_of(4) { + return Err("invalid speaker buffer"); + } + let mut frame = Frame { + samples: [0.0; BLOCK], + len: (bytes.len() / 4).min(BLOCK), + at: Instant::now(), + generation: self.epoch, + }; + for (sample, bytes) in frame.samples.iter_mut().zip(bytes.chunks_exact(4)) { + *sample = f32::from_le_bytes(bytes.try_into().map_err(|_| "invalid speaker buffer")?); + if !sample.is_finite() { + return Err("invalid speaker sample"); + } + } + let _producer = self + .state + .producer + .lock() + .map_err(|_| "speaker writer failed")?; + let deadline = Instant::now() + Duration::from_millis(/*millis*/ 100); + let buffers = &self.state.buffers; + let limit = (self.state.rate / 25).min((BLOCK * buffers.playback.capacity()) as u32); + loop { + if self.epoch % 2 == 1 + || buffers.speaker.load(Ordering::Acquire) != self.epoch + || buffers.failed.load(Ordering::Acquire) + { + return Err("speaker writer cancelled"); + } + if buffers.queued.load(Ordering::Acquire) + frame.len as u32 <= limit + && !buffers.playback.is_full() + { + let bytes = frame.len * 4; + buffers + .push_playback(frame) + .map_err(|_| "speaker queue failed")?; + return Ok(bytes); + } + if Instant::now() >= deadline { + return Err("speaker fell behind"); + } + std::thread::park_timeout(Duration::from_millis(/*millis*/ 1)); + } + } + + pub(crate) fn delay(&self) -> u32 { + let buffers = &self.state.buffers; + if buffers.speaker.load(Ordering::Acquire) != self.epoch || self.epoch % 2 == 1 { + return 0; + } + let remaining = buffers + .last_dac_ns + .load(Ordering::Acquire) + .saturating_sub(buffers.clock.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64); + let hardware = + (u128::from(remaining) * u128::from(self.state.rate)).div_ceil(1_000_000_000); + buffers + .queued + .load(Ordering::Acquire) + .saturating_add(hardware.min(u128::from(u32::MAX)) as u32) + } +} + +#[cfg(test)] +#[path = "playback_tests.rs"] +mod tests; diff --git a/codex-rs/voice-host/src/playback_tests.rs b/codex-rs/voice-host/src/playback_tests.rs new file mode 100644 index 0000000000..6ed06d778f --- /dev/null +++ b/codex-rs/voice-host/src/playback_tests.rs @@ -0,0 +1,88 @@ +use super::*; +use crate::devices::buffers::Playback; +use pretty_assertions::assert_eq; + +impl PlaybackPort { + pub(super) fn writer(&self) -> PlaybackWriter { + PlaybackWriter { + state: self.0.clone(), + epoch: self.0.buffers.speaker.load(Ordering::Acquire), + } + } +} + +fn active(rate: u32) -> (Arc, PlaybackPort, PlaybackWriter) { + let buffers = Arc::new(Buffers::new(rate, rate)); + Buffers::set_disabled(&buffers.speaker, /*disabled*/ false).unwrap(); + let port = PlaybackPort::new(buffers.clone(), rate); + let writer = port.writer(); + (buffers, port, writer) +} + +#[test] +fn partial_writes_account_for_samples_until_the_device_consumes_them() { + let (buffers, _, writer) = active(/*rate*/ 48000); + let samples: Vec<_> = (0..BLOCK + 20).map(|i| i as f32 / 512.0).collect(); + let bytes: Vec<_> = samples.iter().flat_map(|v| v.to_le_bytes()).collect(); + let first = writer.write(&bytes).unwrap(); + assert_eq!(first, BLOCK * 4); + assert_eq!(writer.write(&bytes[first..]).unwrap(), 20 * 4); + assert_eq!( + (writer.rate(), writer.delay()), + (48000, (BLOCK + 20) as u32) + ); + let mut playback = Playback::default(); + let actual: Vec<_> = (0..samples.len()) + .map(|_| playback.next(&buffers).unwrap()) + .collect(); + assert_eq!(actual, samples); + assert_eq!(writer.delay(), 0); + assert_eq!(playback.next(&buffers), None); +} + +#[test] +fn suppression_cancels_a_full_writer_and_old_writers_cannot_resume() { + let (buffers, port, writer) = active(/*rate*/ 8000); + let bytes = vec![0; BLOCK * 4]; + writer.write(&bytes).unwrap(); + let waiting = std::thread::spawn(move || writer.write(&bytes)); + Buffers::set_disabled(&buffers.speaker, /*disabled*/ true).unwrap(); + assert_eq!(waiting.join().unwrap(), Err("speaker writer cancelled")); + let stale = port.writer(); + Buffers::set_disabled(&buffers.speaker, /*disabled*/ false).unwrap(); + assert_eq!(stale.write(&[0; 4]), Err("speaker writer cancelled")); + let mut playback = Playback::default(); + assert_eq!(playback.next(&buffers), None); + assert_eq!(buffers.queued.load(Ordering::Acquire), 0); + assert_eq!(port.writer().write(&[0; 4]), Ok(4)); +} + +#[test] +fn delay_includes_pending_dac_time_but_not_another_generation() { + let (buffers, _, writer) = active(/*rate*/ 48000); + writer.write(&[0; 40]).unwrap(); + let end = buffers.clock.elapsed() + Duration::from_millis(/*millis*/ 100); + buffers + .last_dac_ns + .store(end.as_nanos() as u64, Ordering::Release); + let delay = writer.delay(); + assert!((10..=4810).contains(&delay)); + assert!(delay > 10); + Buffers::set_disabled(&buffers.speaker, /*disabled*/ true).unwrap(); + assert_eq!(writer.delay(), 0); +} + +#[test] +fn invalid_samples_and_stalled_consumption_fail_without_growing_the_queue() { + let (buffers, _, writer) = active(/*rate*/ 8000); + assert_eq!( + writer.write(&f32::NAN.to_le_bytes()), + Err("invalid speaker sample") + ); + writer.write(&vec![0; BLOCK * 4]).unwrap(); + assert_eq!( + writer.write(&vec![0; BLOCK * 4]), + Err("speaker fell behind") + ); + assert_eq!(buffers.queued.load(Ordering::Acquire), BLOCK as u32); +} diff --git a/codex-rs/voice-host/tests/installed_client.rs b/codex-rs/voice-host/tests/installed_client.rs new file mode 100644 index 0000000000..13531ab009 --- /dev/null +++ b/codex-rs/voice-host/tests/installed_client.rs @@ -0,0 +1,363 @@ +//! Exercise the production client against copied helper packages and startup libraries. + +use std::fs; +use std::path::Path; +use std::path::PathBuf; +use std::sync::Arc; +use std::time::Duration; +use std::time::Instant; + +use anyhow::Context; +use anyhow::Result; +use codex_install_context::CodexPackageLayout; +use codex_install_context::InstallContext; +use codex_realtime_webrtc::SessionDescription; +use codex_realtime_webrtc::VoiceHost; +use codex_utils_cargo_bin::cargo_bin; +use futures::future::BoxFuture; +use pretty_assertions::assert_eq; +use tokio::process::Command; +use tokio::sync::Notify; +use tokio::sync::mpsc; +use tokio::time::timeout; +use webrtc::data_channel::DataChannel; +use webrtc::peer_connection::MediaEngine; +use webrtc::peer_connection::PeerConnection; +use webrtc::peer_connection::PeerConnectionBuilder; +use webrtc::peer_connection::PeerConnectionEventHandler; +use webrtc::peer_connection::RTCIceConnectionState; +use webrtc::peer_connection::RTCIceGatheringState; +use webrtc::peer_connection::RTCPeerConnectionState; +use webrtc::peer_connection::RTCSessionDescription; +use webrtc::peer_connection::SettingEngine; + +const DEADLINE: Duration = Duration::from_secs(/*secs*/ 10); + +async fn build_commit() -> Result { + let output = timeout( + DEADLINE, + Command::new(cargo_bin("codex-voice-host")?) + .arg("--build-commit") + .kill_on_drop(true) + .output(), + ) + .await??; + assert!(output.status.success()); + Ok(String::from_utf8(output.stdout)?.trim().to_owned()) +} + +fn install_startup_libraries(runtime: &Path) -> Result<()> { + if !cfg!(any( + target_os = "macos", + all(target_os = "linux", target_env = "gnu"), + all(windows, target_env = "msvc") + )) { + return Ok(()); + } + let source = if codex_utils_cargo_bin::runfiles_available() { + let resource = format!( + "../../third_party/voice/native_link_{}_{}/runtime.json", + std::env::consts::OS, + std::env::consts::ARCH + ); + codex_utils_cargo_bin::find_resource!(resource)? + .parent() + .context("runtime parent")? + .to_owned() + } else { + std::env::var_os("CODEX_TEST_VOICE_RUNTIME") + .map(PathBuf::from) + .context("prepared runtime required for installed helper tests")? + }; + let libraries = if cfg!(windows) { "bin" } else { "lib" }; + let destination = runtime.join(libraries); + fs::create_dir_all(&destination)?; + for entry in fs::read_dir(source.join(libraries))? { + let entry = entry?; + if entry.path().is_file() { + fs::copy(entry.path(), destination.join(entry.file_name()))?; + } + } + Ok(()) +} + +fn install_helper(root: &Path) -> Result { + let bin = root.join("bin"); + let helper_dir = root.join("codex-resources/voice/bin"); + fs::create_dir_all(&bin)?; + fs::create_dir_all(&helper_dir)?; + fs::write(root.join("codex-package.json"), "{}")?; + let app = bin.join(if cfg!(windows) { "codex.exe" } else { "codex" }); + fs::write(&app, [])?; + let source = cargo_bin("codex-voice-host")?; + let helper = helper_dir.join(source.file_name().context("helper binary file name")?); + fs::copy(&source, &helper)?; + install_startup_libraries(helper_dir.parent().context("helper runtime directory")?)?; + InstallContext::from_exe( + /*is_macos*/ cfg!(target_os = "macos"), + Some(&app), + /*method_override*/ None, + ) + .package_layout + .context("package layout") +} + +#[tokio::test] +async fn installed_client_rejects_mixed_builds_and_missing_helper() -> Result<()> { + let directory = tempfile::Builder::new() + .prefix("voice package ") + .tempdir()?; + let package = install_helper(directory.path())?; + let bin = directory.path().join("bin"); + let source = cargo_bin("codex-voice-host")?; + let helper = directory + .path() + .join("codex-resources/voice/bin") + .join(source.file_name().context("helper binary file name")?); + VoiceHost::connect(&package, &build_commit().await?) + .await? + .close() + .await?; + assert!(VoiceHost::connect(&package, "wrong-build").await.is_err()); + // Startup libraries permit handshakes; the missing runtime receipt prevents readiness. + let host = VoiceHost::connect(&package, &build_commit().await?).await?; + assert!(host.initialize_runtime().await.is_err()); + // The same executable elsewhere in the package must not become a fallback. + fs::rename(&helper, bin.join(source.file_name().unwrap()))?; + assert!( + VoiceHost::connect(&package, &build_commit().await?) + .await + .is_err() + ); + #[cfg(unix)] + { + std::os::unix::fs::symlink(&source, &helper)?; + assert!( + VoiceHost::connect(&package, &build_commit().await?) + .await + .is_err() + ); + } + Ok(()) +} + +// Linux permits raw-byte filenames; macOS filesystems reject this name themselves. +#[cfg(target_os = "linux")] +#[tokio::test] +async fn installed_client_accepts_non_utf8_package_path() -> Result<()> { + use std::ffi::OsString; + use std::os::unix::ffi::OsStringExt; + + let directory = tempfile::tempdir()?; + let root = directory + .path() + .join(OsString::from_vec(b"voice-\xff".to_vec())); + let bin = root.join("bin"); + let helper_dir = root.join("codex-resources/voice/bin"); + fs::create_dir_all(&bin)?; + fs::create_dir_all(&helper_dir)?; + fs::write(root.join("codex-package.json"), "{}")?; + let app = bin.join("codex"); + fs::write(&app, [])?; + let source = cargo_bin("codex-voice-host")?; + fs::copy(&source, helper_dir.join(source.file_name().unwrap()))?; + install_startup_libraries(helper_dir.parent().unwrap())?; + let package = InstallContext::from_exe( + /*is_macos*/ cfg!(target_os = "macos"), + Some(&app), + /*method_override*/ None, + ) + .package_layout + .context("package layout")?; + VoiceHost::connect(&package, &build_commit().await?) + .await? + .close() + .await +} + +enum RemoteState { + Ice(RTCIceConnectionState), + Peer(RTCPeerConnectionState), + Channel, +} + +struct RemoteEvents { + channels: mpsc::Sender>, + gathered: Arc, + diagnostics: mpsc::Sender<(Duration, RemoteState)>, + started: Instant, +} + +impl RemoteEvents { + fn record(&self, state: RemoteState) { + // Keep only the first 32 events; diagnostics must never wait on the connection driver. + let _ = self.diagnostics.try_send((self.started.elapsed(), state)); + } +} + +impl PeerConnectionEventHandler for RemoteEvents { + fn on_ice_connection_state_change<'a, 'async_trait>( + &'a self, + state: RTCIceConnectionState, + ) -> BoxFuture<'async_trait, ()> + where + 'a: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { self.record(RemoteState::Ice(state)) }) + } + + fn on_connection_state_change<'a, 'async_trait>( + &'a self, + state: RTCPeerConnectionState, + ) -> BoxFuture<'async_trait, ()> + where + 'a: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { self.record(RemoteState::Peer(state)) }) + } + + fn on_data_channel<'a, 'async_trait>( + &'a self, + channel: Arc, + ) -> BoxFuture<'async_trait, ()> + where + 'a: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + self.record(RemoteState::Channel); + assert!( + self.channels.try_send(channel).is_ok(), + "receiver must accept the event channel" + ); + }) + } + + fn on_ice_gathering_state_change<'a, 'async_trait>( + &'a self, + state: RTCIceGatheringState, + ) -> BoxFuture<'async_trait, ()> + where + 'a: 'async_trait, + Self: 'async_trait, + { + Box::pin(async move { + if state == RTCIceGatheringState::Complete { + self.gathered.notify_one(); + } + }) + } +} + +#[tokio::test] +async fn installed_client_negotiates_and_closes_over_udp_and_tcp() -> Result<()> { + let directory = tempfile::Builder::new() + .prefix("voice package ") + .tempdir()?; + let package = install_helper(directory.path())?; + let commit = build_commit().await?; + for tcp in [false, true] { + let started = Instant::now(); + let (diagnostics, mut states) = mpsc::channel(/*buffer*/ 32); + let negotiation = timeout(Duration::from_secs(/*secs*/ 30), async { + let (sender, mut channels) = mpsc::channel(/*buffer*/ 1); + let gathered = Arc::new(Notify::new()); + let mut settings = SettingEngine::default(); + settings.set_lite(/*lite*/ true); + let mut media = MediaEngine::default(); + media.register_default_codecs()?; + let builder = PeerConnectionBuilder::new() + .with_media_engine(media) + .with_setting_engine(settings) + .with_handler(Arc::new(RemoteEvents { + channels: sender, + gathered: gathered.clone(), + diagnostics, + started, + })); + let remote = if tcp { + builder.with_tcp_addrs(vec!["0.0.0.0:0"]) + } else { + builder.with_udp_addrs(vec!["0.0.0.0:0"]) + } + .build() + .await?; + let result: Result<()> = async { + let host = VoiceHost::connect(&package, &commit).await?; + let (host, offer) = host.start_transport().await?; + remote + .set_remote_description(RTCSessionDescription::offer(offer.into_sdp())?) + .await?; + let answer = remote.create_answer(/*options*/ None).await?; + remote.set_local_description(answer).await?; + gathered.notified().await; + let answer = remote.local_description().await.context("local answer")?; + let host = host + .apply_answer( + SessionDescription::try_from(answer.sdp).map_err(anyhow::Error::msg)?, + ) + .await + .with_context(|| format!("tcp={tcp}: apply voice answer"))?; + let channel = channels.recv().await.context("event channel")?; + assert_eq!( + (channel.label().await?, channel.ordered().await?), + ("oai-events".into(), true) + ); + // This checks the Closed acknowledgement and successful child exit as well. + host.close().await + } + .await; + remote.close().await?; + result + }); + let mut heartbeat = tokio::time::interval(Duration::from_millis(/*millis*/ 100)); + heartbeat.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut ticks = 0_u32; + let mut last_poll = started; + let mut max_gap = Duration::ZERO; + let mut max_gap_at = Duration::ZERO; + let result = { + tokio::pin!(negotiation); + loop { + let outcome = tokio::select! { + result = &mut negotiation => Some(result), + _ = heartbeat.tick() => { + ticks += 1; + None + } + }; + // Include the final polling gap even when negotiation or its timeout wins. + let now = Instant::now(); + let gap = now.duration_since(last_poll); + if gap > max_gap { + max_gap = gap; + max_gap_at = now.duration_since(started); + } + last_poll = now; + if let Some(result) = outcome { + break result; + } + } + }; + result + .with_context(|| format!("tcp={tcp}: helper negotiation timed out")) + .and_then(std::convert::identity) + .with_context(|| { + let events: Vec<_> = std::iter::from_fn(|| states.try_recv().ok()) + .map(|(elapsed, state)| match state { + RemoteState::Ice(state) => format!("{elapsed:?}: ICE {state}"), + RemoteState::Peer(state) => format!("{elapsed:?}: peer {state}"), + RemoteState::Channel => format!("{elapsed:?}: channel arrived"), + }) + .collect(); + format!( + "tcp={tcp}: remote connection events (first 32): {events:?}; \ + test runtime heartbeat: ticks={ticks}, max polling gap={max_gap:?} \ + observed at {max_gap_at:?}" + ) + })?; + } + Ok(()) +} diff --git a/codex-rs/voice-host/tests/lifecycle.rs b/codex-rs/voice-host/tests/lifecycle.rs index ba2fe41a0b..a7c18fcdb9 100644 --- a/codex-rs/voice-host/tests/lifecycle.rs +++ b/codex-rs/voice-host/tests/lifecycle.rs @@ -1,41 +1,20 @@ -//! Exercise the real helper through installed paths and bounded process I/O. +//! Exercise the real helper protocol and bounded process I/O. -use std::fs; -use std::path::Path; use std::process::Stdio; -use std::sync::Arc; use std::time::Duration; -use std::time::Instant; use anyhow::Context; use anyhow::Result; -use codex_install_context::CodexPackageLayout; -use codex_install_context::InstallContext; use codex_realtime_webrtc::Message; -use codex_realtime_webrtc::SessionDescription; -use codex_realtime_webrtc::VoiceHost; use codex_realtime_webrtc::decode_frame; use codex_realtime_webrtc::encode_frame; use codex_utils_cargo_bin::cargo_bin; -use futures::future::BoxFuture; use pretty_assertions::assert_eq; use tokio::io::AsyncReadExt; use tokio::io::AsyncWriteExt; use tokio::process::Child; use tokio::process::Command; -use tokio::sync::Notify; -use tokio::sync::mpsc; use tokio::time::timeout; -use webrtc::data_channel::DataChannel; -use webrtc::peer_connection::MediaEngine; -use webrtc::peer_connection::PeerConnection; -use webrtc::peer_connection::PeerConnectionBuilder; -use webrtc::peer_connection::PeerConnectionEventHandler; -use webrtc::peer_connection::RTCIceConnectionState; -use webrtc::peer_connection::RTCIceGatheringState; -use webrtc::peer_connection::RTCPeerConnectionState; -use webrtc::peer_connection::RTCSessionDescription; -use webrtc::peer_connection::SettingEngine; const DEADLINE: Duration = Duration::from_secs(/*secs*/ 10); @@ -116,8 +95,7 @@ async fn closes_after_acknowledgement_and_on_parent_pipe_loss() -> Result<()> { } #[tokio::test] -async fn exits_after_parent_pipe_loss_following_transport_start_request() -> Result<()> { - // The binary unit test separately forces startup to block and checks watchdog termination. +async fn parent_pipe_loss_cancels_transport_startup() -> Result<()> { let mut child = spawn()?; handshake(&mut child).await?; child @@ -158,281 +136,3 @@ async fn rejects_invalid_input_without_echoing_it() -> Result<()> { } Ok(()) } -fn install_helper(root: &Path) -> Result { - let bin = root.join("bin"); - let helper_dir = root.join("codex-resources/voice/bin"); - fs::create_dir_all(&bin)?; - fs::create_dir_all(&helper_dir)?; - fs::write(root.join("codex-package.json"), "{}")?; - let app = bin.join(if cfg!(windows) { "codex.exe" } else { "codex" }); - fs::write(&app, [])?; - let source = cargo_bin("codex-voice-host")?; - let helper = helper_dir.join(source.file_name().context("helper binary file name")?); - fs::copy(&source, &helper)?; - InstallContext::from_exe( - /*is_macos*/ cfg!(target_os = "macos"), - Some(&app), - /*method_override*/ None, - ) - .package_layout - .context("package layout") -} - -#[tokio::test] -async fn installed_client_rejects_mixed_builds_and_missing_helper() -> Result<()> { - let directory = tempfile::Builder::new() - .prefix("voice package ") - .tempdir()?; - let package = install_helper(directory.path())?; - let bin = directory.path().join("bin"); - let source = cargo_bin("codex-voice-host")?; - let helper = directory - .path() - .join("codex-resources/voice/bin") - .join(source.file_name().unwrap()); - VoiceHost::connect(&package, &build_commit().await?) - .await? - .close() - .await?; - assert!(VoiceHost::connect(&package, "wrong-build").await.is_err()); - // Helper-only installations still handshake, but cannot claim native readiness. - let host = VoiceHost::connect(&package, &build_commit().await?).await?; - assert!(host.initialize_runtime().await.is_err()); - // The same executable elsewhere in the package must not become a fallback. - fs::rename(&helper, bin.join(source.file_name().unwrap()))?; - assert!( - VoiceHost::connect(&package, &build_commit().await?) - .await - .is_err() - ); - #[cfg(unix)] - { - std::os::unix::fs::symlink(&source, &helper)?; - assert!( - VoiceHost::connect(&package, &build_commit().await?) - .await - .is_err() - ); - } - Ok(()) -} - -enum RemoteState { - Ice(RTCIceConnectionState), - Peer(RTCPeerConnectionState), - Channel, -} - -struct RemoteEvents { - channels: mpsc::Sender>, - gathered: Arc, - diagnostics: mpsc::Sender<(Duration, RemoteState)>, - started: Instant, -} - -impl RemoteEvents { - fn record(&self, state: RemoteState) { - // Keep only the first 32 events; diagnostics must never wait on the connection driver. - let _ = self.diagnostics.try_send((self.started.elapsed(), state)); - } -} - -impl PeerConnectionEventHandler for RemoteEvents { - fn on_ice_connection_state_change<'a, 'async_trait>( - &'a self, - state: RTCIceConnectionState, - ) -> BoxFuture<'async_trait, ()> - where - 'a: 'async_trait, - Self: 'async_trait, - { - Box::pin(async move { self.record(RemoteState::Ice(state)) }) - } - - fn on_connection_state_change<'a, 'async_trait>( - &'a self, - state: RTCPeerConnectionState, - ) -> BoxFuture<'async_trait, ()> - where - 'a: 'async_trait, - Self: 'async_trait, - { - Box::pin(async move { self.record(RemoteState::Peer(state)) }) - } - - fn on_data_channel<'a, 'async_trait>( - &'a self, - channel: Arc, - ) -> BoxFuture<'async_trait, ()> - where - 'a: 'async_trait, - Self: 'async_trait, - { - Box::pin(async move { - self.record(RemoteState::Channel); - assert!( - self.channels.try_send(channel).is_ok(), - "receiver must accept the event channel" - ); - }) - } - - fn on_ice_gathering_state_change<'a, 'async_trait>( - &'a self, - state: RTCIceGatheringState, - ) -> BoxFuture<'async_trait, ()> - where - 'a: 'async_trait, - Self: 'async_trait, - { - Box::pin(async move { - if state == RTCIceGatheringState::Complete { - self.gathered.notify_one(); - } - }) - } -} - -#[tokio::test] -async fn installed_client_negotiates_and_closes_over_udp_and_tcp() -> Result<()> { - let directory = tempfile::Builder::new() - .prefix("voice package ") - .tempdir()?; - let package = install_helper(directory.path())?; - let commit = build_commit().await?; - for tcp in [false, true] { - let started = Instant::now(); - let (diagnostics, mut states) = mpsc::channel(/*buffer*/ 32); - let negotiation = timeout(Duration::from_secs(/*secs*/ 30), async { - let (sender, mut channels) = mpsc::channel(/*buffer*/ 1); - let gathered = Arc::new(Notify::new()); - let mut settings = SettingEngine::default(); - settings.set_lite(/*lite*/ true); - let mut media = MediaEngine::default(); - media.register_default_codecs()?; - let builder = PeerConnectionBuilder::new() - .with_media_engine(media) - .with_setting_engine(settings) - .with_handler(Arc::new(RemoteEvents { - channels: sender, - gathered: gathered.clone(), - diagnostics, - started, - })); - let remote = if tcp { - builder.with_tcp_addrs(vec!["0.0.0.0:0"]) - } else { - builder.with_udp_addrs(vec!["0.0.0.0:0"]) - } - .build() - .await?; - let result: Result<()> = async { - let host = VoiceHost::connect(&package, &commit).await?; - let (host, offer) = host.start_transport().await?; - remote - .set_remote_description(RTCSessionDescription::offer(offer.into_sdp())?) - .await?; - let answer = remote.create_answer(/*options*/ None).await?; - remote.set_local_description(answer).await?; - gathered.notified().await; - let answer = remote.local_description().await.context("local answer")?; - let host = host - .apply_answer( - SessionDescription::try_from(answer.sdp).map_err(anyhow::Error::msg)?, - ) - .await - .with_context(|| format!("tcp={tcp}: apply voice answer"))?; - let channel = channels.recv().await.context("event channel")?; - assert_eq!( - (channel.label().await?, channel.ordered().await?), - ("oai-events".into(), true) - ); - // This checks the Closed acknowledgement and successful child exit as well. - host.close().await - } - .await; - remote.close().await?; - result - }); - let mut heartbeat = tokio::time::interval(Duration::from_millis(/*millis*/ 100)); - heartbeat.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - let mut ticks = 0_u32; - let mut last_poll = started; - let mut max_gap = Duration::ZERO; - let mut max_gap_at = Duration::ZERO; - let result = { - tokio::pin!(negotiation); - loop { - let outcome = tokio::select! { - result = &mut negotiation => Some(result), - _ = heartbeat.tick() => { - ticks += 1; - None - } - }; - // Include the final polling gap even when negotiation or its timeout wins. - let now = Instant::now(); - let gap = now.duration_since(last_poll); - if gap > max_gap { - max_gap = gap; - max_gap_at = now.duration_since(started); - } - last_poll = now; - if let Some(result) = outcome { - break result; - } - } - }; - result - .with_context(|| format!("tcp={tcp}: helper negotiation timed out")) - .and_then(std::convert::identity) - .with_context(|| { - let events: Vec<_> = std::iter::from_fn(|| states.try_recv().ok()) - .map(|(elapsed, state)| match state { - RemoteState::Ice(state) => format!("{elapsed:?}: ICE {state}"), - RemoteState::Peer(state) => format!("{elapsed:?}: peer {state}"), - RemoteState::Channel => format!("{elapsed:?}: channel arrived"), - }) - .collect(); - format!( - "tcp={tcp}: remote connection events (first 32): {events:?}; \ - test runtime heartbeat: ticks={ticks}, max polling gap={max_gap:?} \ - observed at {max_gap_at:?}" - ) - })?; - } - Ok(()) -} - -// Linux permits raw-byte filenames; macOS filesystems reject this name themselves. -#[cfg(target_os = "linux")] -#[tokio::test] -async fn installed_client_accepts_non_utf8_package_path() -> Result<()> { - use std::ffi::OsString; - use std::os::unix::ffi::OsStringExt; - - let directory = tempfile::tempdir()?; - let root = directory - .path() - .join(OsString::from_vec(b"voice-\xff".to_vec())); - let bin = root.join("bin"); - let helper_dir = root.join("codex-resources/voice/bin"); - fs::create_dir_all(&bin)?; - fs::create_dir_all(&helper_dir)?; - fs::write(root.join("codex-package.json"), "{}")?; - let app = bin.join("codex"); - fs::write(&app, [])?; - let source = cargo_bin("codex-voice-host")?; - fs::copy(&source, helper_dir.join(source.file_name().unwrap()))?; - let package = InstallContext::from_exe( - /*is_macos*/ cfg!(target_os = "macos"), - Some(&app), - /*method_override*/ None, - ) - .package_layout - .context("package layout")?; - VoiceHost::connect(&package, &build_commit().await?) - .await? - .close() - .await -} diff --git a/third_party/voice/BUILD.bazel b/third_party/voice/BUILD.bazel index a3a51cda5b..3cf29ebde6 100644 --- a/third_party/voice/BUILD.bazel +++ b/third_party/voice/BUILD.bazel @@ -75,7 +75,7 @@ alias( # Keep the pinned pkg-config sources, but explicitly use LLVM archive tools. # The upstream bootstrap otherwise selects host ar/ranlib on macOS. pkg_config( - name = "pkg_config", + name = "pkg_config_unix", build_data = [ ":pkg_config_linker", "@llvm//tools:llvm-ar", @@ -116,9 +116,21 @@ pkg_config( native_tool_toolchain( name = "pkg_config_tool", - path = "$(execpath :pkg_config)", + path = "$(execpath :pkg_config_unix)", tags = ["manual"], - target = ":pkg_config", + target = ":pkg_config_unix", +) + +# Build-script tools select their execution platform, independently of target ABI. +alias( + name = "pkg_config", + actual = select({ + ":windows_x86_64": ":windows_tools_x86_64", + ":windows_aarch64": ":windows_tools_aarch64", + "//conditions:default": ":pkg_config_unix", + }), + tags = ["manual"], + visibility = ["//visibility:public"], ) filegroup( @@ -181,6 +193,9 @@ alias( actual = select({ ":" + os + "_" + cpu: ":native_prefix_" + os + "_" + cpu for os, cpu, _ in _NATIVE_PLATFORMS + } | { + ":windows_" + cpu + "_msvc": ":native_prefix_windows_" + cpu + for cpu in ("x86_64", "aarch64") }), tags = ["manual"], visibility = ["//visibility:public"], @@ -209,6 +224,9 @@ alias( actual = select({ ":" + os + "_" + cpu: ":native_runtime_" + os + "_" + cpu for os, cpu, _ in _NATIVE_PLATFORMS + } | { + ":windows_" + cpu + "_msvc": ":native_runtime_windows_" + cpu + for cpu in ("x86_64", "aarch64") }), tags = ["manual"], visibility = ["//visibility:public"], @@ -241,6 +259,9 @@ alias( actual = select({ ":" + os + "_" + cpu: ":native_link_" + os + "_" + cpu for os, cpu, _ in _NATIVE_PLATFORMS + } | { + ":windows_" + cpu + "_msvc": ":native_link_windows_" + cpu + for cpu in ("x86_64", "aarch64") }), tags = ["manual"], visibility = ["//visibility:public"], @@ -259,6 +280,17 @@ label_flag( visibility = ["//visibility:public"], ) +[ + config_setting( + name = "windows_" + cpu, + constraint_values = [ + "@platforms//os:windows", + "@platforms//cpu:" + cpu, + ], + ) + for cpu in ("x86_64", "aarch64") +] + [ config_setting( name = "windows_" + cpu + "_msvc",