diff --git a/src/command/install.zig b/src/command/install.zig index 77102a8..41b2d44 100644 --- a/src/command/install.zig +++ b/src/command/install.zig @@ -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, @@ -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, .{ @@ -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 @@ -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, .{ @@ -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"; @@ -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 { @@ -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; @@ -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; @@ -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 {}; +} diff --git a/src/command/run.zig b/src/command/run.zig index 7372199..c70cd52 100644 --- a/src/command/run.zig +++ b/src/command/run.zig @@ -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. @@ -20,10 +22,10 @@ pub fn run( std.process.exit(1); } - // Build the zig binary path: data_dir//zig + // Build the zig binary path: data_dir//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, ...] diff --git a/src/command/upgrade.zig b/src/command/upgrade.zig index aaf9ebc..267dad2 100644 --- a/src/command/upgrade.zig +++ b/src/command/upgrade.zig @@ -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/. @@ -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: { @@ -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, .{ diff --git a/src/core/platform.zig b/src/core/platform.zig index 71a4a45..db7e37c 100644 --- a/src/core/platform.zig +++ b/src/core/platform.zig @@ -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).