fix(shim): make env PATH shims work#160
Conversation
- keep using executablePath for shim resolution - fix `zvm use <version>` failing with `NameTooLong` on macOS - local testing showed 512 bytes was too small - 1024 bytes was the smallest working buffer - keep the existing limit on other platforms - stop reusing the version-path scratch buffer during `use`
|
Cool, I went ahead with the While testing it locally, I noticed the Windows Zig zip was extracted one directory deeper than the shim expects, so I included that small extraction fix in this PR too. With that in place I can verify |
There was a problem hiding this comment.
Pull request overview
This PR fixes end-to-end zvm env usability by ensuring zig / zls shims actually exist in the PATH directory that zvm env prints (i.e., $ZVM_HOME/.zm/bin), and aligns documentation/examples to the .zm/bin layout. It also adjusts Windows zip extraction to normalize “single-root-folder” archives into the directory structure the shim expects, and adds an e2e test to cover shim creation.
Changes:
- Create/update
zig/zlsshims onzvm use(symlinks on Unix; copied.exeshims on Windows). - Teach shim dispatch to recognize Windows
.exenames and build.exetool paths. - Normalize zip extraction when the archive contains exactly one top-level directory; update docs/examples to use
.zm/bin; add e2e coverage.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/core/alias.zig |
Ensures zvm use creates zig/zls shims in $ZVM_HOME/.zm/bin across platforms. |
src/shim.zig |
Extends shim-name detection and tool path building for Windows .exe names; adds Windows exec path. |
src/io/extract.zig |
Normalizes single-root zip archives so extracted layouts match what shims expect. |
src/util/tool.zig |
Refactors directory copy path building (now using fixed-size stack buffers). |
tests/e2e.zig |
Adds offline e2e test to assert shims are created after use. |
README.md |
Updates PATH example to .zm/bin and simplifies “PATH not updated” guidance. |
static/layouts/index.shtml |
Updates website PATH example to .zm/bin and clarifies it’s Unix-shell output. |
install.ps1 |
Installs zvm.exe into .zm\\bin and creates zig.exe/zls.exe shims; updates PATH handling. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| var iterate = source.iterate(); | ||
| while (try iterate.next(io)) |entry| { | ||
| const entry_name = entry.name; | ||
|
|
||
| // Build source sub path. | ||
| source_path_buffer.reset(); | ||
| const source_sub_path = try source_path_buffer.set( | ||
| try std.fmt.bufPrint(source_path_buffer.slice(), "{s}/{s}", .{ source_dir, entry_name }), | ||
| ); | ||
| var source_path_storage: [limits.limits.path_length_maximum]u8 = undefined; | ||
| const source_sub_path = try std.fmt.bufPrint(&source_path_storage, "{s}/{s}", .{ source_dir, entry_name }); | ||
|
|
||
| // Build dest sub path. | ||
| dest_path_buffer.reset(); | ||
| const dest_sub_path = try dest_path_buffer.set( | ||
| try std.fmt.bufPrint(dest_path_buffer.slice(), "{s}/{s}", .{ dest_dir, entry_name }), | ||
| ); | ||
| var dest_path_storage: [limits.limits.path_length_maximum]u8 = undefined; | ||
| const dest_sub_path = try std.fmt.bufPrint(&dest_path_storage, "{s}/{s}", .{ dest_dir, entry_name }); | ||
|
|
||
| switch (entry.kind) { | ||
| .directory => try copy_dir_static(io, source_sub_path, dest_sub_path, source_path_buffer, dest_path_buffer), |
| pub fn is_shim_name(program_basename: []const u8) bool { | ||
| return util_tool.eql_str(program_basename, "zig") or util_tool.eql_str(program_basename, "zls"); | ||
| switch (builtin.os.tag) { | ||
| .windows => return util_tool.eql_str(program_basename, "zig") or | ||
| util_tool.eql_str(program_basename, "zig.exe") or | ||
| util_tool.eql_str(program_basename, "zls") or | ||
| util_tool.eql_str(program_basename, "zls.exe"), |
| assert(is_shim_name(program_name)); | ||
| const tool_name = if (util_tool.eql_str(program_name, "zig") or util_tool.eql_str(program_name, "zig.exe")) "zig" else "zls"; | ||
|
|
| var shim_path_storage: [self_path_buffer_len]u8 = undefined; | ||
| const shim_path = try std.fmt.bufPrint(&shim_path_storage, "{s}/{s}", .{ bin_dir, shim_name }); | ||
|
|
||
| if (builtin.os.tag == .windows and std.ascii.eqlIgnoreCase(self_path, shim_path)) return; | ||
|
|
||
| std.Io.Dir.deleteFileAbsolute(ctx.io, shim_path) catch |err| switch (err) { | ||
| error.FileNotFound => {}, |
| var normalized_source_buffer: object_pools.PathBuffer = .{ .data = undefined, .used = 0 }; | ||
| const copy_source = try normalize_archive_root(io, tmp_dir, tmp_path, &normalized_source_buffer); | ||
|
|
||
| // SAFETY: PathBuffer.data is initialized before first use via copy_dir_static |
|
I have found a more elegant way to solve the path_length problem. |
|
RFR now😘 |
hendriknielaender
left a comment
There was a problem hiding this comment.
Great work, looks good to me 👍
Summary
Closes #157.
Closes #158.
This PR makes the
zvm envsetup actually work end to end. After users add the printed.zm/binpath to PATH,zigandzlsnow have real shims there instead of requiring manual symlinks or copies.It also updates the README and website examples so they no longer point at the old
zvm/binpath.Main changes:
zig/zlsshims in$ZVM_HOME/binon Unixzvm.exe,zig.exe, andzls.exein%USERPROFILE%\.zm\binon Windows.execommand nameszvm env, and docs aligned around.zm/binVerification
Verified manually on:
Checklist:
zig build testzig build e2ezvm envoutputzig versionthrough the shimzls --versionthrough the shim