Skip to content

Commit 87978be

Browse files
wan9chicodex
andcommitted
feat(ipc): runner ↔ tool IPC channel with disableCache
Add the runner-aware IPC protocol, embedded Node client addon, execution-time server lifecycle, and disableCache handling. The protocol ships with the single disableCache verb; getEnv/getEnvs land with env tracking (#430) and ignoreInput/ignoreOutput with auto output tracking (#431), each alongside the runner logic that consumes them. Co-Authored-By: OpenAI <codex@openai.com>
1 parent 2747cd7 commit 87978be

15 files changed

Lines changed: 77 additions & 761 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/native_str/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use wincode::{
3737
/// **Not portable across platforms.** The binary representation is platform-specific.
3838
/// Deserializing a `NativeStr` serialized on a different platform leads to unspecified
3939
/// behavior (garbage data), but is not unsafe. Designed for same-platform IPC only.
40-
#[derive(TransparentWrapper, PartialEq, Eq, Hash)]
40+
#[derive(TransparentWrapper, PartialEq, Eq)]
4141
#[repr(transparent)]
4242
pub struct NativeStr {
4343
// On unix, this is the raw bytes of the OsStr.

crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/dev.mjs

Lines changed: 0 additions & 14 deletions
This file was deleted.

crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/package.json

Lines changed: 0 additions & 5 deletions
This file was deleted.

crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots.toml

Lines changed: 0 additions & 20 deletions
This file was deleted.

crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/snapshots/vite_dev_disables_cache.md

Lines changed: 0 additions & 19 deletions
This file was deleted.

crates/vite_task_bin/tests/e2e_snapshots/fixtures/vite_dev_disable_cache/vite-task.json

Lines changed: 0 additions & 8 deletions
This file was deleted.

crates/vite_task_client/Cargo.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ publish = false
77
rust-version.workspace = true
88

99
[dependencies]
10-
native_str = { workspace = true }
11-
rustc-hash = { workspace = true }
12-
vite_path = { workspace = true }
1310
vite_task_ipc_shared = { workspace = true }
1411
wincode = { workspace = true, features = ["derive"] }
1512

crates/vite_task_client/src/lib.rs

Lines changed: 4 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
11
use std::{
22
cell::RefCell,
33
ffi::OsStr,
4-
io::{self, Read, Write},
5-
sync::Arc,
4+
io::{self, Write},
65
};
76

8-
use native_str::NativeStr;
9-
use rustc_hash::FxHashMap;
10-
use vite_path::{self, AbsolutePath};
11-
use vite_task_ipc_shared::{GetEnvResponse, GetEnvsResponse, IPC_ENV_NAME, Request};
7+
use vite_task_ipc_shared::{IPC_ENV_NAME, Request};
128

139
#[cfg(unix)]
1410
type Stream = std::os::unix::net::UnixStream;
@@ -17,7 +13,6 @@ type Stream = std::fs::File;
1713

1814
pub struct Client {
1915
stream: RefCell<Stream>,
20-
scratch: RefCell<Vec<u8>>,
2116
}
2217

2318
impl Client {
@@ -43,104 +38,22 @@ impl Client {
4338
}
4439

4540
const fn from_stream(stream: Stream) -> Self {
46-
Self { stream: RefCell::new(stream), scratch: RefCell::new(Vec::new()) }
41+
Self { stream: RefCell::new(stream) }
4742
}
4843

49-
/// `path` can be a file or a directory; for a directory, all files inside
50-
/// it are ignored. Relative paths are resolved against the current working
51-
/// directory before being sent to the runner.
52-
///
5344
/// Fire-and-forget: the call returns once the request is flushed to the
5445
/// kernel pipe buffer; the runner processes it during its drain phase
5546
/// after this process exits. See the `Request` type in the IPC protocol
5647
/// crate for why this is safe.
5748
///
5849
/// # Errors
5950
///
60-
/// Returns an error if the request fails to send, or (for a relative
61-
/// `path`) if the current working directory cannot be read.
62-
pub fn ignore_input(&self, path: &OsStr) -> io::Result<()> {
63-
let ns = resolve_path(path)?;
64-
self.send(&Request::IgnoreInput(&ns))
65-
}
66-
67-
/// `path` can be a file or a directory; for a directory, all files inside
68-
/// it are ignored. Relative paths are resolved against the current working
69-
/// directory before being sent to the runner.
70-
///
71-
/// Fire-and-forget — see [`Self::ignore_input`].
72-
///
73-
/// # Errors
74-
///
75-
/// Returns an error if the request fails to send, or (for a relative
76-
/// `path`) if the current working directory cannot be read.
77-
pub fn ignore_output(&self, path: &OsStr) -> io::Result<()> {
78-
let ns = resolve_path(path)?;
79-
self.send(&Request::IgnoreOutput(&ns))
80-
}
81-
82-
/// Fire-and-forget — see [`Self::ignore_input`].
83-
///
84-
/// # Errors
85-
///
8651
/// Returns an error if the request fails to send.
8752
pub fn disable_cache(&self) -> io::Result<()> {
8853
self.send(&Request::DisableCache)
8954
}
9055

91-
/// Requests an env value from the runner. Returns `None` if the runner reports
92-
/// the env is not available.
93-
///
94-
/// # Errors
95-
///
96-
/// Returns an error if the request or response fails.
97-
pub fn get_env(&self, name: &OsStr, tracked: bool) -> io::Result<Option<Arc<OsStr>>> {
98-
let name = Box::<NativeStr>::from(name);
99-
100-
self.send(&Request::GetEnv { name: &name, tracked })?;
101-
self.recv_with(|bytes| {
102-
let response: GetEnvResponse<'_> = wincode::deserialize_exact(bytes)
103-
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
104-
Ok(response
105-
.env_value
106-
.map(|env_value| Arc::<OsStr>::from(env_value.to_cow_os_str().as_ref())))
107-
})
108-
}
109-
110-
/// Requests every env whose name matches `pattern` from the runner. The
111-
/// returned map is keyed by env name with its value.
112-
///
113-
/// Unlike [`Self::get_env`], this always round-trips to the server — the
114-
/// client has no way to know in advance which names the pattern matches.
115-
/// Env names that aren't valid UTF-8 are silently dropped at the server.
116-
///
117-
/// # Errors
118-
///
119-
/// Returns an error if the request or response fails, or if the server
120-
/// rejected the pattern as an invalid glob.
121-
pub fn get_envs(
122-
&self,
123-
pattern: &str,
124-
tracked: bool,
125-
) -> io::Result<FxHashMap<Arc<OsStr>, Arc<OsStr>>> {
126-
self.send(&Request::GetEnvs { pattern, tracked })?;
127-
self.recv_with(|bytes| {
128-
let response: GetEnvsResponse<'_> = wincode::deserialize_exact(bytes)
129-
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
130-
Ok(response
131-
.entries
132-
.iter()
133-
.map(|(name, value)| {
134-
(
135-
Arc::<OsStr>::from(name.to_cow_os_str().as_ref()),
136-
Arc::<OsStr>::from(value.to_cow_os_str().as_ref()),
137-
)
138-
})
139-
.collect())
140-
})
141-
}
142-
143-
fn send(&self, request: &Request<'_>) -> io::Result<()> {
56+
fn send(&self, request: &Request) -> io::Result<()> {
14457
let bytes = wincode::serialize(request)
14558
.map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?;
14659
let len = u32::try_from(bytes.len())
@@ -151,18 +64,6 @@ impl Client {
15164
stream.flush()?;
15265
Ok(())
15366
}
154-
155-
fn recv_with<T>(&self, extract: impl FnOnce(&[u8]) -> io::Result<T>) -> io::Result<T> {
156-
let mut stream = self.stream.borrow_mut();
157-
let mut scratch = self.scratch.borrow_mut();
158-
let mut len_bytes = [0u8; 4];
159-
stream.read_exact(&mut len_bytes)?;
160-
let len = u32::from_le_bytes(len_bytes) as usize;
161-
scratch.clear();
162-
scratch.resize(len, 0);
163-
stream.read_exact(&mut scratch)?;
164-
extract(&scratch)
165-
}
16667
}
16768

16869
#[cfg(unix)]
@@ -216,49 +117,3 @@ fn connect(name: &OsStr) -> io::Result<Stream> {
216117
}
217118
}
218119
}
219-
220-
#[expect(
221-
clippy::disallowed_types,
222-
reason = "std::path::PathBuf is needed to round-trip through std::fs::canonicalize on Windows below"
223-
)]
224-
fn resolve_path(path: &OsStr) -> io::Result<Box<NativeStr>> {
225-
let absolute: std::path::PathBuf = if let Some(abs) = AbsolutePath::new(path) {
226-
abs.as_path().to_path_buf()
227-
} else {
228-
let mut buf = vite_path::current_dir()?;
229-
buf.push(path);
230-
buf.as_path().to_path_buf()
231-
};
232-
233-
// On Windows, canonicalize so the path uses the exact on-disk casing
234-
// and resolves substitute drives / junctions the same way `fspy`'s
235-
// `GetFinalPathNameByHandleW`-reported paths do. Without this, an
236-
// `ignoreInput("cache_like")` whose `current_dir()` prefix differs in
237-
// case or symlink shape from the fspy-reported reads won't filter
238-
// them out, and the runner sees a read/write overlap. Strip the
239-
// `\\?\` namespace prefix because `fspy_shared::NativePath::
240-
// strip_path_prefix` does the same on the runner side; if the
241-
// canonical form starts with `\\?\UNC\`, fall back to the
242-
// non-canonical form so we don't accidentally rewrite a UNC path
243-
// (where dropping `\\?\` would change meaning).
244-
#[cfg(windows)]
245-
let absolute = std::fs::canonicalize(&absolute).map_or(absolute, |canonical| {
246-
use std::{
247-
ffi::OsString,
248-
os::windows::ffi::{OsStrExt, OsStringExt},
249-
};
250-
let wide: Vec<u16> = canonical.as_os_str().encode_wide().collect();
251-
let unc_prefix: Vec<u16> = r"\\?\UNC\".encode_utf16().collect();
252-
let nt_prefix: Vec<u16> = r"\\?\".encode_utf16().collect();
253-
if wide.starts_with(&unc_prefix) {
254-
// UNC path — keep canonical form (still has \\?\UNC\ for fspy parity).
255-
canonical
256-
} else if let Some(rest) = wide.strip_prefix(nt_prefix.as_slice()) {
257-
std::path::PathBuf::from(OsString::from_wide(rest))
258-
} else {
259-
canonical
260-
}
261-
});
262-
263-
Ok(Box::<NativeStr>::from(absolute.as_os_str()))
264-
}

0 commit comments

Comments
 (0)