Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog

- **Changed** The helper files `vp run` writes to the system temp directory, such as the library that tracks file accesses and the JSON Schema that `vp run --json` points to, now share one folder that only the current user can write to, like `/tmp/vp_run-1000` on Linux, instead of a folder for each kind of file ([#974](https://github.com/voidzero-dev/vite-task/pull/974)).
- **Perf** When several tasks become ready at once, `vp run` now checks their cache entries at the same time instead of one after another, so cache hits start sooner. Hashing a task's input files for its cache lookup no longer holds up other tasks ([#982](https://github.com/voidzero-dev/vite-task/pull/982)).
- **Fixed** On Windows, the files a task deletes or moves, such as a build cleaning its output folder before writing it again, are no longer among its inputs, as on Linux and macOS. Before, when a later step changed one of those files, for example by stamping a version into the rebuilt output, the next run missed the cache with a reason like `'dist/index.js' modified`, though the task never read the file ([#980](https://github.com/voidzero-dev/vite-task/pull/980)).
- **Changed** In `@voidzero-dev/vite-task-client`, a `getEnvs` query that matches every env name, `{ prefix: '' }` or `*`, now returns nothing instead of every environment variable, and no longer adds them to the cache key ([#976](https://github.com/voidzero-dev/vite-task/pull/976)).
Expand Down
7 changes: 2 additions & 5 deletions crates/fspy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,5 @@ pub struct TrackedChild {
pub process_handle: std::os::windows::io::OwnedHandle,
}

pub(crate) static SPY_IMPL: LazyLock<SpyImpl> = LazyLock::new(|| {
vt_materialized_artifact::user_temp_dir("fspy")
.and_then(|dir| SpyImpl::init_in(&dir))
.expect("Failed to initialize global spy")
});
pub(crate) static SPY_IMPL: LazyLock<SpyImpl> =
LazyLock::new(|| SpyImpl::init().expect("Failed to initialize global spy"));
14 changes: 8 additions & 6 deletions crates/fspy/src/unix/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ mod syscall_handler;
#[cfg(target_os = "macos")]
mod macos_artifacts;

use std::io;
#[cfg(target_os = "linux")]
use std::sync::Arc;
use std::{io, path::Path};

#[cfg(target_os = "linux")]
use fspy_seccomp_unotify::supervisor::supervise;
Expand Down Expand Up @@ -45,19 +45,21 @@ pub struct SpyImpl {
}

impl SpyImpl {
/// Initialize the fs access spy by writing the preload library on disk.
/// Initialize the fs access spy by writing the preload library on disk, in
/// the current user's own `vp_run` folder in the system temp dir.
///
/// On musl targets, we don't build a preload library —
/// only seccomp-based tracking is used.
pub fn init_in(#[cfg_attr(target_env = "musl", allow(unused))] dir: &Path) -> io::Result<Self> {
pub fn init() -> io::Result<Self> {
#[cfg(not(target_env = "musl"))]
let preload_path = {
use vt_materialized_artifact::{Artifact, artifact};

const PRELOAD_CDYLIB: Artifact =
artifact!("fspy_preload", "CARGO_CDYLIB_FILE_FSPY_PRELOAD_UNIX");

let preload_cdylib_path = PRELOAD_CDYLIB.materialize().suffix(".dylib").at(dir)?;
let preload_cdylib_path =
PRELOAD_CDYLIB.materialize().suffix(".dylib").at_user_temp_dir("vp_run")?;
preload_cdylib_path.as_path().into()
};

Expand All @@ -68,14 +70,14 @@ impl SpyImpl {
bash_path: macos_artifacts::OILS_BINARY
.materialize()
.executable()
.at(dir)?
.at_user_temp_dir("vp_run")?
.as_path()
.into(),
#[cfg(target_os = "macos")]
coreutils_path: macos_artifacts::COREUTILS_BINARY
.materialize()
.executable()
.at(dir)?
.at_user_temp_dir("vp_run")?
.as_path()
.into(),
})
Expand Down
7 changes: 4 additions & 3 deletions crates/fspy/src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ use std::{
ffi::OsStrExt,
io::{AsRawHandle, BorrowedHandle},
},
path::Path,
sync::Arc,
};

Expand Down Expand Up @@ -56,8 +55,10 @@ pub struct SpyImpl {
}

impl SpyImpl {
pub fn init_in(path: &Path) -> io::Result<Self> {
let dll_path = INTERPOSE_CDYLIB.materialize().suffix(".dll").at(path)?;
/// Initialize the fs access spy by writing the interpose DLL on disk, in the
/// current user's own `vp_run` folder in the system temp dir.
pub fn init() -> io::Result<Self> {
let dll_path = INTERPOSE_CDYLIB.materialize().suffix(".dll").at_user_temp_dir("vp_run")?;

let wide_dll_path = dll_path.as_os_str().encode_wide().collect::<Vec<u16>>();
let mut ansi_dll_path =
Expand Down
13 changes: 5 additions & 8 deletions crates/vt/src/napi_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use std::sync::LazyLock;

use vt_materialized_artifact::{artifact, user_temp_dir};
use vt_materialized_artifact::artifact;
use vt_path::{AbsolutePath, AbsolutePathBuf};

/// Path to the materialized `vt_client_napi` `.node` addon.
Expand All @@ -20,13 +20,10 @@ use vt_path::{AbsolutePath, AbsolutePathBuf};
#[must_use]
pub fn napi_client_path() -> &'static AbsolutePath {
static PATH: LazyLock<AbsolutePathBuf> = LazyLock::new(|| {
let path = user_temp_dir("vt_client_napi")
.and_then(|dir| {
artifact!("vt_client_napi", "CARGO_CDYLIB_FILE_VT_CLIENT_NAPI")
.materialize()
.suffix(".node")
.at(&dir)
})
let path = artifact!("vt_client_napi", "CARGO_CDYLIB_FILE_VT_CLIENT_NAPI")
.materialize()
.suffix(".node")
.at_user_temp_dir("vp_run")
.expect("materialize vt_client_napi");
AbsolutePathBuf::new(path).expect("system temp dir yields an absolute path")
});
Expand Down
17 changes: 9 additions & 8 deletions crates/vt/src/session/task_graph_json/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::io;

use vt_materialized_artifact::{artifact, user_temp_dir};
use vt_materialized_artifact::artifact;
use vt_path::{AbsolutePath, AbsolutePathBuf};
use vt_str::Str;

Expand All @@ -14,9 +14,10 @@ pub fn schema_url() -> io::Result<Str> {
}

fn materialize() -> io::Result<AbsolutePathBuf> {
let dir = user_temp_dir("vp-schemas")?;
let path =
artifact!("task-graph", "VT_TASK_GRAPH_SCHEMA").materialize().suffix(".json").at(&dir)?;
let path = artifact!("task-graph", "VT_TASK_GRAPH_SCHEMA")
.materialize()
.suffix(".json")
.at_user_temp_dir("vp_run")?;
AbsolutePathBuf::new(path)
.ok_or_else(|| io::Error::other("the system temp directory isn't an absolute path"))
}
Expand Down Expand Up @@ -101,17 +102,17 @@ mod tests {
#[cfg(not(windows))]
#[test]
fn file_urls() {
let path = AbsolutePath::new("/var/my tmp/vp-schemas-501/task-graph_0123.json").unwrap();
assert_eq!(file_url(path), "file:///var/my tmp/vp-schemas-501/task-graph_0123.json");
let path = AbsolutePath::new("/var/my tmp/vp_run-501/task-graph_0123.json").unwrap();
assert_eq!(file_url(path), "file:///var/my tmp/vp_run-501/task-graph_0123.json");
}

#[cfg(windows)]
#[test]
fn file_urls() {
let url = |path: &str| file_url(AbsolutePath::new(path).unwrap());
assert_eq!(
url(r"C:\Users\me\App Data\Temp\vp-schemas\task-graph_0123.json"),
"file:///C:/Users/me/App Data/Temp/vp-schemas/task-graph_0123.json"
url(r"C:\Users\me\App Data\Temp\vp_run\task-graph_0123.json"),
"file:///C:/Users/me/App Data/Temp/vp_run/task-graph_0123.json"
);
assert_eq!(
url(r"\\?\C:\Temp\task-graph_0123.json"),
Expand Down
62 changes: 37 additions & 25 deletions crates/vt_materialized_artifact/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
//! the value-add over a bare `include_bytes!`.
//!
//! Materialized files are named `{name}_{hash}{suffix}` in the caller-chosen
//! directory, normally one from [`user_temp_dir`], which no other user can
//! write to. The hash (computed at macro-expansion time by [`artifact!`])
//! gives three properties without any coordination between processes:
//! directory, normally the current user's own folder in the system temp dir,
//! which no other user can write to (see [`Materialize::at_user_temp_dir`]).
//! The hash (computed at macro-expansion time by [`artifact!`]) gives three
//! properties without any coordination between processes:
//!
//! - **No repeated writes.** [`Materialize::at`] returns the existing path if
//! the file is already there; repeated calls and re-runs skip I/O.
Expand All @@ -28,22 +29,11 @@ use std::{
path::{Path, PathBuf},
};

/// A folder named after `name` in the system temp dir that only the current
/// user can write to, for materializing artifacts. It's created if missing.
///
/// - On Unix, it's `{temp}/{name}-{uid}`, created with mode `0o700`, since the
/// temp dir can be shared by every user (`/tmp` on Linux). An existing path
/// must be a directory, not a symlink, owned by the current user, and not
/// writable by anyone else; otherwise this returns an error, so another user
/// can't plant files there.
/// - On Windows, it's `{temp}\{name}`: the temp dir is already the user's own.
///
/// # Errors
///
/// Returns an error if the folder can't be created, or, on Unix, if the
/// existing path isn't a directory only the current user can write to.
pub fn user_temp_dir(name: &str) -> io::Result<PathBuf> {
user_dir_in(&env::temp_dir(), name)
/// The folder named after `prefix` in the system temp dir that only the
/// current user can write to, created if missing: see
/// [`Materialize::at_user_temp_dir`].
fn user_temp_dir(prefix: &str) -> io::Result<PathBuf> {
user_dir_in(&env::temp_dir(), prefix)
}

#[cfg(unix)]
Expand Down Expand Up @@ -86,8 +76,8 @@ fn user_dir_in(temp_dir: &Path, name: &str) -> io::Result<PathBuf> {
/// A file embedded into the executable at compile time.
///
/// Construct with [`artifact!`]; materialize to disk via
/// [`Artifact::materialize`] + [`Materialize::at`]. See the [crate docs] for
/// the design rationale.
/// [`Artifact::materialize`] + [`Materialize::at_user_temp_dir`] or
/// [`Materialize::at`]. See the [crate docs] for the design rationale.
///
/// [crate docs]: crate
#[derive(Clone, Copy)]
Expand All @@ -110,7 +100,7 @@ impl Artifact {

/// Start a fluent materialize chain. Supply optional [`Materialize::suffix`]
/// / [`Materialize::executable`] knobs, then terminate with
/// [`Materialize::at`].
/// [`Materialize::at_user_temp_dir`] or [`Materialize::at`].
pub const fn materialize(&self) -> Materialize<'static> {
Materialize {
artifact: *self,
Expand All @@ -122,9 +112,9 @@ impl Artifact {
}

/// Builder returned by [`Artifact::materialize`]. Terminate with
/// [`Materialize::at`] to write the file.
/// [`Materialize::at_user_temp_dir`] or [`Materialize::at`] to write the file.
#[derive(Clone, Copy)]
#[must_use = "materialize() only configures — call .at(dir) to write the file"]
#[must_use = "materialize() only configures — call .at_user_temp_dir(prefix) or .at(dir) to write the file"]
pub struct Materialize<'a> {
artifact: Artifact,
suffix: &'a str,
Expand Down Expand Up @@ -156,6 +146,27 @@ impl Materialize<'_> {
self
}

/// Materialize the artifact, as [`Self::at`] does, in the folder named
/// after `prefix` in the system temp dir that only the current user can
/// write to. The folder is created if missing.
///
/// - On Unix, it's `{temp}/{prefix}-{uid}`, created with mode `0o700`,
/// since the temp dir can be shared by every user (`/tmp` on Linux). An
/// existing path must be a directory, not a symlink, owned by the
/// current user, and not writable by anyone else; otherwise this returns
/// an error, so another user can't plant files there.
/// - On Windows, it's `{temp}\{prefix}`: the temp dir is already the
/// user's own.
///
/// # Errors
///
/// Returns an error if the folder can't be created, or, on Unix, if the
/// existing path isn't a directory only the current user can write to,
/// and otherwise as [`Self::at`] does.
pub fn at_user_temp_dir(self, prefix: &str) -> io::Result<PathBuf> {
self.at(user_temp_dir(prefix)?)
}

/// Materialize the artifact in `dir` under a content-addressed filename,
/// writing it if missing.
///
Expand All @@ -166,7 +177,8 @@ impl Materialize<'_> {
/// # Preconditions
///
/// `dir` must already exist and be writable only by the current user, as
/// [`user_temp_dir`] ensures — this method does not create it.
/// [`Self::at_user_temp_dir`]'s folder is — this method does not create
/// it.
///
/// # Errors
///
Expand Down
2 changes: 1 addition & 1 deletion docs/rfcs/dry-run.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ vp run --dry-run --json -r build

```json
{
"$schema": "file:///tmp/vp-schemas-1000/run-plan_3b5d5c3712955042212316173ccf37be.json",
"$schema": "file:///tmp/vp_run-1000/run-plan_3b5d5c3712955042212316173ccf37be.json",
"version": 1,
"workspaceRoot": "/home/me/acme",
"plan": {
Expand Down
6 changes: 3 additions & 3 deletions docs/rfcs/task-graph-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default defineConfig({

```json
{
"$schema": "file:///tmp/vp-schemas-1000/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json",
"$schema": "file:///tmp/vp_run-1000/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json",
"version": 1,
"workspaceRoot": "/home/me/acme",
"packages": {
Expand Down Expand Up @@ -189,11 +189,11 @@ A path inside the workspace is relative to `workspaceRoot`, with `/` on every pl

### `$schema`

`$schema` points to the document's JSON Schema (draft-07), so an agent can read what each field means, and an editor shows the descriptions and checks the document when it is opened as JSON. Each time it prints a document, vp writes the schema to a folder in the system temp directory that only the current user can write to, such as `/tmp/vp-schemas-1000`, unless it is already there. The file is named by a hash of its content, such as `task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`, so documents printed by different vp versions point to their own schemas.
`$schema` points to the document's JSON Schema (draft-07), so an agent can read what each field means, and an editor shows the descriptions and checks the document when it is opened as JSON. Each time it prints a document, vp writes the schema to a folder in the system temp directory that only the current user can write to, such as `/tmp/vp_run-1000`, unless it is already there. The file is named by a hash of its content, such as `task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`, so documents printed by different vp versions point to their own schemas.

Its value uses a form that VS Code and JetBrains IDEs both read on every platform:

- `file://`, then the absolute path with `/` separators, with a `/` before a Windows drive: `file:///tmp/vp-schemas-1000/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`, `file:///C:/Users/me/AppData/Local/Temp/vp-schemas/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`.
- `file://`, then the absolute path with `/` separators, with a `/` before a Windows drive: `file:///tmp/vp_run-1000/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`, `file:///C:/Users/me/AppData/Local/Temp/vp_run/task-graph_80cde1ad99307ef3eea7a658d7c95f88.json`.
- Not percent-encoded. JetBrains IDEs look the path up as written, so they wouldn't find a space written as `%20`; VS Code reads both.

A plain path wouldn't work on Windows: neither editor reads `C:\Users\…` as a schema location.
Expand Down
Loading