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
53 changes: 36 additions & 17 deletions src/command/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
//! Optionally installs ZLS (Zig Language Server) alongside Zig.

const std = @import("std");
const zvm_mod = @import("../core/zvm.zig");

const cli = @import("../cli.zig");
const platform = @import("../core/platform.zig");
const Console = @import("../core/Console.zig");
const version_map = @import("../network/version_map.zig");
const crypto = @import("../core/crypto.zig");
const platform = @import("../core/platform.zig");
const zvm_mod = @import("../core/zvm.zig");
const http_client = @import("../network/http_client.zig");
const version_map = @import("../network/version_map.zig");
const archive = @import("archive.zig");
const crypto = @import("../core/crypto.zig");

/// Main entry point for the `zvm install` command.
/// Resolves the requested version, checks if already installed,
Expand Down Expand Up @@ -45,7 +46,7 @@ pub fn run(
if (version_map.getMasterVersion(vmap)) |remote_ver| {
var ver_buf: [std.fs.max_path_bytes]u8 = undefined;
const ver_path = zvm.versionPath(&ver_buf, version);
const zig_path = try std.fmt.allocPrint(allocator, "{s}/zig", .{ver_path});
const zig_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ ver_path, platform.executableName("zig") });
defer allocator.free(zig_path);

const result = std.process.run(allocator, zvm.io, .{
Expand Down Expand Up @@ -291,7 +292,7 @@ fn verifyInstall(
) !bool {
var ver_buf: [std.fs.max_path_bytes]u8 = undefined;
const ver_path = zvm.versionPath(&ver_buf, version);
const zig_path = try std.fmt.allocPrint(allocator, "{s}/zig", .{ver_path});
const zig_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ ver_path, platform.executableName("zig") });
defer allocator.free(zig_path);

// Create a temporary test file in the cache directory
Expand Down Expand Up @@ -343,7 +344,7 @@ fn installZls(
// Get the installed Zig version string
var ver_buf: [std.fs.max_path_bytes]u8 = undefined;
const ver_path = zvm.versionPath(&ver_buf, version);
const zig_path = try std.fmt.allocPrint(allocator, "{s}/zig", .{ver_path});
const zig_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ ver_path, platform.executableName("zig") });
defer allocator.free(zig_path);

