Skip to content
Merged
10 changes: 3 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ After installation, configure your shell environment:
# Get shell-specific configuration
zvm env

# Example output:
# Example output on Unix shells:
# Add this to your ~/.bashrc, ~/.profile, or ~/.zshrc:
export PATH="/home/user/.local/share/zvm/bin:$PATH"
export PATH="$HOME/.local/share/.zm/bin:$PATH"
```

You can also ask for a specific shell:
Expand Down Expand Up @@ -317,11 +317,7 @@ zig build -Doptimize=ReleaseSafe
### Common Issues

**PATH not updated after installation**
```bash
# Re-run shell configuration
zvm env
source ~/.bashrc # or ~/.zshrc
```
- Follow [Setup Your Shell](#setup-your-shell), then restart or reload your shell.

**Version detection not working**
- Ensure `build.zig.zon` contains `minimum_zig_version` field
Expand Down
49 changes: 39 additions & 10 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ if (-not $useLatest) {

$zvmRepoUrl = "https://github.com/hendriknielaender/zvm"
$zvmInstallDir = "$HOME\.zm"
$zvmBinDir = "$zvmInstallDir\bin"
$architecture = if ([Environment]::Is64BitOperatingSystem) { "x86_64" } else { "x86" }
$zvmFileName = "$architecture-windows-zvm.zip"
$zvmExeFileName = "$architecture-windows-zvm.exe"
$zvmZipPath = "$zvmInstallDir\$zvmFileName"
$zvmExePath = "$zvmInstallDir\$zvmExeFileName"
$zvmRenamedExePath = "$zvmInstallDir\zvm.exe"
$zvmRenamedExePath = "$zvmBinDir\zvm.exe"
$zvmLegacyExePath = "$zvmInstallDir\zvm.exe"

if ($useLatest) {
$zvmUrl = "$zvmRepoUrl/releases/latest/download/$zvmFileName"
Expand All @@ -30,12 +32,17 @@ if ($useLatest) {
Write-Output "Installing $versionLabel (rollback/specific version)..."
}

# Create the installation directory if it doesn't exist
# Create the installation directories if they don't exist
if (-not (Test-Path -Path $zvmInstallDir)) {
Write-Output "Creating installation directory at $zvmInstallDir..."
New-Item -Path $zvmInstallDir -ItemType Directory | Out-Null
}

if (-not (Test-Path -Path $zvmBinDir)) {
Write-Output "Creating binary directory at $zvmBinDir..."
New-Item -Path $zvmBinDir -ItemType Directory | Out-Null
}

# Download the requested release
Write-Output "Downloading $versionLabel from $zvmUrl..."
try {
Expand Down Expand Up @@ -66,25 +73,47 @@ if (Test-Path -Path $zvmRenamedExePath) {
Write-Output "Removed existing zvm.exe."
}

# Rename the new executable
# Move the new executable into bin
if (Test-Path -Path $zvmExePath) {
Rename-Item -Path $zvmExePath -NewName "zvm.exe"
Write-Output "Renamed $zvmExeFileName to zvm.exe"
Move-Item -Path $zvmExePath -Destination $zvmRenamedExePath
Write-Output "Moved $zvmExeFileName to bin\zvm.exe"

Copy-Item -Path $zvmRenamedExePath -Destination "$zvmBinDir\zig.exe" -Force
Copy-Item -Path $zvmRenamedExePath -Destination "$zvmBinDir\zls.exe" -Force
Write-Output "Created zig.exe and zls.exe shims."

if (Test-Path -Path $zvmLegacyExePath) {
try {
Remove-Item -Path $zvmLegacyExePath -Force
Write-Output "Removed legacy zvm.exe from $zvmInstallDir."
} catch {
Write-Output "Warning: Could not remove legacy $zvmLegacyExePath. Remove it manually if it appears before $zvmBinDir in PATH."
}
}

try {
# Set the user environment variable
Write-Output "Setting ZVM_HOME environment variable..."
[System.Environment]::SetEnvironmentVariable("ZVM_HOME", $zvmInstallDir, [System.EnvironmentVariableTarget]::User)
Write-Output "ZVM_HOME has been set to $zvmInstallDir for the current user."

# Add the zvm directory to the user PATH
# Add the zvm bin directory to the user PATH
$currentPath = [System.Environment]::GetEnvironmentVariable("Path", [System.EnvironmentVariableTarget]::User)
if ($currentPath -notlike "*$zvmInstallDir*") {
$newPath = "$currentPath;$zvmInstallDir"
$pathEntries = if ([string]::IsNullOrEmpty($currentPath)) { @() } else { $currentPath -split ';' }
$hasPath = $false
foreach ($pathEntry in $pathEntries) {
if ($pathEntry.TrimEnd('\') -ieq $zvmBinDir.TrimEnd('\')) {
$hasPath = $true
break
}
}

if (-not $hasPath) {
$newPath = if ([string]::IsNullOrEmpty($currentPath)) { $zvmBinDir } else { "$currentPath;$zvmBinDir" }
[System.Environment]::SetEnvironmentVariable("Path", $newPath, [System.EnvironmentVariableTarget]::User)
Write-Output "Added $zvmInstallDir to PATH for the current user."
Write-Output "Added $zvmBinDir to PATH for the current user."
} else {
Write-Output "$zvmInstallDir is already in the user PATH."
Write-Output "$zvmBinDir is already in the user PATH."
}
} catch {
Write-Output "Error: Unable to set environment variable or update PATH. Please run the script as an administrator."
Expand Down
56 changes: 51 additions & 5 deletions src/core/alias.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const context = @import("../Context.zig");
const validation = @import("../cli/validation.zig");
const object_pools = @import("../memory.zig");
const limits = @import("../memory/limits.zig");
const paths = @import("../platform/paths.zig");

const log = std.log.scoped(.alias);

Expand All @@ -32,11 +33,8 @@ pub fn set_version(ctx: *context.CliContext, version: []const u8, is_zls: bool)
else
try util_data.get_zvm_zig_version(base_path_buffer);

var version_path_buffer = try ctx.scratch(.path);
defer version_path_buffer.release();
const version_path = try version_path_buffer.set(
try std.fmt.bufPrint(version_path_buffer.slice(), "{s}/{s}", .{ base_path, version }),
);
var version_path_storage: [limits.limits.path_length_maximum]u8 = undefined;
const version_path = try std.fmt.bufPrint(&version_path_storage, "{s}/{s}", .{ base_path, version });

std.Io.Dir.accessAbsolute(ctx.io, version_path, .{}) catch |err| {
if (err != error.FileNotFound)
Expand All @@ -63,6 +61,7 @@ pub fn set_version(ctx: *context.CliContext, version: []const u8, is_zls: bool)
try util_data.get_zvm_current_zig(symlink_path_buffer);

try update_current(ctx.io, version_path, symlink_path);
try ensure_shim(ctx, if (is_zls) "zls" else "zig");

if (is_zls) {
try verify_zls_version(ctx, version);
Expand Down Expand Up @@ -157,6 +156,53 @@ fn update_current(io: std.Io, zig_path: []const u8, symlink_path: []const u8) !v
try current_dir.symLinkAtomic(io, zig_path, symlink_basename, .{ .is_directory = true });
}

fn ensure_shim(ctx: *context.CliContext, tool_name: []const u8) !void {
assert(tool_name.len > 0);

var zvm_root_storage: [limits.limits.path_length_maximum]u8 = undefined;
const zvm_root = try paths.get_zvm_root(&zvm_root_storage, ctx.get_home_dir());

var bin_dir_storage: [limits.limits.path_length_maximum]u8 = undefined;
const bin_dir = try std.fmt.bufPrint(&bin_dir_storage, "{s}/bin", .{zvm_root});

try util_tool.try_create_path(ctx.io, bin_dir);

var self_storage: [limits.limits.path_length_maximum]u8 = undefined;
const self_len = try std.process.executablePath(ctx.io, &self_storage);
const self_path = self_storage[0..self_len];
assert(self_path.len > 0);

var shim_name_storage: [limits.limits.path_length_maximum]u8 = undefined;
const shim_name = if (builtin.os.tag == .windows)
try std.fmt.bufPrint(shim_name_storage[0 .. tool_name.len + 4], "{s}.exe", .{tool_name})
else
tool_name;

var shim_path_storage: [limits.limits.path_length_maximum]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 => {},
error.IsDir => {
log.err("Cannot create shim: {s} is a directory", .{shim_path});
return err;
},
else => return err,
};

if (builtin.os.tag == .windows) {
try std.Io.Dir.copyFileAbsolute(self_path, shim_path, ctx.io, .{});
return;
}

var shim_dir = try std.Io.Dir.openDirAbsolute(ctx.io, bin_dir, .{});
defer shim_dir.close(ctx.io);

try shim_dir.symLinkAtomic(ctx.io, self_path, shim_name, .{});
}

/// Verify the current Zig version.
fn verify_zig_version(ctx: *context.CliContext, expected_version: []const u8) !void {
var path_buffer = try ctx.scratch(.path);
Expand Down
22 changes: 19 additions & 3 deletions src/io/extract.zig
Original file line number Diff line number Diff line change
Expand Up @@ -157,11 +157,27 @@
const out_path_len = try out_dir.realPath(io, out_path_buffer.slice());
const out_path = try out_path_buffer.set(out_path_buffer.slice()[0..out_path_len]);

// Use temporary buffers for copying directories.
var normalized_source_buffer: object_pools.PathBuffer = .{ .data = undefined, .used = 0 };

Check warning on line 160 in src/io/extract.zig

View workflow job for this annotation

GitHub Actions / lint

unsafe-undefined

`undefined` is missing a safety comment
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
var source_buffer: object_pools.PathBuffer = .{ .data = undefined, .used = 0 };
// SAFETY: PathBuffer.data is initialized before first use via copy_dir_static
var dest_buffer: object_pools.PathBuffer = .{ .data = undefined, .used = 0 };

Check warning on line 165 in src/io/extract.zig

View workflow job for this annotation

GitHub Actions / lint

unsafe-undefined

`undefined` is missing a safety comment
try tool.copy_dir_static(io, tmp_path, out_path, &source_buffer, &dest_buffer);
try tool.copy_dir_static(io, copy_source, out_path, &source_buffer, &dest_buffer);
try signals.check();
}

fn normalize_archive_root(
io: std.Io,
tmp_dir: std.Io.Dir,
tmp_path: []const u8,
buffer: *object_pools.PathBuffer,
) ![]const u8 {
var iter = tmp_dir.iterate();
const entry = (try iter.next(io)) orelse return error.EmptyArchive;
if (entry.kind != .directory) return tmp_path;
if (try iter.next(io) != null) return tmp_path;

const normalized_path = try std.fmt.bufPrint(buffer.slice(), "{s}/{s}", .{ tmp_path, entry.name });
return try buffer.set(normalized_path);
}
2 changes: 1 addition & 1 deletion src/memory/limits.zig
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub const limits = struct {
pub const versions_maximum: u32 = 256; // More than enough for available versions.

/// Maximum length of a file path.
pub const path_length_maximum: u32 = 512; // Reasonable path length.
pub const path_length_maximum: u32 = @min(std.Io.Dir.max_path_bytes, 4096);

/// Maximum number of path buffers.
pub const path_buffers_maximum: u32 = 8; // For concurrent path operations.
Expand Down
Loading
Loading