Skip to content

Commit 031317c

Browse files
committed
fix(trampoline): address review regressions
1 parent 7982433 commit 031317c

6 files changed

Lines changed: 79 additions & 27 deletions

File tree

crates/vp_trampoline/src/cmdline.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,12 @@ pub fn parse_shim_pointer(bytes: &[u8]) -> Option<ShimPointer<'_>> {
7474
/// Index where the raw command line's first (program) argument ends.
7575
///
7676
/// This follows the MSVC parsing rule for the program name: a quote toggles
77-
/// quoted mode and backslashes have no escaping effect. The remainder
78-
/// (`&cmdline[result..]`, leading whitespace included) is the argument tail to
79-
/// forward to the child verbatim.
77+
/// quoted mode, backslashes have no escaping effect, and leading whitespace
78+
/// terminates an empty program argument. The remainder (`&cmdline[result..]`,
79+
/// leading whitespace included) is the argument tail to forward to the child
80+
/// verbatim.
8081
pub fn skip_program_argument(cmdline: &[u16]) -> usize {
8182
let mut i = 0;
82-
while i < cmdline.len() && (cmdline[i] == SPACE || cmdline[i] == TAB) {
83-
i += 1;
84-
}
8583
let mut quoted = false;
8684
while i < cmdline.len() {
8785
let c = cmdline[i];
@@ -196,8 +194,15 @@ mod tests {
196194
}
197195

198196
#[test]
199-
fn skips_leading_whitespace_and_bare_program() {
200-
let cl = wide(" node");
197+
fn treats_leading_whitespace_as_an_empty_program() {
198+
for cl in [wide(" script.js --flag"), wide("\tscript.js --flag")] {
199+
assert_eq!(skip_program_argument(&cl), 0);
200+
}
201+
}
202+
203+
#[test]
204+
fn skips_bare_program() {
205+
let cl = wide("node");
201206
assert_eq!(skip_program_argument(&cl), cl.len());
202207
assert_eq!(skip_program_argument(&[]), 0);
203208
}

justfile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,11 @@ snapshot-test *args='': _install_chromium _build-trampoline
9797
cargo test -p vp_cli_snapshots -- {{args}}
9898

9999
# The trampoline is excluded from the workspace; build it from its own
100-
# directory so its .cargo/config.toml (build-std) applies. Artifacts still
101-
# land in the repo-root target/ directory.
102-
[unix]
103-
_build-trampoline:
104-
cd crates/vp_trampoline && cargo build
105-
106-
[windows]
100+
# directory so its .cargo/config.toml (build-std) applies. The helper anchors
101+
# relative CARGO_TARGET_DIR values to the repo root so all binaries stay in the
102+
# same artifact directory.
107103
_build-trampoline:
108-
Set-Location crates/vp_trampoline; cargo build
104+
node packages/tools/src/build-trampoline.ts
109105

110106
# Browser-mode snapshot cases run with PLAYWRIGHT_BROWSERS_PATH=0, so the
111107
# browser must be installed into node_modules with the same setting.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"type": "module",
66
"scripts": {
77
"build": "pnpm -F rolldown build-binding:release && pnpm -F rolldown build-node && pnpm -F vite build-types && pnpm -F @voidzero-dev/* -F vite-plus build",
8-
"bootstrap-cli": "pnpm build && cargo build -p vp_global_cli --release && cd crates/vp_trampoline && cargo build --release && cd ../.. && pnpm install-global-cli",
8+
"bootstrap-cli": "pnpm build && cargo build -p vp_global_cli --release && node packages/tools/src/build-trampoline.ts --release && pnpm install-global-cli",
99
"bootstrap-cli:ci": "pnpm install-global-cli",
1010
"install-global-cli": "tool install-global-cli",
1111
"local-registry": "node packages/tools/src/local-npm-registry.ts",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import path from 'node:path';
2+
import { fileURLToPath } from 'node:url';
3+
4+
import { describe, expect, test } from 'vitest';
5+
6+
import { resolveCargoTargetDir } from '../build-trampoline.ts';
7+
8+
const repoRoot = fileURLToPath(new URL('../../../..', import.meta.url));
9+
10+
describe('resolveCargoTargetDir', () => {
11+
test('anchors relative paths to the repository root', () => {
12+
expect(resolveCargoTargetDir('artifacts')).toBe(path.join(repoRoot, 'artifacts'));
13+
});
14+
15+
test('uses the repository target directory by default', () => {
16+
expect(resolveCargoTargetDir(undefined)).toBe(path.join(repoRoot, 'target'));
17+
});
18+
19+
test('preserves absolute paths', () => {
20+
const absolute = path.resolve(repoRoot, 'custom-artifacts');
21+
expect(resolveCargoTargetDir(absolute)).toBe(absolute);
22+
});
23+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { execFileSync } from 'node:child_process';
2+
import path from 'node:path';
3+
import { fileURLToPath } from 'node:url';
4+
5+
const repoRoot = fileURLToPath(new URL('../../..', import.meta.url));
6+
7+
export function resolveCargoTargetDir(configured: string | undefined): string {
8+
return path.resolve(repoRoot, configured || 'target');
9+
}
10+
11+
export function buildTrampoline(args: string[] = process.argv.slice(2)) {
12+
const cargo = process.platform === 'win32' ? 'cargo.exe' : 'cargo';
13+
execFileSync(cargo, ['build', ...args], {
14+
cwd: path.join(repoRoot, 'crates/vp_trampoline'),
15+
env: {
16+
...process.env,
17+
CARGO_TARGET_DIR: resolveCargoTargetDir(process.env.CARGO_TARGET_DIR),
18+
},
19+
stdio: 'inherit',
20+
});
21+
}
22+
23+
if (import.meta.main) {
24+
buildTrampoline();
25+
}

rfcs/trampoline-exe-for-shims.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@ symbol, so neither the CRT startup nor `std` runtime init runs. The flow in
168168

169169
1. `GetModuleFileNameW` gives the shim path and tool name. Replacing the `.exe`
170170
extension with `.shim` locates the per-tool sidecar.
171-
2. `CreateFileW` and `ReadFile` load the UTF-8 sidecar. The parser requires the
172-
versioned header and accepts the `single-root` and `split` layouts.
171+
2. `CreateFileW` and `ReadFile` load the UTF-8 sidecar. Long paths are made
172+
absolute with `GetFullPathNameW`, then use the `\\?\` drive prefix or the
173+
`\\?\UNC\` network prefix. The parser requires the versioned header and
174+
accepts the `single-root` and `split` layouts.
173175
3. `SetEnvironmentVariableW` pins the sidecar's layout. A single-root pointer
174176
sets `VP_HOME`. A split pointer removes `VP_HOME` and sets `VP_DATA_DIR`,
175177
`VP_BIN_DIR`, and `VP_CACHE_DIR`. Tool shims also set `VP_SHIM_TOOL` and
@@ -181,6 +183,7 @@ symbol, so neither the CRT startup nor `std` runtime init runs. The flow in
181183
5. `SetConsoleCtrlHandler` installs a handler that ignores Ctrl+C and
182184
Ctrl+Break; the child decides how to react.
183185
6. `CreateProcessW` spawns the child with inherited handles and startup info.
186+
The payload path uses the same extended-length normalization as the sidecar.
184187
When the parent redirected stdio (`STARTF_USESTDHANDLES`), the standard
185188
handles are forced inheritable first, as in uv-trampoline and distlib.
186189
7. `WaitForSingleObject`, `GetExitCodeProcess`, and `ExitProcess` propagate the
@@ -207,7 +210,7 @@ infer the layout from directory paths.
207210
| `#![no_main]` + `mainCRTStartup` (no CRT startup, no `std` runtime init) | Done |
208211
| Raw `CreateProcessW` instead of `std::process::Command` | Done |
209212

210-
**Binary size**: 13,312 B on x86_64-pc-windows-msvc and 13,824 B on
213+
**Binary size**: 14,336 B on both x86_64-pc-windows-msvc and
211214
aarch64-pc-windows-msvc, including sidecar parsing and error diagnostics. The
212215
sidecar-aware `std::process::Command` implementation was 221,696 B on x86_64.
213216
See Future Optimizations for the measured size ladder. The executable imports
@@ -307,17 +310,17 @@ When installing a pre-trampoline version (no `vp-shim.exe` in the package):
307310
| **Data embedding** | PE resources (kind, path, script ZIP) | Adjacent directory-layout sidecar |
308311
| **Dependencies** | `windows` crate (unsafe, no CRT) | Zero (raw FFI declaration) |
309312
| **Toolchain** | Nightly Rust (`panic="immediate-abort"`) | Nightly Rust (same technique) |
310-
| **Binary size** | 39-47 KiB | 13-14 KiB |
313+
| **Binary size** | 39-47 KiB | 14 KiB |
311314
| **Entry point** | `#![no_main]` + `mainCRTStartup` | Same approach |
312315
| **Error output** | `ufmt` (no `core::fmt`) | `WriteFile` + Win32 error codes |
313316
| **Ctrl+C handling** | `SetConsoleCtrlHandler` → ignore | Same approach |
314317
| **Exit code** | `GetExitCodeProcess``exit()` | Same approach |
315318

316-
The Vite+ trampoline is smaller because it embeds no PE resources and needs no
317-
path canonicalization, job objects, or GUI subsystem support. It reads a small
318-
sidecar next to its own filename, resolves `vp.exe` under the recorded data
319-
root, and starts it. Both projects share the same build recipe and entry-point
320-
structure.
319+
The Vite+ trampoline is smaller because it embeds no PE resources and only
320+
normalizes long file and process paths. It needs no job objects or GUI
321+
subsystem support. It reads a small sidecar next to its own filename, resolves
322+
`vp.exe` under the recorded data root, and starts it. Both projects share the
323+
same build recipe and entry-point structure.
321324

322325
## Alternatives Considered
323326

@@ -357,7 +360,7 @@ last row is the current sidecar-aware raw implementation.
357360
| Raw Win32 rewrite, normal `main` + build-std | nightly | 13,824 B |
358361
| Raw Win32 rewrite + `#![no_main]`, no diagnostics | nightly | 6,656 B |
359362
| Fixed-layout raw Win32 + `#![no_main]` + full diagnostics | nightly | 8,192 B |
360-
| Sidecar-aware raw Win32 + `#![no_main]` + full diagnostics (shipped) | nightly | 13,312 B |
363+
| Sidecar-aware raw Win32 + `#![no_main]` + full diagnostics (shipped) | nightly | 14,336 B |
361364

362365
For comparison: uv-trampoline ships 45,056 B (x64 console), Scoop's default
363366
kiennq shim is 136,192 B (statically linked MSVC C), and Scoop once vendored

0 commit comments

Comments
 (0)