const result = std.process.run(allocator, zvm.io, .{
Expand Down Expand Up @@ -417,6 +418,7 @@ fn installZls(
return;
},
};
const zls_exe_name = platform.executableName("zls");

// Download ZLS archive
const zls_archive_name = if (std.mem.lastIndexOfScalar(u8, zls_tarball, '/')) |idx| zls_tarball[idx + 1 ..] else "zls-archive";
Expand All @@ -429,6 +431,7 @@ fn installZls(
// Extract to a temporary directory
var temp_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const temp_dir = try std.fmt.bufPrint(&temp_buf, "{s}/zls-temp", .{zvm.cache_dir});
std.Io.Dir.cwd().deleteTree(zvm.io, temp_dir) catch {};
std.Io.Dir.cwd().createDirPath(zvm.io, temp_dir) catch {};

archive.extractArchive(allocator, zvm.io, zls_archive_path, temp_dir) catch {
Expand All @@ -445,18 +448,21 @@ fn installZls(
var iter = temp_handle.iterate();
while (try iter.next(zvm.io)) |entry| {
// Flat layout: zls binary at root of archive
if (entry.kind == .file and (std.mem.eql(u8, entry.name, "zls") or std.mem.eql(u8, entry.name, "zls.exe"))) {
if (entry.kind == .file and std.mem.eql(u8, entry.name, zls_exe_name)) {
var src_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const src = try std.fmt.bufPrint(&src_buf, "{s}/{s}", .{ temp_dir, entry.name });

var dst_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const dst = try std.fmt.bufPrint(&dst_buf, "{s}/{s}/zls", .{ zvm.data_dir, version });
const dst = try std.fmt.bufPrint(&dst_buf, "{s}/{s}/{s}", .{ zvm.data_dir, version, zls_exe_name });

try platform.copyFile(zvm.io, src, dst);
deleteLegacyZlsWithoutExtension(zvm, version, zls_exe_name);

_ = std.process.run(allocator, zvm.io, .{
.argv = &.{ "chmod", "+x", dst },
}) catch {};
if (!platform.isWindows()) {
_ = std.process.run(allocator, zvm.io, .{
.argv = &.{ "chmod", "+x", dst },
}) catch {};
}
console.success("Installed ZLS", .{});
found = true;
break;
Expand All @@ -472,18 +478,21 @@ fn installZls(

var inner_iter = inner_dir.iterate();
while (try inner_iter.next(zvm.io)) |inner_entry| {
if (std.mem.eql(u8, inner_entry.name, "zls") or std.mem.eql(u8, inner_entry.name, "zls.exe")) {
if (inner_entry.kind == .file and std.mem.eql(u8, inner_entry.name, zls_exe_name)) {
var src_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const src = try std.fmt.bufPrint(&src_buf, "{s}/{s}/{s}", .{ temp_dir, entry.name, inner_entry.name });

var dst_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const dst = try std.fmt.bufPrint(&dst_buf, "{s}/{s}/zls", .{ zvm.data_dir, version });
const dst = try std.fmt.bufPrint(&dst_buf, "{s}/{s}/{s}", .{ zvm.data_dir, version, zls_exe_name });

try platform.copyFile(zvm.io, src, dst);
deleteLegacyZlsWithoutExtension(zvm, version, zls_exe_name);

_ = std.process.run(allocator, zvm.io, .{
.argv = &.{ "chmod", "+x", dst },
}) catch {};
if (!platform.isWindows()) {
_ = std.process.run(allocator, zvm.io, .{
.argv = &.{ "chmod", "+x", dst },
}) catch {};
}
console.success("Installed ZLS", .{});
found = true;
break;
Expand All @@ -501,3 +510,13 @@ fn installZls(
std.Io.Dir.cwd().deleteFile(zvm.io, zls_archive_path) catch {};
std.Io.Dir.cwd().deleteTree(zvm.io, temp_dir) catch {};
}

/// Remove the extensionless ZLS file left by older Windows installs.
/// No-op on Linux/macOS where the expected executable name is already "zls".
fn deleteLegacyZlsWithoutExtension(zvm: *zvm_mod.ZVM, version: []const u8, zls_exe_name: []const u8) void {
if (std.mem.eql(u8, zls_exe_name, "zls")) return;

var legacy_dst_buf: [std.fs.max_path_bytes * 2]u8 = undefined;
const legacy_dst = std.fmt.bufPrint(&legacy_dst_buf, "{s}/{s}/zls", .{ zvm.data_dir, version }) catch return;
std.Io.Dir.cwd().deleteFile(zvm.io, legacy_dst) catch {};
}
8 changes: 5 additions & 3 deletions src/command/run.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
//! Spawns the zig binary from the requested version directory as a child process.

const std = @import("std");
const zvm_mod = @import("../core/zvm.zig");

const Console = @import("../core/Console.zig");
const platform = @import("../core/platform.zig");
const zvm_mod = @import("../core/zvm.zig");

/// Run a Zig command using a specific installed version.
/// All arguments after the version are passed through to the zig binary.
Expand All @@ -20,10 +22,10 @@ pub fn run(
std.process.exit(1);
}

// Build the zig binary path: data_dir/<version>/zig
// Build the zig binary path: data_dir/<version>/zig[.exe]
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
const version_dir = zvm.versionPath(&path_buf, version);
const zig_path = try std.fmt.allocPrint(allocator, "{s}/zig", .{version_dir});
const zig_path = try std.fmt.allocPrint(allocator, "{s}/{s}", .{ version_dir, platform.executableName("zig") });
defer allocator.free(zig_path);

// Build argv: [zig_path, arg1, arg2, ...]
Expand Down
12 changes: 5 additions & 7 deletions src/command/upgrade.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
const std = @import("std");
const builtin = @import("builtin");
const build_options = @import("build_options");
const zvm_mod = @import("../core/zvm.zig");

const Console = @import("../core/Console.zig");
const platform = @import("../core/platform.zig");
const http_client = @import("../network/http_client.zig");
const update_check = @import("../core/update_check.zig");
const zvm_mod = @import("../core/zvm.zig");
const http_client = @import("../network/http_client.zig");

/// Search for the extracted binary inside self_dir, including subdirectories.
/// The archive may extract into a versioned directory like zvm-v0.1.1-aarch64-macos/.
Expand Down Expand Up @@ -160,10 +161,7 @@ pub fn run(
// Find the extracted binary and replace the current installation.
// The archive may extract into a subdirectory (e.g. zvm-v0.1.1-aarch64-macos/zvm),
// so search recursively.
const exe_name = comptime switch (builtin.os.tag) {
.windows => "zvm.exe",
else => "zvm",
};
const exe_name = platform.executableName("zvm");

// Resolve the current zvm install directory
const install_dir = blk: {
Expand Down Expand Up @@ -231,7 +229,7 @@ pub fn run(
defer allocator.free(active);
var ver_buf: [std.fs.max_path_bytes]u8 = undefined;
const ver_path = zvm.versionPath(&ver_buf, active);
const zig_path = std.fmt.allocPrint(allocator, "{s}/zig", .{ver_path}) catch return;
const zig_path = std.fmt.allocPrint(allocator, "{s}/{s}", .{ ver_path, platform.executableName("zig") }) catch return;
defer allocator.free(zig_path);

const ver_result = std.process.run(allocator, zvm.io, .{
Expand Down
14 changes: 14 additions & 0 deletions src/core/platform.zig
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ pub fn getArchiveExtension() []const u8 {
};
}

/// Returns the platform-specific executable filename for a tool.
/// Windows executables must keep their .exe suffix so shells and editors can find them.
pub fn executableName(comptime base_name: []const u8) []const u8 {
return switch (builtin.os.tag) {
.windows => base_name ++ ".exe",
else => base_name,
};
}

/// Returns true when the current target uses Windows executable semantics.
pub fn isWindows() bool {
return builtin.os.tag == .windows;
}

/// Create a symbolic link at `link_path` pointing to `target`.
/// Removes any existing file/link at `link_path` before creation.
/// On Windows, creates a directory junction (no admin privileges required).
Expand Down
Loading