From 4ad28d086dd300c52679d54bc32efd34d31ade0d Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 28 Jul 2024 08:50:19 +0800 Subject: [PATCH 01/26] fix typo --- src/config.zig | 2 +- src/install.zig | 2 +- src/versions.zig | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.zig b/src/config.zig index c819b9e..6e4b87f 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1 +1 @@ -pub const download_manifest_url: []const u8 = "https://ziglang.org/download/index.json"; +pub const download_mainfest_url: []const u8 = "https://ziglang.org/download/index.json"; diff --git a/src/install.zig b/src/install.zig index 8b2896b..df96df8 100644 --- a/src/install.zig +++ b/src/install.zig @@ -30,7 +30,7 @@ const Error = error{ }; fn fetch_version_data(allocator: Allocator, requested_version: []const u8, sub_key: []const u8) !?Version { - const uri = std.Uri.parse(config.download_manifest_url) catch unreachable; + const uri = std.Uri.parse(config.download_mainfest_url) catch unreachable; var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); diff --git a/src/versions.zig b/src/versions.zig index b1d8182..05ad293 100644 --- a/src/versions.zig +++ b/src/versions.zig @@ -2,7 +2,7 @@ const std = @import("std"); const config = @import("config.zig"); -const uri = std.Uri.parse(config.download_manifest_url) catch unreachable; +const uri = std.Uri.parse(config.download_mainfest_url) catch unreachable; pub const VersionList = struct { // this type will store From c9863ff8006f30724f452b24016bf10ef582b7fd Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 28 Jul 2024 09:20:33 +0800 Subject: [PATCH 02/26] support parse zls version list little change --- src/command.zig | 2 +- src/config.zig | 3 ++- src/download.zig | 4 +--- src/install.zig | 2 +- src/tools.zig | 2 ++ src/versions.zig | 41 +++++++++++++++++++++++++++++++++++++---- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/command.zig b/src/command.zig index 67974a0..4b7f512 100644 --- a/src/command.zig +++ b/src/command.zig @@ -114,7 +114,7 @@ fn handle_alias(params: []const []const u8) !void { fn handle_list() !void { const allocator = tools.get_allocator(); - var version_list = try versions.VersionList.init(allocator); + var version_list = try versions.VersionList.init(allocator, .zig); defer version_list.deinit(); for (version_list.slice()) |version| { diff --git a/src/config.zig b/src/config.zig index 6e4b87f..6abab86 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1 +1,2 @@ -pub const download_mainfest_url: []const u8 = "https://ziglang.org/download/index.json"; +pub const zig_mainfest_url: []const u8 = "https://ziglang.org/download/index.json"; +pub const zls_mainfest_url: []const u8 = "https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json"; diff --git a/src/download.zig b/src/download.zig index 1f3eb2b..1a3f1df 100644 --- a/src/download.zig +++ b/src/download.zig @@ -8,8 +8,6 @@ const alias = @import("alias.zig"); const hash = @import("hash.zig"); const lib = @import("extract.zig"); -const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; - pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u8) !?[32]u8 { assert(version.len > 0); assert(url.len > 0); @@ -113,7 +111,7 @@ fn download_and_extract( .reverse = false, }) orelse unreachable; - const file_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "zig-", platform_str, "-", version, ".", archive_ext }); + const file_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "zig-", platform_str, "-", version, ".", tools.archive_ext }); defer allocator.free(file_name); const total_size: usize = @intCast(req.response.content_length orelse 0); diff --git a/src/install.zig b/src/install.zig index df96df8..259a7e9 100644 --- a/src/install.zig +++ b/src/install.zig @@ -30,7 +30,7 @@ const Error = error{ }; fn fetch_version_data(allocator: Allocator, requested_version: []const u8, sub_key: []const u8) !?Version { - const uri = std.Uri.parse(config.download_mainfest_url) catch unreachable; + const uri = std.Uri.parse(config.zig_mainfest_url) catch unreachable; var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); diff --git a/src/tools.zig b/src/tools.zig index ccc0497..dad6154 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -13,6 +13,8 @@ pub const zig_name = switch (builtin.os.tag) { else => @compileError("not support current platform"), }; +pub const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; + /// Initialize the data. pub fn data_init(tmp_allocator: std.mem.Allocator) !void { allocator = tmp_allocator; diff --git a/src/versions.zig b/src/versions.zig index 05ad293..2b3de4a 100644 --- a/src/versions.zig +++ b/src/versions.zig @@ -2,7 +2,14 @@ const std = @import("std"); const config = @import("config.zig"); -const uri = std.Uri.parse(config.download_mainfest_url) catch unreachable; +const zig_url = std.Uri.parse(config.zig_mainfest_url) catch unreachable; +const zls_url = std.Uri.parse(config.zls_mainfest_url) catch unreachable; + +/// for which use +pub const which = enum { + zig, + zls, +}; pub const VersionList = struct { // this type will store @@ -13,7 +20,7 @@ pub const VersionList = struct { allocator: std.mem.Allocator, /// init the VersionList - pub fn init(allocator: std.mem.Allocator) !VersionList { + pub fn init(allocator: std.mem.Allocator, use: which) !VersionList { // create a http client var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); @@ -22,7 +29,11 @@ pub const VersionList = struct { var buffer: [262144]u8 = undefined; // 256 * 1024 = 262kb // try open a request - var req = try client.open(.GET, uri, .{ .server_header_buffer = &buffer }); + var req = try client.open( + .GET, + if (use == .zig) zig_url else zls_url, + .{ .server_header_buffer = &buffer }, + ); defer req.deinit(); // send request and wait response @@ -42,7 +53,27 @@ pub const VersionList = struct { var lists = std.ArrayList([]const u8).init(allocator); - var iterate = root.object.iterator(); + if (use == .zig) { + var iterate = root.object.iterator(); + while (iterate.next()) |entry| { + const key_ptr = entry.key_ptr; + const key = key_ptr.*; + + const key_copy = try allocator.dupe(u8, key); + try lists.append(key_copy); + } + + return VersionList{ + .lists = lists, + .allocator = allocator, + }; + } + + // for zls + + var zls_versions = root.object.get("versions") orelse return error.NotFoundZlsVersion; + + var iterate = zls_versions.object.iterator(); while (iterate.next()) |entry| { const key_ptr = entry.key_ptr; const key = key_ptr.*; @@ -51,6 +82,8 @@ pub const VersionList = struct { try lists.append(key_copy); } + std.mem.reverse([]const u8, lists.items); + return VersionList{ .lists = lists, .allocator = allocator, From 2d86d1be84b25740d32d0b774d9635fe08da71e7 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 31 Jul 2024 17:45:43 +0800 Subject: [PATCH 03/26] fix command parse --- src/command.zig | 55 ++++++++++++++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/command.zig b/src/command.zig index 4b7f512..c0190ba 100644 --- a/src/command.zig +++ b/src/command.zig @@ -53,28 +53,41 @@ pub fn handle_command(params: []const []const u8) !void { const args = params[1..]; - for (args, 0..) |arg, index| { - for (command_opts) |opt| { - const is_eql_short_handle = if (opt.short_handle) |short_handle| - std.mem.eql(u8, arg, short_handle) - else - false; - - const is_eql_handle = std.mem.eql(u8, arg, opt.handle); - - if (!is_eql_short_handle and !is_eql_handle) - continue; - - const next_param = if (index + 1 < args.len) args[index + 1] else null; - const is_version = if (next_param) |np| std.ascii.isDigit(np[0]) else false; - - break :blk CommandData{ - .cmd = opt.cmd, - .param = if (is_version) next_param else null, - .subcmd = if (!is_version) next_param else null, - }; - } + // for (args, 0..) |arg, index| { + const arg = args[0]; + for (command_opts) |opt| { + const is_eql_short_handle = if (opt.short_handle) |short_handle| + std.mem.eql(u8, arg, short_handle) + else + false; + + const is_eql_handle = std.mem.eql(u8, arg, opt.handle); + + if (!is_eql_short_handle and !is_eql_handle) + continue; + + const subcmd = if (args.len > 2) args[1] else null; + const param = kk: { + if (subcmd != null) { + break :kk args[2]; + } + + if (args.len > 1) + break :kk args[1]; + + break :kk null; + }; + + // const next_param = if (index + 1 < args.len) args[index + 1] else null; + // const is_version = if (next_param) |np| std.ascii.isDigit(np[0]) else false; + + break :blk CommandData{ + .cmd = opt.cmd, + .subcmd = subcmd, + .param = param, + }; } + // } break :blk CommandData{}; }; From ae3f8fa0b636db74fe2b8aba58bf9b41eb6b4f69 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 31 Jul 2024 22:09:26 +0800 Subject: [PATCH 04/26] some change --- src/command.zig | 5 +-- src/config.zig | 22 +++++++++++-- src/install.zig | 3 +- src/meta.zig | 80 ++++++++++++++++++++++++++++++++++++++++++++++++ src/versions.zig | 4 +-- 5 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 src/meta.zig diff --git a/src/command.zig b/src/command.zig index c0190ba..0950490 100644 --- a/src/command.zig +++ b/src/command.zig @@ -92,7 +92,7 @@ pub fn handle_command(params: []const []const u8) !void { }; switch (command.cmd) { - .List => try handle_list(), + .List => try handle_list(command.param), .Install => try install_version(command.subcmd, command.param), .Use => try use_version(command.param), .Default => try set_default(), @@ -125,7 +125,8 @@ fn handle_alias(params: []const []const u8) !void { return std.process.execv(allocator, new_params); } -fn handle_list() !void { +fn handle_list(_: ?[]const u8) !void { + // TODO: const allocator = tools.get_allocator(); var version_list = try versions.VersionList.init(allocator, .zig); defer version_list.deinit(); diff --git a/src/config.zig b/src/config.zig index 6abab86..deca22f 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,2 +1,20 @@ -pub const zig_mainfest_url: []const u8 = "https://ziglang.org/download/index.json"; -pub const zls_mainfest_url: []const u8 = "https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json"; +const std = @import("std"); +const builtin = @import("builtin"); + +var allocator: std.mem.Allocator = undefined; +var home_dir: []const u8 = undefined; + +pub const zig_meta_url: []const u8 = "https://ziglang.org/download/index.json"; +pub const zls_meta_url: []const u8 = "https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json"; + +pub const zig_url = std.Uri.parse(zig_meta_url) catch unreachable; +pub const zls_url = std.Uri.parse(zls_meta_url) catch unreachable; + +pub const zig_name = switch (builtin.os.tag) { + .windows => "zig.exe", + .linux => "zig", + .macos => "zig", + else => @compileError("not support current platform"), +}; + +pub const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; diff --git a/src/install.zig b/src/install.zig index 259a7e9..a62ef88 100644 --- a/src/install.zig +++ b/src/install.zig @@ -30,7 +30,7 @@ const Error = error{ }; fn fetch_version_data(allocator: Allocator, requested_version: []const u8, sub_key: []const u8) !?Version { - const uri = std.Uri.parse(config.zig_mainfest_url) catch unreachable; + const uri = config.zig_url; var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); @@ -96,6 +96,7 @@ fn fetch_version_data(allocator: Allocator, requested_version: []const u8, sub_k return null; } +/// Try to install the specified version of zig pub fn from_version(version: []const u8) !void { const allocator = tools.get_allocator(); diff --git a/src/meta.zig b/src/meta.zig new file mode 100644 index 0000000..5e22d81 --- /dev/null +++ b/src/meta.zig @@ -0,0 +1,80 @@ +const std = @import("std"); +const config = @import("config.zig"); + +const json = std.json; +const Allocator = std.mem.Allocator; +const jsonValue = std.json.Parsed(std.json.Value); + +pub const Zig = struct { + data: jsonValue, + + // init the zig data + pub fn init(raw: []const u8, allocator: Allocator) !Zig { + const data = + try json.parseFromSlice(std.json.Value, allocator, raw, .{}); + + return Zig{ .data = data }; + } + + // deinit the zig data + pub fn deinit(self: *Zig) void { + self.data.deinit(); + } + + /// return the version list + pub fn get_version_list(self: *Zig, allocator: Allocator) ![][]const u8 { + const root = self.data.value; + + var list = std.ArrayList([]const u8).init(allocator); + var iterate = root.object.iterator(); + + while (iterate.next()) |entry| { + const key_ptr = entry.key_ptr; + const key = key_ptr.*; + + const key_copy = try allocator.dupe(u8, key); + try list.append(key_copy); + } + + return try list.toOwnedSlice(); + } +}; + +pub const Zls = struct { + data: jsonValue, + + // init the zig data + pub fn init(raw: []const u8, allocator: Allocator) !Zls { + const data = + try json.parseFromSlice(std.json.Value, allocator, raw, .{}); + + return Zls{ .data = data }; + } + + // deinit the zig data + pub fn deinit(self: *Zls) void { + self.data.deinit(); + } + + /// return the version list + pub fn get_version_list(self: *Zls, allocator: Allocator) ![][]const u8 { + var zls_versions = + self.data.value.object.get("versions") orelse + return error.NotFoundZlsVersion; + + var list = std.ArrayList([]const u8).init(allocator); + + var iterate = zls_versions.object.iterator(); + while (iterate.next()) |entry| { + const key_ptr = entry.key_ptr; + const key = key_ptr.*; + + const key_copy = try allocator.dupe(u8, key); + try list.append(key_copy); + } + + const slice = try list.toOwnedSlice(); + + std.mem.reverse([]const u8, slice); + } +}; diff --git a/src/versions.zig b/src/versions.zig index 2b3de4a..707c3d4 100644 --- a/src/versions.zig +++ b/src/versions.zig @@ -2,8 +2,8 @@ const std = @import("std"); const config = @import("config.zig"); -const zig_url = std.Uri.parse(config.zig_mainfest_url) catch unreachable; -const zls_url = std.Uri.parse(config.zls_mainfest_url) catch unreachable; +const zig_url = config.zig_url; +const zls_url = config.zls_url; /// for which use pub const which = enum { From 7cd83559e9d5aa9f43ab1f47b421faabc1e94f2a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 31 Jul 2024 22:44:48 +0800 Subject: [PATCH 05/26] remove `hash.zig` fix --- src/hash.zig | 40 ---------------------------------------- src/install.zig | 3 +-- src/tools.zig | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 40 insertions(+), 42 deletions(-) delete mode 100644 src/hash.zig diff --git a/src/hash.zig b/src/hash.zig deleted file mode 100644 index d02204e..0000000 --- a/src/hash.zig +++ /dev/null @@ -1,40 +0,0 @@ -const std = @import("std"); -const crypto = std.crypto; -const testing = std.testing; -const mem = std.mem; - -pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: []const u8) bool { - if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters - - var actual_hash_bytes: [32]u8 = undefined; - var i: usize = 0; - - for (actual_hash_string) |char| { - const byte = switch (char) { - '0'...'9' => char - '0', - 'a'...'f' => char - 'a' + 10, - 'A'...'F' => char - 'A' + 10, - else => return false, // Invalid character in hash string - }; - - if (i % 2 == 0) { - actual_hash_bytes[i / 2] = byte << 4; - } else { - actual_hash_bytes[i / 2] |= byte; - } - - i += 1; - } - - return std.mem.eql(u8, computed_hash[0..], actual_hash_bytes[0..]); -} - -test "verify_hash basic test" { - const sample_hash: [32]u8 = [_]u8{ 0x33, 0x9a, 0x89, 0xdc, 0x08, 0x73, 0x6b, 0x84, 0xc4, 0x75, 0x2b, 0x3d, 0xed, 0xdc, 0x0f, 0x2c, 0x71, 0xb5, 0x0b, 0x66, 0xa2, 0x68, 0x5f, 0x26, 0x77, 0x9c, 0xbb, 0xac, 0x46, 0x11, 0x1b, 0x68 }; - - var sample_hash_hex: [64]u8 = undefined; - _ = std.fmt.bufPrint(&sample_hash_hex, "{}", .{std.fmt.fmtSliceHexLower(sample_hash[0..])}) catch unreachable; - - try testing.expect(verify_hash(sample_hash, &sample_hash_hex)); - try testing.expect(!verify_hash(sample_hash, "incorrect_hash")); -} diff --git a/src/install.zig b/src/install.zig index a62ef88..75dfd9c 100644 --- a/src/install.zig +++ b/src/install.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); -const hash = @import("hash.zig"); const download = @import("download.zig"); const architecture = @import("architecture.zig"); const tools = @import("tools.zig"); @@ -116,7 +115,7 @@ pub fn from_version(version: []const u8) !void { if (data.shasum) |actual_shasum| { const computed_hash = try download.content(allocator, data.name, data.tarball.?); if (computed_hash) |shasum| { - if (!hash.verify_hash(shasum, actual_shasum)) { + if (!tools.verify_hash(shasum, actual_shasum)) { return error.HashMismatch; } } diff --git a/src/tools.zig b/src/tools.zig index dad6154..0c8fc0d 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -1,6 +1,8 @@ const std = @import("std"); const builtin = @import("builtin"); +const testing = std.testing; + var allocator: std.mem.Allocator = undefined; var home_dir: []const u8 = undefined; @@ -47,3 +49,40 @@ pub fn get_zvm_path_segment(tmp_allocator: std.mem.Allocator, segment: []const u &[_][]const u8{ get_home(), ".zm", segment }, ); } + +/// for verify hash +pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: []const u8) bool { + if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters + + var actual_hash_bytes: [32]u8 = undefined; + var i: usize = 0; + + for (actual_hash_string) |char| { + const byte = switch (char) { + '0'...'9' => char - '0', + 'a'...'f' => char - 'a' + 10, + 'A'...'F' => char - 'A' + 10, + else => return false, // Invalid character in hash string + }; + + if (i % 2 == 0) { + actual_hash_bytes[i / 2] = byte << 4; + } else { + actual_hash_bytes[i / 2] |= byte; + } + + i += 1; + } + + return std.mem.eql(u8, computed_hash[0..], actual_hash_bytes[0..]); +} + +test "verify_hash basic test" { + const sample_hash: [32]u8 = [_]u8{ 0x33, 0x9a, 0x89, 0xdc, 0x08, 0x73, 0x6b, 0x84, 0xc4, 0x75, 0x2b, 0x3d, 0xed, 0xdc, 0x0f, 0x2c, 0x71, 0xb5, 0x0b, 0x66, 0xa2, 0x68, 0x5f, 0x26, 0x77, 0x9c, 0xbb, 0xac, 0x46, 0x11, 0x1b, 0x68 }; + + var sample_hash_hex: [64]u8 = undefined; + _ = std.fmt.bufPrint(&sample_hash_hex, "{}", .{std.fmt.fmtSliceHexLower(sample_hash[0..])}) catch unreachable; + + try testing.expect(verify_hash(sample_hash, &sample_hash_hex)); + try testing.expect(!verify_hash(sample_hash, "incorrect_hash")); +} From b8a6cbd543a73e84d3f9f9faec0b4c8da2a7244b Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 2 Aug 2024 10:51:36 +0800 Subject: [PATCH 06/26] feat: add `zvm ls zig` and `zvm ls zls` --- src/alias.zig | 3 +- src/command.zig | 43 ++++++++++++++++--- src/config.zig | 18 +++++--- src/download.zig | 11 ++++- src/meta.zig | 1 + src/tools.zig | 71 ++++++++++++++++++++++---------- src/versions.zig | 105 ----------------------------------------------- 7 files changed, 111 insertions(+), 141 deletions(-) delete mode 100644 src/versions.zig diff --git a/src/alias.zig b/src/alias.zig index 34ea396..ed0cca9 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -2,6 +2,7 @@ const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); const tools = @import("tools.zig"); +const config = @import("config.zig"); /// try to set zig version /// this will use system link on unix-like @@ -122,7 +123,7 @@ fn verify_zig_version(expected_version: []const u8) !void { /// try to get zig version fn retrieve_zig_version(allocator: std.mem.Allocator) ![]u8 { const home_dir = tools.get_home(); - const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", tools.zig_name }); + const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", config.zig_name }); defer allocator.free(current_zig_path); // here we must use the absolute path, we can not just use "zig" diff --git a/src/command.zig b/src/command.zig index 0950490..7eb0f33 100644 --- a/src/command.zig +++ b/src/command.zig @@ -2,10 +2,11 @@ const std = @import("std"); const builtin = @import("builtin"); const options = @import("options"); -const versions = @import("versions.zig"); const install = @import("install.zig"); const alias = @import("alias.zig"); const tools = @import("tools.zig"); +const meta = @import("meta.zig"); +const config = @import("config.zig"); // Command types pub const Command = enum { @@ -125,13 +126,43 @@ fn handle_alias(params: []const []const u8) !void { return std.process.execv(allocator, new_params); } -fn handle_list(_: ?[]const u8) !void { - // TODO: +fn handle_list(param: ?[]const u8) !void { const allocator = tools.get_allocator(); - var version_list = try versions.VersionList.init(allocator, .zig); - defer version_list.deinit(); - for (version_list.slice()) |version| { + const version_list: [][]const u8 = blk: { + if (param) |p| { + // when zls + if (tools.eql_str(p, "zls")) { + const res = try tools.http_get(allocator, config.zls_url); + defer allocator.free(res); + + var zls_meta = try meta.Zls.init(res, allocator); + defer zls_meta.deinit(); + + const version_list = try zls_meta.get_version_list(allocator); + break :blk version_list; + } else + // when not zig + if (!tools.eql_str(p, "zig")) { + std.debug.print("Error param, you can specify zig or zls\n", .{}); + return; + } + } + + // when param is null + const res = try tools.http_get(allocator, config.zig_url); + defer allocator.free(res); + + var zig_meta = try meta.Zig.init(res, allocator); + defer zig_meta.deinit(); + + const version_list = try zig_meta.get_version_list(allocator); + break :blk version_list; + }; + + defer tools.free_str_array(version_list, allocator); + + for (version_list) |version| { std.debug.print("{s}\n", .{version}); } } diff --git a/src/config.zig b/src/config.zig index deca22f..81fd311 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1,20 +1,28 @@ +// ! this file just store some config meta data const std = @import("std"); const builtin = @import("builtin"); -var allocator: std.mem.Allocator = undefined; -var home_dir: []const u8 = undefined; +/// global allocator +pub var allocator: std.mem.Allocator = undefined; +/// home dir environment variable +pub var home_dir: []const u8 = undefined; +/// zig meta data url pub const zig_meta_url: []const u8 = "https://ziglang.org/download/index.json"; +/// zls meta data url pub const zls_meta_url: []const u8 = "https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json"; +/// parsed zig url pub const zig_url = std.Uri.parse(zig_meta_url) catch unreachable; +/// parsed zls url pub const zls_url = std.Uri.parse(zls_meta_url) catch unreachable; +/// zig file name pub const zig_name = switch (builtin.os.tag) { .windows => "zig.exe", - .linux => "zig", - .macos => "zig", + .linux, .macos => "zig", else => @compileError("not support current platform"), }; -pub const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; +/// zig archive_ext +pub const zig_archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; diff --git a/src/download.zig b/src/download.zig index 1a3f1df..0a742a4 100644 --- a/src/download.zig +++ b/src/download.zig @@ -5,8 +5,8 @@ const tools = @import("tools.zig"); const sha2 = std.crypto.hash.sha2; const architecture = @import("architecture.zig"); const alias = @import("alias.zig"); -const hash = @import("hash.zig"); const lib = @import("extract.zig"); +const config = @import("config.zig"); pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u8) !?[32]u8 { assert(version.len > 0); @@ -111,7 +111,14 @@ fn download_and_extract( .reverse = false, }) orelse unreachable; - const file_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "zig-", platform_str, "-", version, ".", tools.archive_ext }); + const file_name = try std.mem.concat(allocator, u8, &.{ + "zig-", + platform_str, + "-", + version, + ".", + config.zig_archive_ext, + }); defer allocator.free(file_name); const total_size: usize = @intCast(req.response.content_length orelse 0); diff --git a/src/meta.zig b/src/meta.zig index 5e22d81..13ac033 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -76,5 +76,6 @@ pub const Zls = struct { const slice = try list.toOwnedSlice(); std.mem.reverse([]const u8, slice); + return slice; } }; diff --git a/src/tools.zig b/src/tools.zig index 0c8fc0d..f150ba7 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -1,27 +1,15 @@ +//! this file just contains util function const std = @import("std"); const builtin = @import("builtin"); +const config = @import("config.zig"); const testing = std.testing; -var allocator: std.mem.Allocator = undefined; -var home_dir: []const u8 = undefined; - -pub const log = std.log.scoped(.zvm); - -pub const zig_name = switch (builtin.os.tag) { - .windows => "zig.exe", - .linux => "zig", - .macos => "zig", - else => @compileError("not support current platform"), -}; - -pub const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; - /// Initialize the data. pub fn data_init(tmp_allocator: std.mem.Allocator) !void { - allocator = tmp_allocator; - home_dir = if (builtin.os.tag == .windows) - try std.process.getEnvVarOwned(allocator, "USERPROFILE") + config.allocator = tmp_allocator; + config.home_dir = if (builtin.os.tag == .windows) + try std.process.getEnvVarOwned(config.allocator, "USERPROFILE") else std.posix.getenv("HOME") orelse "."; } @@ -29,20 +17,20 @@ pub fn data_init(tmp_allocator: std.mem.Allocator) !void { /// Deinitialize the data. pub fn data_deinit() void { if (builtin.os.tag == .windows) - allocator.free(home_dir); + config.allocator.free(config.home_dir); } /// Get home directory. pub fn get_home() []const u8 { - return home_dir; + return config.home_dir; } /// Get the allocator. pub fn get_allocator() std.mem.Allocator { - return allocator; + return config.allocator; } -/// get zvm path segment +/// Get zvm path segment pub fn get_zvm_path_segment(tmp_allocator: std.mem.Allocator, segment: []const u8) ![]u8 { return std.fs.path.join( tmp_allocator, @@ -50,7 +38,15 @@ pub fn get_zvm_path_segment(tmp_allocator: std.mem.Allocator, segment: []const u ); } -/// for verify hash +/// Free str array +pub fn free_str_array(str_arr: []const []const u8, allocator: std.mem.Allocator) void { + for (str_arr) |str| + allocator.free(str); + + allocator.free(str_arr); +} + +/// For verifying hash pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: []const u8) bool { if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters @@ -86,3 +82,34 @@ test "verify_hash basic test" { try testing.expect(verify_hash(sample_hash, &sample_hash_hex)); try testing.expect(!verify_hash(sample_hash, "incorrect_hash")); } + +/// http get +pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { + + // create a http client + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + // we ceate a buffer to store the http response + var buf: [1024]u8 = undefined; // 256 * 1024 = 262kb + + // try open a request + var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); + defer req.deinit(); + + // send request and wait response + try req.send(); + try req.wait(); + + if (req.response.status != .ok) { + return error.ListResponseNotOk; + } + + const res = try req.reader().readAllAlloc(allocator, 256 * 1024); + return res; +} + +/// eql str +pub fn eql_str(str1: []const u8, str2: []const u8) bool { + return std.mem.eql(u8, str1, str2); +} diff --git a/src/versions.zig b/src/versions.zig deleted file mode 100644 index 707c3d4..0000000 --- a/src/versions.zig +++ /dev/null @@ -1,105 +0,0 @@ -//! For getting zig version info json from offical website -const std = @import("std"); -const config = @import("config.zig"); - -const zig_url = config.zig_url; -const zls_url = config.zls_url; - -/// for which use -pub const which = enum { - zig, - zls, -}; - -pub const VersionList = struct { - // this type will store - const List = std.ArrayList([]const u8); - - // store the version message - lists: List, - allocator: std.mem.Allocator, - - /// init the VersionList - pub fn init(allocator: std.mem.Allocator, use: which) !VersionList { - // create a http client - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - // we ceate a buffer to store the http response - var buffer: [262144]u8 = undefined; // 256 * 1024 = 262kb - - // try open a request - var req = try client.open( - .GET, - if (use == .zig) zig_url else zls_url, - .{ .server_header_buffer = &buffer }, - ); - defer req.deinit(); - - // send request and wait response - try req.send(); - try req.wait(); - - if (req.response.status != .ok) { - return error.ListResponseNotOk; - } - - const len = try req.readAll(buffer[0..]); - - // parse json - const json = try std.json.parseFromSlice(std.json.Value, allocator, buffer[0..len], .{}); - defer json.deinit(); - const root = json.value; - - var lists = std.ArrayList([]const u8).init(allocator); - - if (use == .zig) { - var iterate = root.object.iterator(); - while (iterate.next()) |entry| { - const key_ptr = entry.key_ptr; - const key = key_ptr.*; - - const key_copy = try allocator.dupe(u8, key); - try lists.append(key_copy); - } - - return VersionList{ - .lists = lists, - .allocator = allocator, - }; - } - - // for zls - - var zls_versions = root.object.get("versions") orelse return error.NotFoundZlsVersion; - - var iterate = zls_versions.object.iterator(); - while (iterate.next()) |entry| { - const key_ptr = entry.key_ptr; - const key = key_ptr.*; - - const key_copy = try allocator.dupe(u8, key); - try lists.append(key_copy); - } - - std.mem.reverse([]const u8, lists.items); - - return VersionList{ - .lists = lists, - .allocator = allocator, - }; - } - - // get the slice items - pub fn slice(self: *VersionList) [][]const u8 { - return self.lists.items; - } - - /// deinit will free memory - pub fn deinit(self: *VersionList) void { - defer self.lists.deinit(); - for (self.lists.items) |value| { - self.allocator.free(value); - } - } -}; From eaf79c54ac14810a0ad892afcbaf9f656ed41ba4 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 2 Aug 2024 19:03:01 +0800 Subject: [PATCH 07/26] remove useless comment --- src/command.zig | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/command.zig b/src/command.zig index 7eb0f33..1eff356 100644 --- a/src/command.zig +++ b/src/command.zig @@ -79,9 +79,6 @@ pub fn handle_command(params: []const []const u8) !void { break :kk null; }; - // const next_param = if (index + 1 < args.len) args[index + 1] else null; - // const is_version = if (next_param) |np| std.ascii.isDigit(np[0]) else false; - break :blk CommandData{ .cmd = opt.cmd, .subcmd = subcmd, From c980dd217bfc51ea95171502ec872c006b1439c6 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 2 Aug 2024 20:37:01 +0800 Subject: [PATCH 08/26] function integration, remove `fetch_version_data` and use `meta.Zig.get_version_data` --- src/install.zig | 93 ++++++++++--------------------------------------- src/meta.zig | 93 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tools.zig | 4 +-- 3 files changed, 113 insertions(+), 77 deletions(-) diff --git a/src/install.zig b/src/install.zig index 75dfd9c..c693703 100644 --- a/src/install.zig +++ b/src/install.zig @@ -4,6 +4,7 @@ const config = @import("config.zig"); const download = @import("download.zig"); const architecture = @import("architecture.zig"); const tools = @import("tools.zig"); +const meta = @import("meta.zig"); const Allocator = std.mem.Allocator; const io = std.io; const json = std.json; @@ -28,73 +29,6 @@ const Error = error{ ContentMissing, }; -fn fetch_version_data(allocator: Allocator, requested_version: []const u8, sub_key: []const u8) !?Version { - const uri = config.zig_url; - - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - var buffer: [262144]u8 = undefined; // 256 * 1024 = 262kb - - var req = try client.open(.GET, uri, .{ .server_header_buffer = &buffer }); - defer req.deinit(); - try req.send(); - try req.wait(); - - try std.testing.expect(req.response.status == .ok); - - const read_len = try req.readAll(buffer[0..]); - - const parsed = try std.json.parseFromSlice(std.json.Value, allocator, buffer[0..read_len], .{}); - defer parsed.deinit(); - const root = parsed.value; - - var it = root.object.iterator(); - while (it.next()) |entry| { - if (std.mem.eql(u8, entry.key_ptr.*, requested_version)) { - var version: ?[]const u8 = "not_set"; - var date: ?[]const u8 = null; - var tarball: ?[]const u8 = null; - var shasum: ?[]const u8 = null; - - var val_obj = entry.value_ptr.*.object.iterator(); - while (val_obj.next()) |value| { - if (std.mem.eql(u8, value.key_ptr.*, "version")) { - version = value.value_ptr.*.string; - } - if (std.mem.eql(u8, value.key_ptr.*, "date")) { - date = value.value_ptr.*.string; - } else if (std.mem.eql(u8, value.key_ptr.*, sub_key)) { - var nested_obj = value.value_ptr.*.object.iterator(); - while (nested_obj.next()) |nested_value| { - if (std.mem.eql(u8, nested_value.key_ptr.*, "tarball")) { - tarball = nested_value.value_ptr.*.string; - } - if (std.mem.eql(u8, nested_value.key_ptr.*, "shasum")) { - shasum = nested_value.value_ptr.*.string; - } - } - } - } - - if (date == null or tarball == null or shasum == null) { - return Error.MissingExpectedFields; - } - - const version_name = if (std.mem.eql(u8, requested_version, "master")) version.? else requested_version; - - return Version{ - .name = try allocator.dupe(u8, version_name), - .date = try allocator.dupe(u8, date.?), - .tarball = try allocator.dupe(u8, tarball.?), - .shasum = try allocator.dupe(u8, shasum.?), - }; - } - } - - return null; -} - /// Try to install the specified version of zig pub fn from_version(version: []const u8) !void { const allocator = tools.get_allocator(); @@ -108,16 +42,25 @@ pub fn from_version(version: []const u8) !void { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); - const version_data = try fetch_version_data(arena.allocator(), version, platform_str); + // get version data + const version_data: ?meta.Zig.VersionData = blk: { + const res = try tools.http_get(allocator, config.zig_url); + defer allocator.free(res); + + var zig_meta = try meta.Zig.init(res, allocator); + defer zig_meta.deinit(); + + break :blk try zig_meta.get_version_data(version, platform_str, allocator); + }; + if (version_data) |data| { - std.debug.print("Install {s}\n", .{data.name}); + defer data.deinit(allocator); + std.debug.print("Install {s}\n", .{data.version}); - if (data.shasum) |actual_shasum| { - const computed_hash = try download.content(allocator, data.name, data.tarball.?); - if (computed_hash) |shasum| { - if (!tools.verify_hash(shasum, actual_shasum)) { - return error.HashMismatch; - } + const computed_hash = try download.content(allocator, data.version, data.tarball); + if (computed_hash) |shasum| { + if (!tools.verify_hash(shasum, data.shasum)) { + return error.HashMismatch; } } } else { diff --git a/src/meta.zig b/src/meta.zig index 13ac033..278af19 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -1,13 +1,31 @@ const std = @import("std"); const config = @import("config.zig"); +const tools = @import("tools.zig"); const json = std.json; const Allocator = std.mem.Allocator; const jsonValue = std.json.Parsed(std.json.Value); +const eql_str = tools.eql_str; + pub const Zig = struct { data: jsonValue, + /// version data for zig + pub const VersionData = struct { + version: []const u8, + date: []const u8, + tarball: []const u8, + shasum: [64]u8, + size: usize, + + pub fn deinit(self: VersionData, allocator: Allocator) void { + allocator.free(self.version); + allocator.free(self.date); + allocator.free(self.tarball); + } + }; + // init the zig data pub fn init(raw: []const u8, allocator: Allocator) !Zig { const data = @@ -21,6 +39,81 @@ pub const Zig = struct { self.data.deinit(); } + pub fn get_version_data( + self: *Zig, + version: []const u8, + platform_str: []const u8, + allocator: Allocator, + ) !?VersionData { + // root node + var it = self.data.value.object.iterator(); + + while (it.next()) |entry| { + if (!eql_str(entry.key_ptr.*, version)) + continue; + + const now_version = entry.value_ptr; + var version_info = now_version.object.iterator(); + + var result: VersionData = undefined; + // for "version" field + var is_set_version = false; + + // traverse versions + while (version_info.next()) |version_info_entry| { + const version_info_key = version_info_entry.key_ptr.*; + const version_info_entry_val = version_info_entry.value_ptr; + + // get version + // only for "master" it will have "version" field + if (eql_str(version_info_key, "version")) { + result.version = try allocator.dupe(u8, version_info_entry_val.string); + is_set_version = true; + } else + // get date + if (eql_str(version_info_key, "date")) { + result.date = try allocator.dupe( + u8, + version_info_entry_val.string, + ); + } else + // skip the useless entry + if (eql_str(version_info_key, platform_str)) { + var platform_info = version_info_entry_val.object.iterator(); + + while (platform_info.next()) |playform_info_entry| { + const platform_info_entry_key = playform_info_entry.key_ptr.*; + const playform_info_entry_val = playform_info_entry.value_ptr; + + // get tarball + if (eql_str(platform_info_entry_key, "tarball")) { + result.tarball = try allocator.dupe(u8, playform_info_entry_val.string); + } else + // get shasum + if (eql_str(platform_info_entry_key, "shasum")) { + result.shasum = playform_info_entry_val.string[0..64].*; + } else + // get size + if (eql_str(platform_info_entry_key, "size")) { + const size = try std.fmt.parseUnsigned( + usize, + playform_info_entry_val.string, + 10, + ); + result.size = size; + } + } + } + } + + if (!is_set_version) + result.version = try allocator.dupe(u8, version); + + return result; + } + return null; + } + /// return the version list pub fn get_version_list(self: *Zig, allocator: Allocator) ![][]const u8 { const root = self.data.value; diff --git a/src/tools.zig b/src/tools.zig index f150ba7..2102dcb 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -47,8 +47,8 @@ pub fn free_str_array(str_arr: []const []const u8, allocator: std.mem.Allocator) } /// For verifying hash -pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: []const u8) bool { - if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters +pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: [64]u8) bool { + // if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters var actual_hash_bytes: [32]u8 = undefined; var i: usize = 0; From b0d1698a8615c527719cbae574c95513c13f90b2 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 4 Aug 2024 10:00:57 +0800 Subject: [PATCH 09/26] simplify code --- src/alias.zig | 100 ++--------------------------------------------- src/download.zig | 58 +++++++-------------------- src/tools.zig | 73 ++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 139 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index ed0cca9..e698e71 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -25,122 +25,30 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { assert(symlink_path.len > 0); if (builtin.os.tag == .windows) { - if (does_dir_exist(symlink_path)) try std.fs.deleteTreeAbsolute(symlink_path); - try copy_dir(zig_path, symlink_path); + if (tools.does_path_exist(symlink_path)) try std.fs.deleteTreeAbsolute(symlink_path); + try tools.copy_dir(zig_path, symlink_path); return; } // when platform is not windows, this is execute here // when file exist(it is a systemlink), delete it - if (does_file_exist(symlink_path)) try std.fs.cwd().deleteFile(symlink_path); + if (tools.does_path_exist(symlink_path)) try std.fs.cwd().deleteFile(symlink_path); // system link it try std.posix.symlink(zig_path, symlink_path); } -/// Nested copy dir -/// only copy dir and file, no including link -fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { - assert(source_dir.len > 0); - assert(dest_dir.len > 0); - - var source = try std.fs.openDirAbsolute(source_dir, .{ .iterate = true }); - defer source.close(); - - std.fs.makeDirAbsolute(dest_dir) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => { - tools.log.err("Failed to create directory: {s}", .{dest_dir}); - return err; - }, - }; - - var dest = try std.fs.openDirAbsolute(dest_dir, .{ .iterate = true }); - defer dest.close(); - - var iterate = source.iterate(); - const allocator = tools.get_allocator(); - while (try iterate.next()) |entry| { - const entry_name = entry.name; - - const source_sub_path = try std.fs.path.join(allocator, &.{ source_dir, entry_name }); - defer allocator.free(source_sub_path); - - const dest_sub_path = try std.fs.path.join(allocator, &.{ dest_dir, entry_name }); - defer allocator.free(dest_sub_path); - - switch (entry.kind) { - .directory => try copy_dir(source_sub_path, dest_sub_path), - .file => try std.fs.copyFileAbsolute(source_sub_path, dest_sub_path, .{}), - else => {}, - } - } -} - -/// detect the dir whether exist -fn does_dir_exist(path: []const u8) bool { - const result = blk: { - std.fs.accessAbsolute(path, .{}) catch |err| { - if (err == error.FileNotFound) - break :blk false; - break :blk true; - }; - break :blk true; - }; - return result; -} - -/// detect the dir whether exist -fn does_file_exist(path: []const u8) bool { - const result = blk: { - std.fs.accessAbsolute(path, .{}) catch |err| { - if (err == error.FileNotFound) - break :blk false; - break :blk true; - }; - break :blk true; - }; - return result; -} - /// verify current zig version fn verify_zig_version(expected_version: []const u8) !void { const allocator = tools.get_allocator(); - const actual_version = try retrieve_zig_version(allocator); + const actual_version = try tools.get_zig_version(allocator); defer allocator.free(actual_version); - assert(actual_version.len > 0); - if (!std.mem.eql(u8, expected_version, actual_version)) { std.debug.print("Expected Zig version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); } else { std.debug.print("Now using Zig version {s}\n", .{expected_version}); } } - -/// try to get zig version -fn retrieve_zig_version(allocator: std.mem.Allocator) ![]u8 { - const home_dir = tools.get_home(); - const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", config.zig_name }); - defer allocator.free(current_zig_path); - - // here we must use the absolute path, we can not just use "zig" - // because child process will use environment variable - var child_process = std.process.Child.init(&[_][]const u8{ current_zig_path, "version" }, allocator); - - child_process.stdin_behavior = .Close; - child_process.stdout_behavior = .Pipe; - child_process.stderr_behavior = .Close; - - try child_process.spawn(); - - if (child_process.stdout) |stdout| { - const version = try stdout.reader().readUntilDelimiterOrEofAlloc(allocator, '\n', 100) orelse return error.EmptyVersion; - assert(version.len > 0); - return version; - } - - return error.FailedToReadVersion; -} diff --git a/src/download.zig b/src/download.zig index 0a742a4..f2e9bd2 100644 --- a/src/download.zig +++ b/src/download.zig @@ -22,13 +22,7 @@ pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u const version_path = try tools.get_zvm_path_segment(data_allocator, "versions"); defer data_allocator.free(version_path); - std.fs.cwd().makePath(version_path) catch |err| switch (err) { - error.PathAlreadyExists => {}, - else => { - std.debug.print("Failed to create versions directory: {}\n", .{err}); - return err; - }, - }; + try tools.try_create_path(version_path); const uri = std.Uri.parse(url) catch unreachable; const version_folder_name = try std.fmt.allocPrint(allocator, "versions/{s}", .{version}); @@ -37,7 +31,7 @@ pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u const version_folder_path = try tools.get_zvm_path_segment(data_allocator, version_folder_name); defer data_allocator.free(version_folder_path); - if (check_existing_version(version_folder_path)) { + if (tools.does_path_exist(version_folder_path)) { std.debug.print("→ Version {s} is already installed.\n", .{version}); std.debug.print("Do you want to reinstall? (\x1b[1mY\x1b[0mes/\x1b[1mN\x1b[0mo): ", .{}); @@ -67,12 +61,6 @@ pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u return computed_hash; } -fn check_existing_version(version_path: []const u8) bool { - const open_dir_options = .{ .access_sub_paths = true, .no_follow = false }; - _ = std.fs.cwd().openDir(version_path, open_dir_options) catch return false; - return true; -} - fn confirm_user_choice() bool { var buffer: [4]u8 = undefined; _ = std.io.getStdIn().read(buffer[0..]) catch return false; @@ -102,7 +90,14 @@ fn download_and_extract( try req.wait(); try std.testing.expect(req.response.status == .ok); - var zvm_dir = try open_or_create_zvm_dir(); + const zvm_path = try tools.get_zvm_path_segment(allocator, ""); + defer allocator.free(zvm_path); + + var zvm_dir = std.fs.cwd().makeOpenPath(zvm_path, .{}) catch |err| { + std.debug.print("sorry, open zvm path failed, erro is {}\n", .{err}); + std.process.exit(1); + }; + defer zvm_dir.close(); const platform_str = try architecture.platform_str(architecture.DetectParams{ @@ -129,7 +124,6 @@ fn download_and_extract( var download_node = root_node.start(download_message, total_size); const file_stream = try zvm_dir.createFile(file_name, .{}); - defer file_stream.close(); std.debug.print("Download complete, file written: {s}\n", .{file_name}); @@ -145,15 +139,13 @@ fn download_and_extract( sha256.update(buffer[0..bytes_read]); try file_stream.writeAll(buffer[0..bytes_read]); } + file_stream.close(); download_node.end(); var extract_node = root_node.start("Extracting", 1); const data_allocator = tools.get_allocator(); - const zvm_path = try tools.get_zvm_path_segment(data_allocator, ""); - defer data_allocator.free(zvm_path); - const downloaded_file_path = try std.fs.path.join(data_allocator, &.{ zvm_path, file_name }); defer data_allocator.free(downloaded_file_path); @@ -184,29 +176,9 @@ fn download_and_extract( } fn open_or_create_zvm_dir() !std.fs.Dir { - const zvm_path = try tools.get_zvm_path_segment(tools.get_allocator(), ""); - defer tools.get_allocator().free(zvm_path); - - const open_dir_options = .{ .access_sub_paths = true, .no_follow = false }; - const potential_dir = std.fs.cwd().openDir(zvm_path, open_dir_options); - - if (potential_dir) |dir| { - return dir; - } else |err| switch (err) { - error.FileNotFound => { - std.debug.print("→ Directory not found. Creating: {s}...\n", .{zvm_path}); - - if (std.fs.cwd().makeDir(zvm_path)) |_| { - std.debug.print("✓ Directory created successfully: {s}\n", .{zvm_path}); - } else |err_make_dir| { - std.debug.print("✗ Error: Failed to create directory. Reason: {}\n", .{err_make_dir}); - } + const allocator = tools.get_allocator(); + const zvm_path = try tools.get_zvm_path_segment(allocator, ""); + defer allocator.free(zvm_path); - return std.fs.cwd().openDir(zvm_path, open_dir_options); - }, - else => |e| { - std.debug.print("Unexpected error when checking directory: {}\n", .{e}); - return e; - }, - } + return try std.fs.cwd().makeOpenPath(zvm_path, .{}); } diff --git a/src/tools.zig b/src/tools.zig index 2102dcb..14ec293 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -113,3 +113,76 @@ pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { pub fn eql_str(str1: []const u8, str2: []const u8) bool { return std.mem.eql(u8, str1, str2); } + +/// try to create path +pub fn try_create_path(path: []const u8) !void { + std.fs.cwd().makePath(path) catch |err| { + if (err != error.PathAlreadyExists) + return err; + }; +} + +/// try to get zig version +pub fn get_zig_version(allocator: std.mem.Allocator) ![]u8 { + const home_dir = get_home(); + const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", config.zig_name }); + defer allocator.free(current_zig_path); + + // here we must use the absolute path, we can not just use "zig" + // because child process will use environment variable + var child_process = std.process.Child.init(&[_][]const u8{ current_zig_path, "version" }, allocator); + + child_process.stdin_behavior = .Close; + child_process.stdout_behavior = .Pipe; + child_process.stderr_behavior = .Close; + + try child_process.spawn(); + + if (child_process.stdout) |stdout| { + const version = try stdout.reader().readUntilDelimiterOrEofAlloc(allocator, '\n', 100) orelse return error.EmptyVersion; + return version; + } + + return error.FailedToReadVersion; +} + +// check dir exist +pub fn does_path_exist(version_path: []const u8) bool { + std.fs.accessAbsolute(version_path, .{}) catch |err| { + if (err == error.FileNotFound) return false; + }; + return true; +} + +/// Nested copy dir +/// only copy dir and file, no including link +pub fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { + var source = try std.fs.openDirAbsolute(source_dir, .{ .iterate = true }); + defer source.close(); + + std.fs.makeDirAbsolute(dest_dir) catch |err| { + if (err != error.PathAlreadyExists) + return err; + }; + + var dest = try std.fs.openDirAbsolute(dest_dir, .{ .iterate = true }); + defer dest.close(); + + var iterate = source.iterate(); + const allocator = get_allocator(); + while (try iterate.next()) |entry| { + const entry_name = entry.name; + + const source_sub_path = try std.fs.path.join(allocator, &.{ source_dir, entry_name }); + defer allocator.free(source_sub_path); + + const dest_sub_path = try std.fs.path.join(allocator, &.{ dest_dir, entry_name }); + defer allocator.free(dest_sub_path); + + switch (entry.kind) { + .directory => try copy_dir(source_sub_path, dest_sub_path), + .file => try std.fs.copyFileAbsolute(source_sub_path, dest_sub_path, .{}), + else => {}, + } + } +} From 7e3cd05fc52edf7fb0450c888968744a18a622ab Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 4 Aug 2024 16:52:49 +0800 Subject: [PATCH 10/26] Refactoring the zig download section --- src/alias.zig | 2 +- src/command.zig | 4 +- src/config.zig | 3 + src/download.zig | 203 ++++++++++++----------------------------------- src/extract.zig | 18 ++++- src/install.zig | 72 ++++++++--------- src/tools.zig | 15 +++- 7 files changed, 118 insertions(+), 199 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index e698e71..c698f8a 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -13,7 +13,7 @@ pub fn set_zig_version(version: []const u8) !void { const arena_allocator = arena.allocator(); const user_home = tools.get_home(); - const version_path = try std.fs.path.join(arena_allocator, &[_][]const u8{ user_home, ".zm", "versions", version }); + const version_path = try std.fs.path.join(arena_allocator, &[_][]const u8{ user_home, ".zm", "version", version }); const symlink_path = try tools.get_zvm_path_segment(arena_allocator, "current"); try update_current(version_path, symlink_path); diff --git a/src/command.zig b/src/command.zig index 1eff356..1412217 100644 --- a/src/command.zig +++ b/src/command.zig @@ -168,7 +168,7 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { if (subcmd) |scmd| { if (std.mem.eql(u8, scmd, "zig")) { if (param) |version| { - try install.from_version(version); + try install.install_zig(version); } else { std.debug.print("Please specify a version to install using 'install zig '.\n", .{}); } @@ -179,7 +179,7 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { std.debug.print("Unknown subcommand '{s}'. Use 'install zig ' or 'install zls '.\n", .{scmd}); } } else if (param) |version| { - try install.from_version(version); + try install.install_zig(version); } else { std.debug.print("Error: Please specify a version to install using 'install '.\n", .{}); } diff --git a/src/config.zig b/src/config.zig index 81fd311..aa2a02d 100644 --- a/src/config.zig +++ b/src/config.zig @@ -7,6 +7,9 @@ pub var allocator: std.mem.Allocator = undefined; /// home dir environment variable pub var home_dir: []const u8 = undefined; +/// global progress root node +pub var progress_root: std.Progress.Node = undefined; + /// zig meta data url pub const zig_meta_url: []const u8 = "https://ziglang.org/download/index.json"; /// zls meta data url diff --git a/src/download.zig b/src/download.zig index f2e9bd2..7518cc8 100644 --- a/src/download.zig +++ b/src/download.zig @@ -1,184 +1,81 @@ const std = @import("std"); -const assert = std.debug.assert; const builtin = @import("builtin"); const tools = @import("tools.zig"); -const sha2 = std.crypto.hash.sha2; -const architecture = @import("architecture.zig"); -const alias = @import("alias.zig"); -const lib = @import("extract.zig"); -const config = @import("config.zig"); - -pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u8) !?[32]u8 { - assert(version.len > 0); - assert(url.len > 0); - - const root_node = std.Progress.start(.{ - .root_name = "", - .estimated_total_items = 4, - }); - defer root_node.end(); - - const data_allocator = tools.get_allocator(); - const version_path = try tools.get_zvm_path_segment(data_allocator, "versions"); - defer data_allocator.free(version_path); - - try tools.try_create_path(version_path); - - const uri = std.Uri.parse(url) catch unreachable; - const version_folder_name = try std.fmt.allocPrint(allocator, "versions/{s}", .{version}); - defer allocator.free(version_folder_name); - - const version_folder_path = try tools.get_zvm_path_segment(data_allocator, version_folder_name); - defer data_allocator.free(version_folder_path); - - if (tools.does_path_exist(version_folder_path)) { - std.debug.print("→ Version {s} is already installed.\n", .{version}); - std.debug.print("Do you want to reinstall? (\x1b[1mY\x1b[0mes/\x1b[1mN\x1b[0mo): ", .{}); - - if (!confirm_user_choice()) { - std.debug.print("Do you want to set version {s} as the default? (\x1b[1mY\x1b[0mes/\x1b[1mN\x1b[0mo): ", .{version}); - if (confirm_user_choice()) { - try alias.set_zig_version(version); - std.debug.print("Version {s} has been set as the default.\n", .{version}); - return null; - } else { - std.debug.print("Aborting...\n", .{}); - return null; - } - } - - try std.fs.cwd().deleteTree(version_folder_path); - } else { - std.debug.print("→ Version {s} is not installed. Beginning download...\n", .{version}); - } - - const computed_hash = try download_and_extract(allocator, uri, version_path, version, root_node); - - var set_version_node = root_node.start("Setting Version", 1); - try alias.set_zig_version(version); - set_version_node.end(); - - return computed_hash; -} -fn confirm_user_choice() bool { - var buffer: [4]u8 = undefined; - _ = std.io.getStdIn().read(buffer[0..]) catch return false; - - return std.ascii.toLower(buffer[0]) == 'y'; -} +const sha2 = std.crypto.hash.sha2; -fn download_and_extract( - allocator: std.mem.Allocator, +/// download the url +/// and verify hashsum (if exist) +pub fn download( uri: std.Uri, - version_path: []const u8, - version: []const u8, - root_node: std.Progress.Node, -) ![32]u8 { - assert(version_path.len > 0); - assert(version.len > 0); + file_name: []const u8, + shasum: ?[64]u8, +) !std.fs.File { + // whether verify hashsum + const if_hash = shasum != null; + // allocator + const allocator = tools.get_allocator(); + + // http client var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); - var header_buffer: [262144]u8 = undefined; // 256 * 1024 = 262kb + var header_buffer: [1024]u8 = undefined; // 1024b var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); defer req.deinit(); try req.send(); try req.wait(); - try std.testing.expect(req.response.status == .ok); - - const zvm_path = try tools.get_zvm_path_segment(allocator, ""); - defer allocator.free(zvm_path); - var zvm_dir = std.fs.cwd().makeOpenPath(zvm_path, .{}) catch |err| { - std.debug.print("sorry, open zvm path failed, erro is {}\n", .{err}); - std.process.exit(1); - }; - - defer zvm_dir.close(); - - const platform_str = try architecture.platform_str(architecture.DetectParams{ - .os = builtin.os.tag, - .arch = builtin.cpu.arch, - .reverse = false, - }) orelse unreachable; - - const file_name = try std.mem.concat(allocator, u8, &.{ - "zig-", - platform_str, - "-", - version, - ".", - config.zig_archive_ext, - }); - defer allocator.free(file_name); + // ensure req successfully + if (req.response.status != .ok) + return error.DownFailed; - const total_size: usize = @intCast(req.response.content_length orelse 0); - var downloaded_bytes: usize = 0; + // NOTE: + // const total_size: usize = @intCast(req.response.content_length orelse 0); - const download_message = try std.fmt.allocPrint(allocator, "Downloading Zig version {s} for platform {s}...", .{ version, platform_str }); - defer allocator.free(download_message); - var download_node = root_node.start(download_message, total_size); + // this file store the downloaded src + const zvm_path = try tools.get_zvm_path_segment(allocator, "store"); + defer allocator.free(zvm_path); - const file_stream = try zvm_dir.createFile(file_name, .{}); + var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); + defer store.close(); - std.debug.print("Download complete, file written: {s}\n", .{file_name}); + // create a new file + const new_file = try store.createFile(file_name, .{ + .read = true, + }); - var sha256 = sha2.Sha256.init(.{}); + // whether enable hashsum + var sha256 = if (if_hash) sha2.Sha256.init(.{}) else undefined; + // the tmp buffer to store the receive data + var buffer: [512]u8 = undefined; + // get reader + const reader = req.reader(); while (true) { - var buffer: [8192]u8 = undefined; - const bytes_read = try req.reader().read(buffer[0..]); - if (bytes_read == 0) break; - - downloaded_bytes += bytes_read; - download_node.setCompletedItems(downloaded_bytes); - sha256.update(buffer[0..bytes_read]); - try file_stream.writeAll(buffer[0..bytes_read]); + // the read byte number + const byte_nums = try reader.read(&buffer); + if (byte_nums == 0) + break; + if (if_hash) + sha256.update(buffer[0..byte_nums]); + // write to file + try new_file.writeAll(buffer[0..byte_nums]); } - file_stream.close(); - - download_node.end(); - - var extract_node = root_node.start("Extracting", 1); - const data_allocator = tools.get_allocator(); - - const downloaded_file_path = try std.fs.path.join(data_allocator, &.{ zvm_path, file_name }); - defer data_allocator.free(downloaded_file_path); - - std.debug.print("Downloaded file path: {s}\n", .{downloaded_file_path}); - const folder_path = try std.fs.path.join(allocator, &.{ version_path, version }); - defer allocator.free(folder_path); + // when calculate hashsum + if (if_hash) { + var result = std.mem.zeroes([32]u8); + sha256.final(&result); - std.fs.makeDirAbsolute(folder_path) catch |err| { - std.debug.print("makeDirAbsolute: {any}\n", .{err}); - }; - - const zvm_dir_version = try std.fs.openDirAbsolute(folder_path, .{}); - const downloaded_file = try zvm_dir.openFile(downloaded_file_path, .{}); - defer downloaded_file.close(); - - if (builtin.os.tag == .windows) { - try lib.extract_zip_dir(zvm_dir_version, downloaded_file); - } else { - try lib.extract_tarxz_to_dir(allocator, zvm_dir_version, downloaded_file); + if (!tools.verify_hash(result, shasum.?)) + return error.HashMismatch; } - extract_node.end(); - - var result: [32]u8 = undefined; - sha256.final(&result); - return result; -} - -fn open_or_create_zvm_dir() !std.fs.Dir { - const allocator = tools.get_allocator(); - const zvm_path = try tools.get_zvm_path_segment(allocator, ""); - defer allocator.free(zvm_path); + try new_file.seekTo(0); - return try std.fs.cwd().makeOpenPath(zvm_path, .{}); + return new_file; } diff --git a/src/extract.zig b/src/extract.zig index c2e5eaa..1a4b1f9 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -5,11 +5,23 @@ const tools = @import("tools.zig"); const xz = std.compress.xz; const tar = std.tar; +/// extract file to out_dir +pub fn extrace( + out_dir: std.fs.Dir, + file: std.fs.File, + file_type: enum { tarxz, zip }, +) !void { + switch (file_type) { + .zip => try extract_zip_dir(out_dir, file), + .tarxz => try extract_tarxz_to_dir(out_dir, file), + } +} + /// extract tar.xz to dir -pub fn extract_tarxz_to_dir(allocator: std.mem.Allocator, out_dir: std.fs.Dir, file: std.fs.File) !void { +fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { var buffered_reader = std.io.bufferedReader(file.reader()); - var decompressed = try xz.decompress(allocator, buffered_reader.reader()); + var decompressed = try xz.decompress(tools.get_allocator(), buffered_reader.reader()); defer decompressed.deinit(); try tar.pipeToFileSystem( @@ -20,7 +32,7 @@ pub fn extract_tarxz_to_dir(allocator: std.mem.Allocator, out_dir: std.fs.Dir, f } /// extract zip to directory -pub fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { +fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); defer arena.deinit(); diff --git a/src/install.zig b/src/install.zig index c693703..566dddf 100644 --- a/src/install.zig +++ b/src/install.zig @@ -4,13 +4,9 @@ const config = @import("config.zig"); const download = @import("download.zig"); const architecture = @import("architecture.zig"); const tools = @import("tools.zig"); +const alias = @import("alias.zig"); const meta = @import("meta.zig"); -const Allocator = std.mem.Allocator; -const io = std.io; -const json = std.json; -const fs = std.fs; -const crypto = std.crypto; -const os = std.os; +const extract = @import("extract.zig"); const Version = struct { name: []const u8, @@ -19,18 +15,8 @@ const Version = struct { shasum: ?[]const u8, }; -const Error = error{ - HttpError, - UnsupportedVersion, - JSONParsingFailed, - MissingExpectedFields, - FileError, - HashMismatch, - ContentMissing, -}; - /// Try to install the specified version of zig -pub fn from_version(version: []const u8) !void { +pub fn install_zig(version: []const u8) !void { const allocator = tools.get_allocator(); const platform_str = try architecture.platform_str(architecture.DetectParams{ @@ -42,28 +28,42 @@ pub fn from_version(version: []const u8) !void { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); + const arena_allocator = arena.allocator(); + // get version data - const version_data: ?meta.Zig.VersionData = blk: { - const res = try tools.http_get(allocator, config.zig_url); - defer allocator.free(res); + const version_data: meta.Zig.VersionData = blk: { + const res = try tools.http_get(arena_allocator, config.zig_url); + var zig_meta = try meta.Zig.init(res, arena_allocator); + const tmp_val = try zig_meta.get_version_data(version, platform_str, arena_allocator); + break :blk tmp_val orelse return error.UnsupportedVersion; + }; - var zig_meta = try meta.Zig.init(res, allocator); - defer zig_meta.deinit(); + std.debug.print("Install {s}\n", .{version_data.version}); - break :blk try zig_meta.get_version_data(version, platform_str, allocator); - }; + const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ + .os = builtin.os.tag, + .arch = builtin.cpu.arch, + .reverse = false, + }) orelse unreachable; + + const file_name = try std.mem.concat( + arena_allocator, + u8, + &.{ "zig-", reverse_platform_str, "-", version, ".", config.zig_archive_ext }, + ); + + const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; + const new_file = try download.download(parsed_uri, file_name, version_data.shasum); + defer new_file.close(); + + // get version path + const version_path = try tools.get_zvm_path_segment(arena_allocator, "version"); + // get extract path + const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); + try tools.try_create_path(extract_path); + const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - if (version_data) |data| { - defer data.deinit(allocator); - std.debug.print("Install {s}\n", .{data.version}); + try extract.extrace(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz); - const computed_hash = try download.content(allocator, data.version, data.tarball); - if (computed_hash) |shasum| { - if (!tools.verify_hash(shasum, data.shasum)) { - return error.HashMismatch; - } - } - } else { - return Error.UnsupportedVersion; - } + try alias.set_zig_version(version); } diff --git a/src/tools.zig b/src/tools.zig index 14ec293..5a12627 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -12,12 +12,21 @@ pub fn data_init(tmp_allocator: std.mem.Allocator) !void { try std.process.getEnvVarOwned(config.allocator, "USERPROFILE") else std.posix.getenv("HOME") orelse "."; + + // config.progress_root = std.Progress.start(.{ .root_name = "zvm" }); } /// Deinitialize the data. pub fn data_deinit() void { if (builtin.os.tag == .windows) config.allocator.free(config.home_dir); + + // config.progress_root.end(); +} + +/// new progress node +pub fn new_progress_node(name: []const u8, estimated_total_items: usize) std.Progress.Node { + return config.progress_root.start(name, estimated_total_items); } /// Get home directory. @@ -116,10 +125,8 @@ pub fn eql_str(str1: []const u8, str2: []const u8) bool { /// try to create path pub fn try_create_path(path: []const u8) !void { - std.fs.cwd().makePath(path) catch |err| { - if (err != error.PathAlreadyExists) - return err; - }; + std.fs.cwd().makePath(path) catch |err| + if (err != error.PathAlreadyExists) return err; } /// try to get zig version From f553563438f1bdd6a52da8efd8c7f93f9aa4af72 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 4 Aug 2024 17:38:49 +0800 Subject: [PATCH 11/26] Before downloading, check whether the file already exists. If there is a hash, try to verify it directly. --- src/download.zig | 51 +++++++++++++++++++++++++++++++++++++++--------- src/install.zig | 2 +- src/tools.zig | 8 ++++++++ 3 files changed, 51 insertions(+), 10 deletions(-) diff --git a/src/download.zig b/src/download.zig index 7518cc8..e1cc79b 100644 --- a/src/download.zig +++ b/src/download.zig @@ -10,13 +10,49 @@ pub fn download( uri: std.Uri, file_name: []const u8, shasum: ?[64]u8, + size: ?usize, ) !std.fs.File { + // whether verify hashsum const if_hash = shasum != null; // allocator const allocator = tools.get_allocator(); + // this file store the downloaded src + const zvm_path = try tools.get_zvm_path_segment(allocator, "store"); + defer allocator.free(zvm_path); + + var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); + defer store.close(); + + // if file exist + // and provide shasum + // then calculate hash and verify, return the file if eql + // otherwise delete this file + if (tools.does_path_exist2(store, file_name)) { + if (if_hash) { + var sha256 = sha2.Sha256.init(.{}); + const file = try store.openFile(file_name, .{}); + var buffer: [512]u8 = undefined; + while (true) { + const byte_nums = try file.read(&buffer); + if (byte_nums == 0) + break; + + sha256.update(buffer[0..byte_nums]); + } + var result = std.mem.zeroes([32]u8); + sha256.final(&result); + + if (tools.verify_hash(result, shasum.?)) { + try file.seekTo(0); + return file; + } + } + try std.fs.deleteFileAbsolute(file_name); + } + // http client var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); @@ -33,15 +69,12 @@ pub fn download( if (req.response.status != .ok) return error.DownFailed; - // NOTE: - // const total_size: usize = @intCast(req.response.content_length orelse 0); - - // this file store the downloaded src - const zvm_path = try tools.get_zvm_path_segment(allocator, "store"); - defer allocator.free(zvm_path); - - var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); - defer store.close(); + // Compare file sizes + if (size) |ss| { + const total_size: usize = @intCast(req.response.content_length orelse 0); + if (ss != total_size) + return error.IncorrectSize; + } // create a new file const new_file = try store.createFile(file_name, .{ diff --git a/src/install.zig b/src/install.zig index 566dddf..f7dc614 100644 --- a/src/install.zig +++ b/src/install.zig @@ -53,7 +53,7 @@ pub fn install_zig(version: []const u8) !void { ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; - const new_file = try download.download(parsed_uri, file_name, version_data.shasum); + const new_file = try download.download(parsed_uri, file_name, version_data.shasum, version_data.size); defer new_file.close(); // get version path diff --git a/src/tools.zig b/src/tools.zig index 5a12627..24b8716 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -161,6 +161,14 @@ pub fn does_path_exist(version_path: []const u8) bool { return true; } +// check dir exist +pub fn does_path_exist2(dir: std.fs.Dir, file_name: []const u8) bool { + dir.access(file_name, .{}) catch |err| { + if (err == error.FileNotFound) return false; + }; + return true; +} + /// Nested copy dir /// only copy dir and file, no including link pub fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { From 37cf22cd1736717882b629f6994f3795afb7090a Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 4 Aug 2024 17:47:40 +0800 Subject: [PATCH 12/26] When there is content in the version directory, use it directly --- src/install.zig | 16 ++++++++++------ src/tools.zig | 8 ++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/src/install.zig b/src/install.zig index f7dc614..cd97534 100644 --- a/src/install.zig +++ b/src/install.zig @@ -30,6 +30,16 @@ pub fn install_zig(version: []const u8) !void { const arena_allocator = arena.allocator(); + // get version path + const version_path = try tools.get_zvm_path_segment(arena_allocator, "version"); + // get extract path + const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); + + if (tools.does_path_exist(extract_path)) { + try alias.set_zig_version(version); + return; + } + // get version data const version_data: meta.Zig.VersionData = blk: { const res = try tools.http_get(arena_allocator, config.zig_url); @@ -38,8 +48,6 @@ pub fn install_zig(version: []const u8) !void { break :blk tmp_val orelse return error.UnsupportedVersion; }; - std.debug.print("Install {s}\n", .{version_data.version}); - const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ .os = builtin.os.tag, .arch = builtin.cpu.arch, @@ -56,10 +64,6 @@ pub fn install_zig(version: []const u8) !void { const new_file = try download.download(parsed_uri, file_name, version_data.shasum, version_data.size); defer new_file.close(); - // get version path - const version_path = try tools.get_zvm_path_segment(arena_allocator, "version"); - // get extract path - const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); try tools.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); diff --git a/src/tools.zig b/src/tools.zig index 24b8716..04bf612 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -154,16 +154,16 @@ pub fn get_zig_version(allocator: std.mem.Allocator) ![]u8 { } // check dir exist -pub fn does_path_exist(version_path: []const u8) bool { - std.fs.accessAbsolute(version_path, .{}) catch |err| { +pub fn does_path_exist(path: []const u8) bool { + std.fs.accessAbsolute(path, .{}) catch |err| { if (err == error.FileNotFound) return false; }; return true; } // check dir exist -pub fn does_path_exist2(dir: std.fs.Dir, file_name: []const u8) bool { - dir.access(file_name, .{}) catch |err| { +pub fn does_path_exist2(dir: std.fs.Dir, path: []const u8) bool { + dir.access(path, .{}) catch |err| { if (err == error.FileNotFound) return false; }; return true; From 1d42ad8a1523f39246eec4295ce10578ed16e4dd Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Mon, 5 Aug 2024 23:15:06 +0800 Subject: [PATCH 13/26] Modify zls resource acquisition logic --- src/config.zig | 2 +- src/meta.zig | 14 +++----------- src/tools.zig | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/config.zig b/src/config.zig index aa2a02d..03fcce0 100644 --- a/src/config.zig +++ b/src/config.zig @@ -13,7 +13,7 @@ pub var progress_root: std.Progress.Node = undefined; /// zig meta data url pub const zig_meta_url: []const u8 = "https://ziglang.org/download/index.json"; /// zls meta data url -pub const zls_meta_url: []const u8 = "https://zigtools-releases.nyc3.digitaloceanspaces.com/zls/index.json"; +pub const zls_meta_url: []const u8 = "https://api.github.com/repos/zigtools/zls/releases"; /// parsed zig url pub const zig_url = std.Uri.parse(zig_meta_url) catch unreachable; diff --git a/src/meta.zig b/src/meta.zig index 278af19..8792ac0 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -151,24 +151,16 @@ pub const Zls = struct { /// return the version list pub fn get_version_list(self: *Zls, allocator: Allocator) ![][]const u8 { - var zls_versions = - self.data.value.object.get("versions") orelse - return error.NotFoundZlsVersion; - var list = std.ArrayList([]const u8).init(allocator); - var iterate = zls_versions.object.iterator(); - while (iterate.next()) |entry| { - const key_ptr = entry.key_ptr; - const key = key_ptr.*; + for (self.data.value.array.items) |item| { + const tag = item.object.get("tag_name") orelse continue; - const key_copy = try allocator.dupe(u8, key); + const key_copy = try allocator.dupe(u8, tag.string); try list.append(key_copy); } const slice = try list.toOwnedSlice(); - - std.mem.reverse([]const u8, slice); return slice; } }; diff --git a/src/tools.zig b/src/tools.zig index 04bf612..b551e02 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -100,7 +100,7 @@ pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { defer client.deinit(); // we ceate a buffer to store the http response - var buf: [1024]u8 = undefined; // 256 * 1024 = 262kb + var buf: [2048]u8 = undefined; // try open a request var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); From 305c5e971cbcc4a147c357045f7d4694752b3188 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Mon, 5 Aug 2024 23:19:06 +0800 Subject: [PATCH 14/26] remove download file --- src/download.zig | 114 ----------------------------------------------- src/install.zig | 3 +- src/tools.zig | 110 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+), 116 deletions(-) delete mode 100644 src/download.zig diff --git a/src/download.zig b/src/download.zig deleted file mode 100644 index e1cc79b..0000000 --- a/src/download.zig +++ /dev/null @@ -1,114 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const tools = @import("tools.zig"); - -const sha2 = std.crypto.hash.sha2; - -/// download the url -/// and verify hashsum (if exist) -pub fn download( - uri: std.Uri, - file_name: []const u8, - shasum: ?[64]u8, - size: ?usize, -) !std.fs.File { - - // whether verify hashsum - const if_hash = shasum != null; - - // allocator - const allocator = tools.get_allocator(); - - // this file store the downloaded src - const zvm_path = try tools.get_zvm_path_segment(allocator, "store"); - defer allocator.free(zvm_path); - - var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); - defer store.close(); - - // if file exist - // and provide shasum - // then calculate hash and verify, return the file if eql - // otherwise delete this file - if (tools.does_path_exist2(store, file_name)) { - if (if_hash) { - var sha256 = sha2.Sha256.init(.{}); - const file = try store.openFile(file_name, .{}); - var buffer: [512]u8 = undefined; - while (true) { - const byte_nums = try file.read(&buffer); - if (byte_nums == 0) - break; - - sha256.update(buffer[0..byte_nums]); - } - var result = std.mem.zeroes([32]u8); - sha256.final(&result); - - if (tools.verify_hash(result, shasum.?)) { - try file.seekTo(0); - return file; - } - } - try std.fs.deleteFileAbsolute(file_name); - } - - // http client - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - var header_buffer: [1024]u8 = undefined; // 1024b - - var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); - defer req.deinit(); - - try req.send(); - try req.wait(); - - // ensure req successfully - if (req.response.status != .ok) - return error.DownFailed; - - // Compare file sizes - if (size) |ss| { - const total_size: usize = @intCast(req.response.content_length orelse 0); - if (ss != total_size) - return error.IncorrectSize; - } - - // create a new file - const new_file = try store.createFile(file_name, .{ - .read = true, - }); - - // whether enable hashsum - var sha256 = if (if_hash) sha2.Sha256.init(.{}) else undefined; - - // the tmp buffer to store the receive data - var buffer: [512]u8 = undefined; - // get reader - const reader = req.reader(); - while (true) { - // the read byte number - const byte_nums = try reader.read(&buffer); - if (byte_nums == 0) - break; - if (if_hash) - sha256.update(buffer[0..byte_nums]); - // write to file - try new_file.writeAll(buffer[0..byte_nums]); - } - - // when calculate hashsum - if (if_hash) { - var result = std.mem.zeroes([32]u8); - sha256.final(&result); - - if (!tools.verify_hash(result, shasum.?)) - return error.HashMismatch; - } - - try new_file.seekTo(0); - - return new_file; -} diff --git a/src/install.zig b/src/install.zig index cd97534..ae779dd 100644 --- a/src/install.zig +++ b/src/install.zig @@ -1,7 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); -const download = @import("download.zig"); const architecture = @import("architecture.zig"); const tools = @import("tools.zig"); const alias = @import("alias.zig"); @@ -61,7 +60,7 @@ pub fn install_zig(version: []const u8) !void { ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; - const new_file = try download.download(parsed_uri, file_name, version_data.shasum, version_data.size); + const new_file = try tools.download(parsed_uri, file_name, version_data.shasum, version_data.size); defer new_file.close(); try tools.try_create_path(extract_path); diff --git a/src/tools.zig b/src/tools.zig index b551e02..a6eac25 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -3,6 +3,7 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); +const sha2 = std.crypto.hash.sha2; const testing = std.testing; /// Initialize the data. @@ -201,3 +202,112 @@ pub fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { } } } + +/// download the url +/// and verify hashsum (if exist) +pub fn download( + uri: std.Uri, + file_name: []const u8, + shasum: ?[64]u8, + size: ?usize, +) !std.fs.File { + + // whether verify hashsum + const if_hash = shasum != null; + + // allocator + const allocator = get_allocator(); + + // this file store the downloaded src + const zvm_path = try get_zvm_path_segment(allocator, "store"); + defer allocator.free(zvm_path); + + var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); + defer store.close(); + + // if file exist + // and provide shasum + // then calculate hash and verify, return the file if eql + // otherwise delete this file + if (does_path_exist2(store, file_name)) { + if (if_hash) { + var sha256 = sha2.Sha256.init(.{}); + const file = try store.openFile(file_name, .{}); + var buffer: [512]u8 = undefined; + while (true) { + const byte_nums = try file.read(&buffer); + if (byte_nums == 0) + break; + + sha256.update(buffer[0..byte_nums]); + } + var result = std.mem.zeroes([32]u8); + sha256.final(&result); + + if (verify_hash(result, shasum.?)) { + try file.seekTo(0); + return file; + } + } + try std.fs.deleteFileAbsolute(file_name); + } + + // http client + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + var header_buffer: [1024]u8 = undefined; // 1024b + + var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); + defer req.deinit(); + + try req.send(); + try req.wait(); + + // ensure req successfully + if (req.response.status != .ok) + return error.DownFailed; + + // Compare file sizes + if (size) |ss| { + const total_size: usize = @intCast(req.response.content_length orelse 0); + if (ss != total_size) + return error.IncorrectSize; + } + + // create a new file + const new_file = try store.createFile(file_name, .{ + .read = true, + }); + + // whether enable hashsum + var sha256 = if (if_hash) sha2.Sha256.init(.{}) else undefined; + + // the tmp buffer to store the receive data + var buffer: [512]u8 = undefined; + // get reader + const reader = req.reader(); + while (true) { + // the read byte number + const byte_nums = try reader.read(&buffer); + if (byte_nums == 0) + break; + if (if_hash) + sha256.update(buffer[0..byte_nums]); + // write to file + try new_file.writeAll(buffer[0..byte_nums]); + } + + // when calculate hashsum + if (if_hash) { + var result = std.mem.zeroes([32]u8); + sha256.final(&result); + + if (!verify_hash(result, shasum.?)) + return error.HashMismatch; + } + + try new_file.seekTo(0); + + return new_file; +} From 25053c71260b324ad6fe9db56e9cb388ed838b3e Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Tue, 6 Aug 2024 22:20:12 +0800 Subject: [PATCH 15/26] feat: support download zls --- src/alias.zig | 19 +++++++++++--- src/command.zig | 24 ++++++++++-------- src/config.zig | 5 +++- src/extract.zig | 9 ++++--- src/install.zig | 66 ++++++++++++++++++++++++++++++++++++++++++++++--- src/main.zig | 3 +++ src/meta.zig | 53 +++++++++++++++++++++++++++++++++++++++ src/tools.zig | 41 ++++++++++++++++++++++++------ 8 files changed, 191 insertions(+), 29 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index c698f8a..6e58a1b 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -12,9 +12,22 @@ pub fn set_zig_version(version: []const u8) !void { defer arena.deinit(); const arena_allocator = arena.allocator(); - const user_home = tools.get_home(); - const version_path = try std.fs.path.join(arena_allocator, &[_][]const u8{ user_home, ".zm", "version", version }); - const symlink_path = try tools.get_zvm_path_segment(arena_allocator, "current"); + try tools.try_create_path(try tools.get_zvm_path_segment(arena_allocator, "current")); + + const version_path = try std.fs.path.join( + arena_allocator, + &.{ try tools.get_zvm_zig_version(arena_allocator), version }, + ); + + std.fs.accessAbsolute(version_path, .{}) catch |err| { + if (err != error.FileNotFound) + return err; + + std.debug.print("zig {s} is not installed, please install it!\n", .{version}); + std.process.exit(1); + }; + + const symlink_path = try tools.get_zvm_current_zig(arena_allocator); try update_current(version_path, symlink_path); try verify_zig_version(version); diff --git a/src/command.zig b/src/command.zig index 1412217..d542881 100644 --- a/src/command.zig +++ b/src/command.zig @@ -44,11 +44,6 @@ const command_opts = [_]CommandOption{ /// Parse and handle commands pub fn handle_command(params: []const []const u8) !void { - if (builtin.os.tag != .windows) { - if (std.mem.eql(u8, std.fs.path.basename(params[0]), "zig")) - try handle_alias(params); - } - const command: CommandData = blk: { if (params.len < 2) break :blk CommandData{}; @@ -100,7 +95,13 @@ pub fn handle_command(params: []const []const u8) !void { } } -fn handle_alias(params: []const []const u8) !void { +/// handle alias, now only support zig +pub fn handle_alias(params: []const []const u8) !void { + if (builtin.os.tag != .windows) { + if (!std.mem.eql(u8, std.fs.path.basename(params[0]), "zig")) + return; + } + var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); defer arena.deinit(); @@ -108,8 +109,8 @@ fn handle_alias(params: []const []const u8) !void { const new_params = try allocator.dupe([]const u8, params); - const home = tools.get_home(); - const current_zig_path = try std.fs.path.join(allocator, &.{ home, ".zm", "current", "zig" }); + const current_zig = try tools.get_zvm_current_zig(allocator) ; + const current_zig_path = try std.fs.path.join(allocator, &.{ current_zig, "zig" }); std.fs.accessAbsolute(current_zig_path, .{}) catch |err| { if (err == std.fs.Dir.AccessError.FileNotFound) { @@ -173,8 +174,11 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { std.debug.print("Please specify a version to install using 'install zig '.\n", .{}); } } else if (std.mem.eql(u8, scmd, "zls")) { - // Handle ZLS installation if supported - std.debug.print("[Unsupported] install zls\n", .{}); + if (param) |version| { + try install.install_zls(version); + } else { + std.debug.print("Please specify a version to install using 'install zls '.\n", .{}); + } } else { std.debug.print("Unknown subcommand '{s}'. Use 'install zig ' or 'install zls '.\n", .{scmd}); } diff --git a/src/config.zig b/src/config.zig index 03fcce0..2bc01ad 100644 --- a/src/config.zig +++ b/src/config.zig @@ -28,4 +28,7 @@ pub const zig_name = switch (builtin.os.tag) { }; /// zig archive_ext -pub const zig_archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; +pub const archive_ext = if (builtin.os.tag == .windows) + "zip" +else + "tar.xz"; diff --git a/src/extract.zig b/src/extract.zig index 1a4b1f9..0541d7d 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -6,19 +6,20 @@ const xz = std.compress.xz; const tar = std.tar; /// extract file to out_dir -pub fn extrace( +pub fn extract( out_dir: std.fs.Dir, file: std.fs.File, file_type: enum { tarxz, zip }, + is_zls: bool, ) !void { switch (file_type) { .zip => try extract_zip_dir(out_dir, file), - .tarxz => try extract_tarxz_to_dir(out_dir, file), + .tarxz => try extract_tarxz_to_dir(out_dir, file, is_zls), } } /// extract tar.xz to dir -fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { +fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File, is_zls: bool) !void { var buffered_reader = std.io.bufferedReader(file.reader()); var decompressed = try xz.decompress(tools.get_allocator(), buffered_reader.reader()); @@ -27,7 +28,7 @@ fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { try tar.pipeToFileSystem( out_dir, decompressed.reader(), - .{ .mode_mode = .executable_bit_only, .strip_components = 1 }, + .{ .mode_mode = .executable_bit_only, .strip_components = if (is_zls) 0 else 1 }, ); } diff --git a/src/install.zig b/src/install.zig index ae779dd..f2fbc2d 100644 --- a/src/install.zig +++ b/src/install.zig @@ -30,7 +30,7 @@ pub fn install_zig(version: []const u8) !void { const arena_allocator = arena.allocator(); // get version path - const version_path = try tools.get_zvm_path_segment(arena_allocator, "version"); + const version_path = try tools.get_zvm_zig_version(arena_allocator); // get extract path const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); @@ -56,7 +56,7 @@ pub fn install_zig(version: []const u8) !void { const file_name = try std.mem.concat( arena_allocator, u8, - &.{ "zig-", reverse_platform_str, "-", version, ".", config.zig_archive_ext }, + &.{ "zig-", reverse_platform_str, "-", version, ".", config.archive_ext }, ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; @@ -66,7 +66,67 @@ pub fn install_zig(version: []const u8) !void { try tools.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - try extract.extrace(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz); + try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); try alias.set_zig_version(version); } + +/// Try to install the specified version of zls +pub fn install_zls(version: []const u8) !void { + const allocator = tools.get_allocator(); + + const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ + .os = builtin.os.tag, + .arch = builtin.cpu.arch, + .reverse = true, + }) orelse unreachable; + + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + + const arena_allocator = arena.allocator(); + + // get version data + const version_data: meta.Zls.VersionData = blk: { + const res = try tools.http_get(arena_allocator, config.zls_url); + var zls_meta = try meta.Zls.init(res, arena_allocator); + const tmp_val = try zls_meta.get_version_data(version, reverse_platform_str, arena_allocator); + break :blk tmp_val orelse return error.UnsupportedVersion; + }; + + const file_name = try std.mem.concat( + arena_allocator, + u8, + &.{ "zls-", reverse_platform_str, "-", version, ".", config.archive_ext }, + ); + + const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; + const new_file = try tools.download(parsed_uri, file_name, null, version_data.size); + defer new_file.close(); + + // get version path + const version_path = try tools.get_zvm_zls_version(arena_allocator); + // get extract path + const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); + + try tools.try_create_path(extract_path); + + const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); + try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, true); + + std.debug.print( + \\zls version data: + \\version: {s} + \\id: {} + \\size: {} + \\tarball: {s} + \\ + , .{ + version_data.version, + version_data.id, + version_data.size, + version_data.tarball, + }); +} + +pub fn build_zls() !void {} diff --git a/src/main.zig b/src/main.zig index fbdd213..c2b508d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -19,6 +19,9 @@ pub fn main() !void { const args = try std.process.argsAlloc(allocator); defer std.process.argsFree(allocator, args); + // try handle alias + try command.handle_alias(args); + // parse the args and handle command try command.handle_command(args); } diff --git a/src/meta.zig b/src/meta.zig index 8792ac0..bab99a4 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -136,6 +136,19 @@ pub const Zig = struct { pub const Zls = struct { data: jsonValue, + /// version data for zig + pub const VersionData = struct { + version: []const u8, + id: usize, + tarball: []const u8, + size: usize, + + pub fn deinit(self: VersionData, allocator: Allocator) void { + allocator.free(self.version); + allocator.free(self.tarball); + } + }; + // init the zig data pub fn init(raw: []const u8, allocator: Allocator) !Zls { const data = @@ -149,6 +162,46 @@ pub const Zls = struct { self.data.deinit(); } + pub fn get_version_data( + self: *Zls, + version: []const u8, + platform_str: []const u8, + allocator: Allocator, + ) !?VersionData { + const file_name = try std.fmt.allocPrint( + allocator, + "zls-{s}.{s}", + .{ platform_str, config.archive_ext }, + ); + for (self.data.value.array.items) |item| { + const item_obj = item.object; + + const tag = item_obj.get("tag_name") orelse continue; + if (!tools.eql_str(version, tag.string)) continue; + + const assets = item_obj.get("assets") orelse continue; + for (assets.array.items) |asset| { + const asset_obj = asset.object; + + const name = asset_obj.get("name") orelse continue; + if (!tools.eql_str(file_name, name.string)) continue; + + const tarball = asset_obj.get("browser_download_url") orelse return null; + const id = asset_obj.get("id") orelse return null; + const size = asset_obj.get("size") orelse return null; + + return VersionData{ + .version = try allocator.dupe(u8, version), + .id = @intCast(id.integer), + .tarball = try allocator.dupe(u8, tarball.string), + .size = @intCast(size.integer), + }; + } + break; + } + return null; + } + /// return the version list pub fn get_version_list(self: *Zls, allocator: Allocator) ![][]const u8 { var list = std.ArrayList([]const u8).init(allocator); diff --git a/src/tools.zig b/src/tools.zig index a6eac25..1420c65 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -41,11 +41,36 @@ pub fn get_allocator() std.mem.Allocator { } /// Get zvm path segment -pub fn get_zvm_path_segment(tmp_allocator: std.mem.Allocator, segment: []const u8) ![]u8 { - return std.fs.path.join( - tmp_allocator, - &[_][]const u8{ get_home(), ".zm", segment }, - ); +pub fn get_zvm_path_segment(allocator: std.mem.Allocator, segment: []const u8) ![]u8 { + return try std.fs.path.join(allocator, &[_][]const u8{ get_home(), ".zm", segment }); +} + +pub fn get_zvm_current_zig(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "current"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); +} + +pub fn get_zvm_current_zls(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "current"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); +} + +pub fn get_zvm_store(allocator: std.mem.Allocator) ![]u8 { + return get_zvm_path_segment(allocator, "store"); +} + +pub fn get_zvm_zig_version(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "version"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); +} + +pub fn get_zvm_zls_version(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "version"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); } /// Free str array @@ -133,7 +158,7 @@ pub fn try_create_path(path: []const u8) !void { /// try to get zig version pub fn get_zig_version(allocator: std.mem.Allocator) ![]u8 { const home_dir = get_home(); - const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", config.zig_name }); + const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", "zig", config.zig_name }); defer allocator.free(current_zig_path); // here we must use the absolute path, we can not just use "zig" @@ -249,14 +274,14 @@ pub fn download( return file; } } - try std.fs.deleteFileAbsolute(file_name); + try store.deleteFile(file_name); } // http client var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); - var header_buffer: [1024]u8 = undefined; // 1024b + var header_buffer: [10240]u8 = undefined; // 1024b var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); defer req.deinit(); From 0f2c85f276471bf83bf36c7942f101f709231777 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Tue, 6 Aug 2024 23:35:09 +0800 Subject: [PATCH 16/26] feat: support install zls --- src/alias.zig | 37 ++++++++++++++++++++++++++++++++----- src/command.zig | 4 ++-- src/config.zig | 7 +++++++ src/install.zig | 20 +++++++++++++------- src/tools.zig | 21 ++++++++++++++++----- 5 files changed, 70 insertions(+), 19 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index 6e58a1b..1fc236a 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -7,7 +7,7 @@ const config = @import("config.zig"); /// try to set zig version /// this will use system link on unix-like /// for windows, this will use copy dir -pub fn set_zig_version(version: []const u8) !void { +pub fn set_version(version: []const u8, is_zls: bool) !void { var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); defer arena.deinit(); const arena_allocator = arena.allocator(); @@ -16,7 +16,13 @@ pub fn set_zig_version(version: []const u8) !void { const version_path = try std.fs.path.join( arena_allocator, - &.{ try tools.get_zvm_zig_version(arena_allocator), version }, + &.{ + if (is_zls) + try tools.get_zvm_zls_version(arena_allocator) + else + try tools.get_zvm_zig_version(arena_allocator), + version, + }, ); std.fs.accessAbsolute(version_path, .{}) catch |err| { @@ -27,10 +33,17 @@ pub fn set_zig_version(version: []const u8) !void { std.process.exit(1); }; - const symlink_path = try tools.get_zvm_current_zig(arena_allocator); + const symlink_path = if (is_zls) + try tools.get_zvm_current_zls(arena_allocator) + else + try tools.get_zvm_current_zig(arena_allocator); try update_current(version_path, symlink_path); - try verify_zig_version(version); + if (is_zls) { + try verify_zls_version(version); + } else { + try verify_zig_version(version); + } } fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { @@ -56,7 +69,7 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { fn verify_zig_version(expected_version: []const u8) !void { const allocator = tools.get_allocator(); - const actual_version = try tools.get_zig_version(allocator); + const actual_version = try tools.get_version(allocator, false); defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { @@ -65,3 +78,17 @@ fn verify_zig_version(expected_version: []const u8) !void { std.debug.print("Now using Zig version {s}\n", .{expected_version}); } } + +/// verify current zig version +fn verify_zls_version(expected_version: []const u8) !void { + const allocator = tools.get_allocator(); + + const actual_version = try tools.get_version(allocator, true); + defer allocator.free(actual_version); + + if (!std.mem.eql(u8, expected_version, actual_version)) { + std.debug.print("Expected Zls version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); + } else { + std.debug.print("Now using Zls version {s}\n", .{expected_version}); + } +} diff --git a/src/command.zig b/src/command.zig index d542881..387ba3f 100644 --- a/src/command.zig +++ b/src/command.zig @@ -109,7 +109,7 @@ pub fn handle_alias(params: []const []const u8) !void { const new_params = try allocator.dupe([]const u8, params); - const current_zig = try tools.get_zvm_current_zig(allocator) ; + const current_zig = try tools.get_zvm_current_zig(allocator); const current_zig_path = try std.fs.path.join(allocator, &.{ current_zig, "zig" }); std.fs.accessAbsolute(current_zig_path, .{}) catch |err| { @@ -191,7 +191,7 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { fn use_version(params: ?[]const u8) !void { if (params) |version| { - try alias.set_zig_version(version); + try alias.set_version(version, false); } else { std.debug.print("Error: Please specify a version to use with 'use '.\n", .{}); } diff --git a/src/config.zig b/src/config.zig index 2bc01ad..ed6f7b3 100644 --- a/src/config.zig +++ b/src/config.zig @@ -27,6 +27,13 @@ pub const zig_name = switch (builtin.os.tag) { else => @compileError("not support current platform"), }; +/// zig file name +pub const zls_name = switch (builtin.os.tag) { + .windows => "zls.exe", + .linux, .macos => "zls", + else => @compileError("not support current platform"), +}; + /// zig archive_ext pub const archive_ext = if (builtin.os.tag == .windows) "zip" diff --git a/src/install.zig b/src/install.zig index f2fbc2d..49c6603 100644 --- a/src/install.zig +++ b/src/install.zig @@ -35,7 +35,7 @@ pub fn install_zig(version: []const u8) !void { const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); if (tools.does_path_exist(extract_path)) { - try alias.set_zig_version(version); + try alias.set_version(version, false); return; } @@ -68,7 +68,7 @@ pub fn install_zig(version: []const u8) !void { try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); - try alias.set_zig_version(version); + try alias.set_version(version, false); } /// Try to install the specified version of zls @@ -86,6 +86,16 @@ pub fn install_zls(version: []const u8) !void { const arena_allocator = arena.allocator(); + // get version path + const version_path = try tools.get_zvm_zls_version(arena_allocator); + // get extract path + const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); + + if (tools.does_path_exist(extract_path)) { + try alias.set_version(version, true); + return; + } + // get version data const version_data: meta.Zls.VersionData = blk: { const res = try tools.http_get(arena_allocator, config.zls_url); @@ -104,16 +114,12 @@ pub fn install_zls(version: []const u8) !void { const new_file = try tools.download(parsed_uri, file_name, null, version_data.size); defer new_file.close(); - // get version path - const version_path = try tools.get_zvm_zls_version(arena_allocator); - // get extract path - const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); - try tools.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, true); + try alias.set_version(version, true); std.debug.print( \\zls version data: \\version: {s} diff --git a/src/tools.zig b/src/tools.zig index 1420c65..832c7ac 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -156,14 +156,25 @@ pub fn try_create_path(path: []const u8) !void { } /// try to get zig version -pub fn get_zig_version(allocator: std.mem.Allocator) ![]u8 { - const home_dir = get_home(); - const current_zig_path = try std.fs.path.join(allocator, &.{ home_dir, ".zm", "current", "zig", config.zig_name }); - defer allocator.free(current_zig_path); +pub fn get_version(allocator: std.mem.Allocator, is_zls: bool) ![]u8 { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const arena_allocator = arena.allocator(); + + const current_path = try std.fs.path.join(arena_allocator, &.{ + if (is_zls) + try get_zvm_current_zls(arena_allocator) + else + try get_zvm_current_zig(arena_allocator), + if (is_zls) + config.zls_name + else + config.zig_name, + }); // here we must use the absolute path, we can not just use "zig" // because child process will use environment variable - var child_process = std.process.Child.init(&[_][]const u8{ current_zig_path, "version" }, allocator); + var child_process = std.process.Child.init(&[_][]const u8{ current_path, if (is_zls) "--version" else "version" }, arena_allocator); child_process.stdin_behavior = .Close; child_process.stdout_behavior = .Pipe; From 148de26742ffa7959e74f2952494df639ca9d959 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 7 Aug 2024 20:55:41 +0800 Subject: [PATCH 17/26] feat: support alias zig and zls --- src/command.zig | 94 +++++++++++++++++++++++++++++++++---------------- src/install.zig | 43 ++++++++++++++++++---- 2 files changed, 100 insertions(+), 37 deletions(-) diff --git a/src/command.zig b/src/command.zig index 387ba3f..5fa23f5 100644 --- a/src/command.zig +++ b/src/command.zig @@ -87,7 +87,7 @@ pub fn handle_command(params: []const []const u8) !void { switch (command.cmd) { .List => try handle_list(command.param), .Install => try install_version(command.subcmd, command.param), - .Use => try use_version(command.param), + .Use => try use_version(command.subcmd, command.param), .Default => try set_default(), .Version => try get_version(), .Help => try display_help(), @@ -97,10 +97,16 @@ pub fn handle_command(params: []const []const u8) !void { /// handle alias, now only support zig pub fn handle_alias(params: []const []const u8) !void { - if (builtin.os.tag != .windows) { - if (!std.mem.eql(u8, std.fs.path.basename(params[0]), "zig")) - return; - } + if (builtin.os.tag == .windows) return; + + var is_zls: bool = undefined; + + const basename = std.fs.path.basename(params[0]); + if (tools.eql_str(basename, "zig")) { + is_zls = false; + } else if (tools.eql_str(basename, "zls")) { + is_zls = true; + } else return; var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); defer arena.deinit(); @@ -109,18 +115,18 @@ pub fn handle_alias(params: []const []const u8) !void { const new_params = try allocator.dupe([]const u8, params); - const current_zig = try tools.get_zvm_current_zig(allocator); - const current_zig_path = try std.fs.path.join(allocator, &.{ current_zig, "zig" }); + const current = try if (is_zls) tools.get_zvm_current_zls(allocator) else tools.get_zvm_current_zig(allocator); + const current_path = try std.fs.path.join(allocator, &.{ current, if (is_zls) "zls" else "zig" }); - std.fs.accessAbsolute(current_zig_path, .{}) catch |err| { + std.fs.accessAbsolute(current_path, .{}) catch |err| { if (err == std.fs.Dir.AccessError.FileNotFound) { - std.debug.print("Zig has not been installed yet, please install zig with zvm!\n", .{}); + std.debug.print("{s} has not been installed yet, please install it fist!\n", .{if (is_zls) "Zls" else "Zig"}); std.process.exit(1); } return err; }; - new_params[0] = current_zig_path; + new_params[0] = current_path; return std.process.execv(allocator, new_params); } @@ -167,33 +173,59 @@ fn handle_list(param: ?[]const u8) !void { fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { if (subcmd) |scmd| { - if (std.mem.eql(u8, scmd, "zig")) { - if (param) |version| { - try install.install_zig(version); - } else { - std.debug.print("Please specify a version to install using 'install zig '.\n", .{}); - } - } else if (std.mem.eql(u8, scmd, "zls")) { - if (param) |version| { - try install.install_zls(version); - } else { - std.debug.print("Please specify a version to install using 'install zls '.\n", .{}); - } + var is_zls: bool = undefined; + + if (tools.eql_str(scmd, "zig")) { + is_zls = false; + } else if (tools.eql_str(scmd, "zls")) { + is_zls = true; } else { - std.debug.print("Unknown subcommand '{s}'. Use 'install zig ' or 'install zls '.\n", .{scmd}); + std.debug.print("Unknown subcommand '{s}'. Use 'install zig/zls '.\n", .{scmd}); + return; } + + const version = param orelse { + std.debug.print("Please specify a version to install: 'install zig/zls '.\n", .{}); + return; + }; + + try install.install(version, is_zls); } else if (param) |version| { - try install.install_zig(version); + // set zig version + try install.install(version, false); + // set zls version + try install.install(version, true); } else { - std.debug.print("Error: Please specify a version to install using 'install '.\n", .{}); + std.debug.print("Please specify a version to install: 'install zig/zls ' or 'install '.\n", .{}); } } -fn use_version(params: ?[]const u8) !void { - if (params) |version| { +fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { + if (subcmd) |scmd| { + var is_zls: bool = undefined; + + if (tools.eql_str(scmd, "zig")) { + is_zls = false; + } else if (tools.eql_str(scmd, "zls")) { + is_zls = true; + } else { + std.debug.print("Unknown subcommand '{s}'. Use 'use zig ' or 'use zls '.\n", .{scmd}); + return; + } + + const version = param orelse { + std.debug.print("Please specify a version to use: 'use zig/zls '.\n", .{}); + return; + }; + + try alias.set_version(version, is_zls); + } else if (param) |version| { + // set zig version try alias.set_version(version, false); + // set zls version + try alias.set_version(version, true); } else { - std.debug.print("Error: Please specify a version to use with 'use '.\n", .{}); + std.debug.print("Please specify a version to use: 'use zig/zls ' or 'use '.\n", .{}); } } @@ -220,8 +252,10 @@ fn display_help() !void { \\ --help Display this help message. \\ \\Example: - \\ zvm install 0.8.0 Install Zig version 0.8.0. - \\ zvm use 0.8.0 Switch to using Zig version 0.8.0. + \\ zvm install 0.12.0 Install Zig and zls version 0.12.0. + \\ zvm install zig 0.12.0 Install Zig version 0.12.0. + \\ zvm use 0.12.0 Switch to using Zig version 0.12.0. + \\ zvm use zig 0.12.0 Switch to using Zig version 0.12.0. \\ \\For additional information and contributions, please visit the GitHub repository. \\ diff --git a/src/install.zig b/src/install.zig index 49c6603..1d02280 100644 --- a/src/install.zig +++ b/src/install.zig @@ -14,8 +14,17 @@ const Version = struct { shasum: ?[]const u8, }; +/// try install specified version +pub fn install(version: []const u8, is_zls: bool) !void { + if (is_zls) { + try install_zls(version); + } else { + try install_zig(version); + } +} + /// Try to install the specified version of zig -pub fn install_zig(version: []const u8) !void { +fn install_zig(version: []const u8) !void { const allocator = tools.get_allocator(); const platform_str = try architecture.platform_str(architecture.DetectParams{ @@ -71,8 +80,28 @@ pub fn install_zig(version: []const u8) !void { try alias.set_version(version, false); } +const zls_list_1 = [_][]const u8{ + "0.12.1", +}; + +const zls_list_2 = [_][]const u8{ + "0.12.0", +}; + +comptime { + if (zls_list_1.len != zls_list_2.len) + @compileError("zls_list_1 length not equal to zls_list_2!"); +} + /// Try to install the specified version of zls -pub fn install_zls(version: []const u8) !void { +fn install_zls(version: []const u8) !void { + const true_version = blk: { + for (zls_list_1, 0..) |val, i| { + if (tools.eql_str(val, version)) + break :blk zls_list_2[i]; + } + break :blk version; + }; const allocator = tools.get_allocator(); const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ @@ -89,10 +118,10 @@ pub fn install_zls(version: []const u8) !void { // get version path const version_path = try tools.get_zvm_zls_version(arena_allocator); // get extract path - const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); + const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, true_version }); if (tools.does_path_exist(extract_path)) { - try alias.set_version(version, true); + try alias.set_version(true_version, true); return; } @@ -100,14 +129,14 @@ pub fn install_zls(version: []const u8) !void { const version_data: meta.Zls.VersionData = blk: { const res = try tools.http_get(arena_allocator, config.zls_url); var zls_meta = try meta.Zls.init(res, arena_allocator); - const tmp_val = try zls_meta.get_version_data(version, reverse_platform_str, arena_allocator); + const tmp_val = try zls_meta.get_version_data(true_version, reverse_platform_str, arena_allocator); break :blk tmp_val orelse return error.UnsupportedVersion; }; const file_name = try std.mem.concat( arena_allocator, u8, - &.{ "zls-", reverse_platform_str, "-", version, ".", config.archive_ext }, + &.{ "zls-", reverse_platform_str, "-", true_version, ".", config.archive_ext }, ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; @@ -119,7 +148,7 @@ pub fn install_zls(version: []const u8) !void { const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, true); - try alias.set_version(version, true); + try alias.set_version(true_version, true); std.debug.print( \\zls version data: \\version: {s} From ffc245d3d0da5cdd23043ed8ff1481930be886bf Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 7 Aug 2024 21:32:15 +0800 Subject: [PATCH 18/26] add command `remove ` and `remove zig/zls ` --- src/alias.zig | 2 +- src/command.zig | 53 ++++++++++++++++++++++++++++++++++++++++--------- src/config.zig | 13 ++++++++++++ src/install.zig | 22 +++++++------------- src/remove.zig | 49 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 25 deletions(-) create mode 100644 src/remove.zig diff --git a/src/alias.zig b/src/alias.zig index 1fc236a..fc437c7 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -59,7 +59,7 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { // when platform is not windows, this is execute here // when file exist(it is a systemlink), delete it - if (tools.does_path_exist(symlink_path)) try std.fs.cwd().deleteFile(symlink_path); + if (tools.does_path_exist(symlink_path)) try std.fs.deleteFileAbsolute(symlink_path); // system link it try std.posix.symlink(zig_path, symlink_path); diff --git a/src/command.zig b/src/command.zig index 5fa23f5..d456c42 100644 --- a/src/command.zig +++ b/src/command.zig @@ -4,6 +4,7 @@ const options = @import("options"); const install = @import("install.zig"); const alias = @import("alias.zig"); +const remove = @import("remove.zig"); const tools = @import("tools.zig"); const meta = @import("meta.zig"); const config = @import("config.zig"); @@ -13,7 +14,8 @@ pub const Command = enum { List, Install, Use, - Default, + Remove, + // Default, Version, Help, Unknown, @@ -36,10 +38,11 @@ const CommandOption = struct { const command_opts = [_]CommandOption{ .{ .short_handle = "ls", .handle = "list", .cmd = Command.List }, .{ .short_handle = "i", .handle = "install", .cmd = Command.Install }, - .{ .short_handle = null, .handle = "use", .cmd = Command.Use }, + .{ .short_handle = "u", .handle = "use", .cmd = Command.Use }, + .{ .short_handle = "rm", .handle = "remove", .cmd = Command.Remove }, .{ .short_handle = null, .handle = "--version", .cmd = Command.Version }, .{ .short_handle = null, .handle = "--help", .cmd = Command.Help }, - .{ .short_handle = null, .handle = "--default", .cmd = Command.Default }, + // .{ .short_handle = null, .handle = "--default", .cmd = Command.Default }, }; /// Parse and handle commands @@ -88,7 +91,8 @@ pub fn handle_command(params: []const []const u8) !void { .List => try handle_list(command.param), .Install => try install_version(command.subcmd, command.param), .Use => try use_version(command.subcmd, command.param), - .Default => try set_default(), + .Remove => try remove_version(command.subcmd, command.param), + // .Default => try set_default(), .Version => try get_version(), .Help => try display_help(), .Unknown => try handle_unknown(), @@ -229,6 +233,35 @@ fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { } } +fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { + if (subcmd) |scmd| { + var is_zls: bool = undefined; + + if (tools.eql_str(scmd, "zig")) { + is_zls = false; + } else if (tools.eql_str(scmd, "zls")) { + is_zls = true; + } else { + std.debug.print("Unknown subcommand '{s}'. Use 'remove zig ' or 'remove zls '.\n", .{scmd}); + return; + } + + const version = param orelse { + std.debug.print("Please specify a version to use: 'remove zig/zls '.\n", .{}); + return; + }; + + try remove.remove(version, is_zls); + } else if (param) |version| { + // set zig version + try remove.remove(version, false); + // set zls version + try remove.remove(version, true); + } else { + std.debug.print("Please specify a version to use: 'remove zig/zls ' or 'remove '.\n", .{}); + } +} + fn set_default() !void { std.debug.print("Handling 'default' command.\n", .{}); // Your default code here @@ -244,11 +277,12 @@ fn display_help() !void { \\ zvm [args] \\ \\Commands: - \\ ls, list List the versions of Zig available to zvm. - \\ i, install Install the specified version of Zig. - \\ use Use the specified version of Zig. - \\ --version Display the currently active Zig version. - \\ --default Set a specified Zig version as the default for new shells. + \\ ls, list List the versions of Zig or Zls available to zvm. + \\ i, install Install the specified version of Zig or Zls. + \\ use Use the specified version of Zig Zls. + \\ remove Remove the specified version of Zig or Zls + \\ --version Display zvm version. + // \\ --default Set a specified Zig or Zls version as the default for new shells. \\ --help Display this help message. \\ \\Example: @@ -256,6 +290,7 @@ fn display_help() !void { \\ zvm install zig 0.12.0 Install Zig version 0.12.0. \\ zvm use 0.12.0 Switch to using Zig version 0.12.0. \\ zvm use zig 0.12.0 Switch to using Zig version 0.12.0. + \\ zvm remove zig 0.12.0 Remove Zig version 0.12.0. \\ \\For additional information and contributions, please visit the GitHub repository. \\ diff --git a/src/config.zig b/src/config.zig index ed6f7b3..9a9279b 100644 --- a/src/config.zig +++ b/src/config.zig @@ -39,3 +39,16 @@ pub const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; + +pub const zls_list_1 = [_][]const u8{ + "0.12.1", +}; + +pub const zls_list_2 = [_][]const u8{ + "0.12.0", +}; + +comptime { + if (zls_list_1.len != zls_list_2.len) + @compileError("zls_list_1 length not equal to zls_list_2!"); +} diff --git a/src/install.zig b/src/install.zig index 1d02280..19c6301 100644 --- a/src/install.zig +++ b/src/install.zig @@ -80,25 +80,17 @@ fn install_zig(version: []const u8) !void { try alias.set_version(version, false); } -const zls_list_1 = [_][]const u8{ - "0.12.1", -}; - -const zls_list_2 = [_][]const u8{ - "0.12.0", -}; - -comptime { - if (zls_list_1.len != zls_list_2.len) - @compileError("zls_list_1 length not equal to zls_list_2!"); -} - /// Try to install the specified version of zls fn install_zls(version: []const u8) !void { const true_version = blk: { - for (zls_list_1, 0..) |val, i| { + if (tools.eql_str("master", version)) { + std.debug.print("sorry, now not support install zls, that need compile locally!", .{}); + return; + } + + for (config.zls_list_1, 0..) |val, i| { if (tools.eql_str(val, version)) - break :blk zls_list_2[i]; + break :blk config.zls_list_2[i]; } break :blk version; }; diff --git a/src/remove.zig b/src/remove.zig new file mode 100644 index 0000000..d57009b --- /dev/null +++ b/src/remove.zig @@ -0,0 +1,49 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const config = @import("config.zig"); +const tools = @import("tools.zig"); + +/// try remove specified version +pub fn remove(version: []const u8, is_zls: bool) !void { + const true_version = blk: { + if (!is_zls) + break :blk version; + for (config.zls_list_1, 0..) |val, i| { + if (tools.eql_str(val, version)) + break :blk config.zls_list_2[i]; + } + break :blk version; + }; + + var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); + defer arena.deinit(); + + const allocator = arena.allocator(); + + const current_path = try if (is_zls) tools.get_zvm_current_zls(allocator) else tools.get_zvm_current_zig(allocator); + + // try remove current path + if (tools.does_path_exist(current_path)) { + const current_version = try tools.get_version(allocator, is_zls); + if (tools.eql_str(current_version, true_version)) { + if (builtin.os.tag == .windows) { + try std.fs.deleteTreeAbsolute(current_path); + } else { + try std.fs.deleteFileAbsolute(current_path); + } + } + } + + const version_path = try std.fs.path.join(allocator, &.{ + try if (is_zls) + tools.get_zvm_zls_version(allocator) + else + tools.get_zvm_zig_version(allocator), + true_version, + }); + + // try remove version path + if (tools.does_path_exist(version_path)) { + try std.fs.deleteTreeAbsolute(version_path); + } +} From cba75d5b2234e47876896724e4ea720be845d99e Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 7 Aug 2024 21:37:46 +0800 Subject: [PATCH 19/26] rename `tools.get_version` to `tools.get_current_version` --- src/alias.zig | 4 ++-- src/remove.zig | 2 +- src/tools.zig | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index fc437c7..eb1d543 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -69,7 +69,7 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { fn verify_zig_version(expected_version: []const u8) !void { const allocator = tools.get_allocator(); - const actual_version = try tools.get_version(allocator, false); + const actual_version = try tools.get_current_version(allocator, false); defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { @@ -83,7 +83,7 @@ fn verify_zig_version(expected_version: []const u8) !void { fn verify_zls_version(expected_version: []const u8) !void { const allocator = tools.get_allocator(); - const actual_version = try tools.get_version(allocator, true); + const actual_version = try tools.get_current_version(allocator, true); defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { diff --git a/src/remove.zig b/src/remove.zig index d57009b..08edccd 100644 --- a/src/remove.zig +++ b/src/remove.zig @@ -24,7 +24,7 @@ pub fn remove(version: []const u8, is_zls: bool) !void { // try remove current path if (tools.does_path_exist(current_path)) { - const current_version = try tools.get_version(allocator, is_zls); + const current_version = try tools.get_current_version(allocator, is_zls); if (tools.eql_str(current_version, true_version)) { if (builtin.os.tag == .windows) { try std.fs.deleteTreeAbsolute(current_path); diff --git a/src/tools.zig b/src/tools.zig index 832c7ac..1c5c097 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -155,8 +155,8 @@ pub fn try_create_path(path: []const u8) !void { if (err != error.PathAlreadyExists) return err; } -/// try to get zig version -pub fn get_version(allocator: std.mem.Allocator, is_zls: bool) ![]u8 { +/// try to get zig/zls version +pub fn get_current_version(allocator: std.mem.Allocator, is_zls: bool) ![]u8 { var arena = std.heap.ArenaAllocator.init(allocator); defer arena.deinit(); const arena_allocator = arena.allocator(); From c82c31c01f3c1480e1fe02f0755792c1d7125afd Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Wed, 7 Aug 2024 22:25:52 +0800 Subject: [PATCH 20/26] add file module comment --- src/alias.zig | 3 +++ src/architecture.zig | 1 + src/command.zig | 1 + src/extract.zig | 1 + src/install.zig | 1 + src/meta.zig | 4 ++++ src/remove.zig | 1 + src/tools.zig | 1 + 8 files changed, 13 insertions(+) diff --git a/src/alias.zig b/src/alias.zig index eb1d543..ef8c636 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -1,3 +1,6 @@ +//! This file is used to create soft links and verify version +//! for Windows, we will use copy dir(when Windows create soft link it requires admin) +//! for set version const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); diff --git a/src/architecture.zig b/src/architecture.zig index 542e0d7..42c8b6b 100644 --- a/src/architecture.zig +++ b/src/architecture.zig @@ -1,3 +1,4 @@ +//! This file is used to splice os and architecture into the correct file name const std = @import("std"); pub const DetectParams = struct { diff --git a/src/command.zig b/src/command.zig index d456c42..01e87ab 100644 --- a/src/command.zig +++ b/src/command.zig @@ -1,3 +1,4 @@ +//! This file stores command line parsing and processing const std = @import("std"); const builtin = @import("builtin"); const options = @import("options"); diff --git a/src/extract.zig b/src/extract.zig index 0541d7d..66d241c 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -1,3 +1,4 @@ +//! This file is used to decompress the file const std = @import("std"); const builtin = @import("builtin"); const tools = @import("tools.zig"); diff --git a/src/install.zig b/src/install.zig index 19c6301..87a1c72 100644 --- a/src/install.zig +++ b/src/install.zig @@ -1,3 +1,4 @@ +//! This file is used to install zig or zls const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); diff --git a/src/meta.zig b/src/meta.zig index bab99a4..73224a3 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -1,3 +1,7 @@ +//! This file is used to parse the json information of zig and zls +//! The json of zig comes from the official website +//! The json of zls comes from the official github api +//! We can get version list and version data const std = @import("std"); const config = @import("config.zig"); const tools = @import("tools.zig"); diff --git a/src/remove.zig b/src/remove.zig index 08edccd..10389a3 100644 --- a/src/remove.zig +++ b/src/remove.zig @@ -1,3 +1,4 @@ +//! For removing the zig or zls const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); diff --git a/src/tools.zig b/src/tools.zig index 1c5c097..aea5f58 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -1,4 +1,5 @@ //! this file just contains util function +//! these functions are generic const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); From c4da33848a2722e1d5c165e00744760562ac0481 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 8 Aug 2024 12:38:26 +0800 Subject: [PATCH 21/26] Text modification --- src/alias.zig | 6 +++--- src/command.zig | 11 +++-------- src/config.zig | 12 +++++++++--- src/install.zig | 2 +- src/tools.zig | 8 ++------ 5 files changed, 18 insertions(+), 21 deletions(-) diff --git a/src/alias.zig b/src/alias.zig index ef8c636..0af8aff 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -32,7 +32,7 @@ pub fn set_version(version: []const u8, is_zls: bool) !void { if (err != error.FileNotFound) return err; - std.debug.print("zig {s} is not installed, please install it!\n", .{version}); + std.debug.print("zig version {s} is not installed. Please install it before proceeding.\n", .{version}); std.process.exit(1); }; @@ -90,8 +90,8 @@ fn verify_zls_version(expected_version: []const u8) !void { defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { - std.debug.print("Expected Zls version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); + std.debug.print("Expected zls version {s}, but currently using {s}. Please check.\n", .{ expected_version, actual_version }); } else { - std.debug.print("Now using Zls version {s}\n", .{expected_version}); + std.debug.print("Now using zls version {s}\n", .{expected_version}); } } diff --git a/src/command.zig b/src/command.zig index 01e87ab..9bc6469 100644 --- a/src/command.zig +++ b/src/command.zig @@ -16,7 +16,6 @@ pub const Command = enum { Install, Use, Remove, - // Default, Version, Help, Unknown, @@ -53,7 +52,6 @@ pub fn handle_command(params: []const []const u8) !void { const args = params[1..]; - // for (args, 0..) |arg, index| { const arg = args[0]; for (command_opts) |opt| { const is_eql_short_handle = if (opt.short_handle) |short_handle| @@ -84,7 +82,6 @@ pub fn handle_command(params: []const []const u8) !void { .param = param, }; } - // } break :blk CommandData{}; }; @@ -93,7 +90,6 @@ pub fn handle_command(params: []const []const u8) !void { .Install => try install_version(command.subcmd, command.param), .Use => try use_version(command.subcmd, command.param), .Remove => try remove_version(command.subcmd, command.param), - // .Default => try set_default(), .Version => try get_version(), .Help => try display_help(), .Unknown => try handle_unknown(), @@ -248,13 +244,13 @@ fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { } const version = param orelse { - std.debug.print("Please specify a version to use: 'remove zig/zls '.\n", .{}); + std.debug.print("Please specify a version: 'remove zig ' or 'remove zls '.\n", .{}); return; }; try remove.remove(version, is_zls); } else if (param) |version| { - // set zig version + // remove zig version try remove.remove(version, false); // set zls version try remove.remove(version, true); @@ -278,12 +274,11 @@ fn display_help() !void { \\ zvm [args] \\ \\Commands: - \\ ls, list List the versions of Zig or Zls available to zvm. + \\ ls, list List the versions of Zig or zls available to zvm. \\ i, install Install the specified version of Zig or Zls. \\ use Use the specified version of Zig Zls. \\ remove Remove the specified version of Zig or Zls \\ --version Display zvm version. - // \\ --default Set a specified Zig or Zls version as the default for new shells. \\ --help Display this help message. \\ \\Example: diff --git a/src/config.zig b/src/config.zig index 9a9279b..77580eb 100644 --- a/src/config.zig +++ b/src/config.zig @@ -24,14 +24,14 @@ pub const zls_url = std.Uri.parse(zls_meta_url) catch unreachable; pub const zig_name = switch (builtin.os.tag) { .windows => "zig.exe", .linux, .macos => "zig", - else => @compileError("not support current platform"), + else => @compileError("Current platform not supported"), }; /// zig file name pub const zls_name = switch (builtin.os.tag) { .windows => "zls.exe", .linux, .macos => "zls", - else => @compileError("not support current platform"), + else => @compileError("Current platform not supported"), }; /// zig archive_ext @@ -40,14 +40,20 @@ pub const archive_ext = if (builtin.os.tag == .windows) else "tar.xz"; +/// zls_list_1 and zls_list_2 are path for zls version +/// because zls not have 0.12.1, +/// so when user install zls 0.12.1 +/// zvm will automatically install 0.12.0 +/// zls_list_1 is the list for mapping source pub const zls_list_1 = [_][]const u8{ "0.12.1", }; - +/// zls_list_2 is the list for mapping result pub const zls_list_2 = [_][]const u8{ "0.12.0", }; +// ensure correct comptime { if (zls_list_1.len != zls_list_2.len) @compileError("zls_list_1 length not equal to zls_list_2!"); diff --git a/src/install.zig b/src/install.zig index 87a1c72..ee0e8d1 100644 --- a/src/install.zig +++ b/src/install.zig @@ -85,7 +85,7 @@ fn install_zig(version: []const u8) !void { fn install_zls(version: []const u8) !void { const true_version = blk: { if (tools.eql_str("master", version)) { - std.debug.print("sorry, now not support install zls, that need compile locally!", .{}); + std.debug.print("Sorry, the 'install zls' feature is not supported at this time. Please compile zls locally.", .{}); return; } diff --git a/src/tools.zig b/src/tools.zig index aea5f58..1be0cca 100644 --- a/src/tools.zig +++ b/src/tools.zig @@ -14,16 +14,12 @@ pub fn data_init(tmp_allocator: std.mem.Allocator) !void { try std.process.getEnvVarOwned(config.allocator, "USERPROFILE") else std.posix.getenv("HOME") orelse "."; - - // config.progress_root = std.Progress.start(.{ .root_name = "zvm" }); } /// Deinitialize the data. pub fn data_deinit() void { if (builtin.os.tag == .windows) config.allocator.free(config.home_dir); - - // config.progress_root.end(); } /// new progress node @@ -138,7 +134,7 @@ pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { try req.wait(); if (req.response.status != .ok) { - return error.ListResponseNotOk; + return error.HttpRequestFailed; } const res = try req.reader().readAllAlloc(allocator, 256 * 1024); @@ -199,7 +195,7 @@ pub fn does_path_exist(path: []const u8) bool { return true; } -// check dir exist +// Check if directory path exists pub fn does_path_exist2(dir: std.fs.Dir, path: []const u8) bool { dir.access(path, .{}) catch |err| { if (err == error.FileNotFound) return false; From ada0c042742671c98f6e3ed4267ec561ad9f4fca Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 8 Aug 2024 13:19:51 +0800 Subject: [PATCH 22/26] Split `tools.zig` into `util/*.zig`, and put arch and extract related functions into `util` --- src/alias.zig | 29 +- src/command.zig | 46 ++-- src/install.zig | 49 ++-- src/main.zig | 8 +- src/meta.zig | 22 +- src/remove.zig | 22 +- src/tools.zig | 346 ------------------------ src/{architecture.zig => util/arch.zig} | 0 src/util/data.zig | 101 +++++++ src/{ => util}/extract.zig | 8 +- src/util/hash.zig | 38 +++ src/util/http.zig | 139 ++++++++++ src/util/tool.zig | 70 +++++ 13 files changed, 448 insertions(+), 430 deletions(-) delete mode 100644 src/tools.zig rename src/{architecture.zig => util/arch.zig} (100%) create mode 100644 src/util/data.zig rename src/{ => util}/extract.zig (87%) create mode 100644 src/util/hash.zig create mode 100644 src/util/http.zig create mode 100644 src/util/tool.zig diff --git a/src/alias.zig b/src/alias.zig index 0af8aff..a57e33b 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -4,26 +4,27 @@ const std = @import("std"); const assert = std.debug.assert; const builtin = @import("builtin"); -const tools = @import("tools.zig"); const config = @import("config.zig"); +const util_data = @import("util/data.zig"); +const util_tool = @import("util/tool.zig"); /// try to set zig version /// this will use system link on unix-like /// for windows, this will use copy dir pub fn set_version(version: []const u8, is_zls: bool) !void { - var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); + var arena = std.heap.ArenaAllocator.init(util_data.get_allocator()); defer arena.deinit(); const arena_allocator = arena.allocator(); - try tools.try_create_path(try tools.get_zvm_path_segment(arena_allocator, "current")); + try util_tool.try_create_path(try util_data.get_zvm_path_segment(arena_allocator, "current")); const version_path = try std.fs.path.join( arena_allocator, &.{ if (is_zls) - try tools.get_zvm_zls_version(arena_allocator) + try util_data.get_zvm_zls_version(arena_allocator) else - try tools.get_zvm_zig_version(arena_allocator), + try util_data.get_zvm_zig_version(arena_allocator), version, }, ); @@ -37,9 +38,9 @@ pub fn set_version(version: []const u8, is_zls: bool) !void { }; const symlink_path = if (is_zls) - try tools.get_zvm_current_zls(arena_allocator) + try util_data.get_zvm_current_zls(arena_allocator) else - try tools.get_zvm_current_zig(arena_allocator); + try util_data.get_zvm_current_zig(arena_allocator); try update_current(version_path, symlink_path); if (is_zls) { @@ -54,15 +55,15 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { assert(symlink_path.len > 0); if (builtin.os.tag == .windows) { - if (tools.does_path_exist(symlink_path)) try std.fs.deleteTreeAbsolute(symlink_path); - try tools.copy_dir(zig_path, symlink_path); + if (util_tool.does_path_exist(symlink_path)) try std.fs.deleteTreeAbsolute(symlink_path); + try util_tool.copy_dir(zig_path, symlink_path); return; } // when platform is not windows, this is execute here // when file exist(it is a systemlink), delete it - if (tools.does_path_exist(symlink_path)) try std.fs.deleteFileAbsolute(symlink_path); + if (util_tool.does_path_exist(symlink_path)) try std.fs.deleteFileAbsolute(symlink_path); // system link it try std.posix.symlink(zig_path, symlink_path); @@ -70,9 +71,9 @@ fn update_current(zig_path: []const u8, symlink_path: []const u8) !void { /// verify current zig version fn verify_zig_version(expected_version: []const u8) !void { - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); - const actual_version = try tools.get_current_version(allocator, false); + const actual_version = try util_data.get_current_version(allocator, false); defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { @@ -84,9 +85,9 @@ fn verify_zig_version(expected_version: []const u8) !void { /// verify current zig version fn verify_zls_version(expected_version: []const u8) !void { - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); - const actual_version = try tools.get_current_version(allocator, true); + const actual_version = try util_data.get_current_version(allocator, true); defer allocator.free(actual_version); if (!std.mem.eql(u8, expected_version, actual_version)) { diff --git a/src/command.zig b/src/command.zig index 9bc6469..132de86 100644 --- a/src/command.zig +++ b/src/command.zig @@ -6,9 +6,12 @@ const options = @import("options"); const install = @import("install.zig"); const alias = @import("alias.zig"); const remove = @import("remove.zig"); -const tools = @import("tools.zig"); + const meta = @import("meta.zig"); const config = @import("config.zig"); +const util_data = @import("util/data.zig"); +const util_tool = @import("util/tool.zig"); +const util_http = @import("util/http.zig"); // Command types pub const Command = enum { @@ -103,21 +106,28 @@ pub fn handle_alias(params: []const []const u8) !void { var is_zls: bool = undefined; const basename = std.fs.path.basename(params[0]); - if (tools.eql_str(basename, "zig")) { + if (util_tool.eql_str(basename, "zig")) { is_zls = false; - } else if (tools.eql_str(basename, "zls")) { + } else if (util_tool.eql_str(basename, "zls")) { is_zls = true; } else return; - var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); + var arena = std.heap.ArenaAllocator.init(util_data.get_allocator()); defer arena.deinit(); const allocator = arena.allocator(); const new_params = try allocator.dupe([]const u8, params); - const current = try if (is_zls) tools.get_zvm_current_zls(allocator) else tools.get_zvm_current_zig(allocator); - const current_path = try std.fs.path.join(allocator, &.{ current, if (is_zls) "zls" else "zig" }); + const current = try if (is_zls) + util_data.get_zvm_current_zls(allocator) + else + util_data.get_zvm_current_zig(allocator); + + const current_path = try std.fs.path.join( + allocator, + &.{ current, if (is_zls) "zls" else "zig" }, + ); std.fs.accessAbsolute(current_path, .{}) catch |err| { if (err == std.fs.Dir.AccessError.FileNotFound) { @@ -132,13 +142,13 @@ pub fn handle_alias(params: []const []const u8) !void { } fn handle_list(param: ?[]const u8) !void { - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); const version_list: [][]const u8 = blk: { if (param) |p| { // when zls - if (tools.eql_str(p, "zls")) { - const res = try tools.http_get(allocator, config.zls_url); + if (util_tool.eql_str(p, "zls")) { + const res = try util_http.http_get(allocator, config.zls_url); defer allocator.free(res); var zls_meta = try meta.Zls.init(res, allocator); @@ -148,14 +158,14 @@ fn handle_list(param: ?[]const u8) !void { break :blk version_list; } else // when not zig - if (!tools.eql_str(p, "zig")) { + if (!util_tool.eql_str(p, "zig")) { std.debug.print("Error param, you can specify zig or zls\n", .{}); return; } } // when param is null - const res = try tools.http_get(allocator, config.zig_url); + const res = try util_http.http_get(allocator, config.zig_url); defer allocator.free(res); var zig_meta = try meta.Zig.init(res, allocator); @@ -165,7 +175,7 @@ fn handle_list(param: ?[]const u8) !void { break :blk version_list; }; - defer tools.free_str_array(version_list, allocator); + defer util_tool.free_str_array(version_list, allocator); for (version_list) |version| { std.debug.print("{s}\n", .{version}); @@ -176,9 +186,9 @@ fn install_version(subcmd: ?[]const u8, param: ?[]const u8) !void { if (subcmd) |scmd| { var is_zls: bool = undefined; - if (tools.eql_str(scmd, "zig")) { + if (util_tool.eql_str(scmd, "zig")) { is_zls = false; - } else if (tools.eql_str(scmd, "zls")) { + } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { std.debug.print("Unknown subcommand '{s}'. Use 'install zig/zls '.\n", .{scmd}); @@ -205,9 +215,9 @@ fn use_version(subcmd: ?[]const u8, param: ?[]const u8) !void { if (subcmd) |scmd| { var is_zls: bool = undefined; - if (tools.eql_str(scmd, "zig")) { + if (util_tool.eql_str(scmd, "zig")) { is_zls = false; - } else if (tools.eql_str(scmd, "zls")) { + } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { std.debug.print("Unknown subcommand '{s}'. Use 'use zig ' or 'use zls '.\n", .{scmd}); @@ -234,9 +244,9 @@ fn remove_version(subcmd: ?[]const u8, param: ?[]const u8) !void { if (subcmd) |scmd| { var is_zls: bool = undefined; - if (tools.eql_str(scmd, "zig")) { + if (util_tool.eql_str(scmd, "zig")) { is_zls = false; - } else if (tools.eql_str(scmd, "zls")) { + } else if (util_tool.eql_str(scmd, "zls")) { is_zls = true; } else { std.debug.print("Unknown subcommand '{s}'. Use 'remove zig ' or 'remove zls '.\n", .{scmd}); diff --git a/src/install.zig b/src/install.zig index ee0e8d1..08c0922 100644 --- a/src/install.zig +++ b/src/install.zig @@ -2,11 +2,13 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); -const architecture = @import("architecture.zig"); -const tools = @import("tools.zig"); const alias = @import("alias.zig"); const meta = @import("meta.zig"); -const extract = @import("extract.zig"); +const util_arch = @import("util/arch.zig"); +const util_data = @import("util/data.zig"); +const util_extract = @import("util/extract.zig"); +const util_tool = @import("util/tool.zig"); +const util_http = @import("util/http.zig"); const Version = struct { name: []const u8, @@ -26,9 +28,9 @@ pub fn install(version: []const u8, is_zls: bool) !void { /// Try to install the specified version of zig fn install_zig(version: []const u8) !void { - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); - const platform_str = try architecture.platform_str(architecture.DetectParams{ + const platform_str = try util_arch.platform_str(.{ .os = builtin.os.tag, .arch = builtin.cpu.arch, .reverse = true, @@ -40,24 +42,24 @@ fn install_zig(version: []const u8) !void { const arena_allocator = arena.allocator(); // get version path - const version_path = try tools.get_zvm_zig_version(arena_allocator); + const version_path = try util_data.get_zvm_zig_version(arena_allocator); // get extract path const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, version }); - if (tools.does_path_exist(extract_path)) { + if (util_tool.does_path_exist(extract_path)) { try alias.set_version(version, false); return; } // get version data const version_data: meta.Zig.VersionData = blk: { - const res = try tools.http_get(arena_allocator, config.zig_url); + const res = try util_http.http_get(arena_allocator, config.zig_url); var zig_meta = try meta.Zig.init(res, arena_allocator); const tmp_val = try zig_meta.get_version_data(version, platform_str, arena_allocator); break :blk tmp_val orelse return error.UnsupportedVersion; }; - const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ + const reverse_platform_str = try util_arch.platform_str(.{ .os = builtin.os.tag, .arch = builtin.cpu.arch, .reverse = false, @@ -70,13 +72,13 @@ fn install_zig(version: []const u8) !void { ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; - const new_file = try tools.download(parsed_uri, file_name, version_data.shasum, version_data.size); + const new_file = try util_http.download(parsed_uri, file_name, version_data.shasum, version_data.size); defer new_file.close(); - try tools.try_create_path(extract_path); + try util_tool.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); + try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); try alias.set_version(version, false); } @@ -84,20 +86,20 @@ fn install_zig(version: []const u8) !void { /// Try to install the specified version of zls fn install_zls(version: []const u8) !void { const true_version = blk: { - if (tools.eql_str("master", version)) { + if (util_tool.eql_str("master", version)) { std.debug.print("Sorry, the 'install zls' feature is not supported at this time. Please compile zls locally.", .{}); return; } for (config.zls_list_1, 0..) |val, i| { - if (tools.eql_str(val, version)) + if (util_tool.eql_str(val, version)) break :blk config.zls_list_2[i]; } break :blk version; }; - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); - const reverse_platform_str = try architecture.platform_str(architecture.DetectParams{ + const reverse_platform_str = try util_arch.platform_str(.{ .os = builtin.os.tag, .arch = builtin.cpu.arch, .reverse = true, @@ -109,18 +111,18 @@ fn install_zls(version: []const u8) !void { const arena_allocator = arena.allocator(); // get version path - const version_path = try tools.get_zvm_zls_version(arena_allocator); + const version_path = try util_data.get_zvm_zls_version(arena_allocator); // get extract path const extract_path = try std.fs.path.join(arena_allocator, &.{ version_path, true_version }); - if (tools.does_path_exist(extract_path)) { + if (util_tool.does_path_exist(extract_path)) { try alias.set_version(true_version, true); return; } // get version data const version_data: meta.Zls.VersionData = blk: { - const res = try tools.http_get(arena_allocator, config.zls_url); + const res = try util_http.http_get(arena_allocator, config.zls_url); var zls_meta = try meta.Zls.init(res, arena_allocator); const tmp_val = try zls_meta.get_version_data(true_version, reverse_platform_str, arena_allocator); break :blk tmp_val orelse return error.UnsupportedVersion; @@ -133,13 +135,16 @@ fn install_zls(version: []const u8) !void { ); const parsed_uri = std.Uri.parse(version_data.tarball) catch unreachable; - const new_file = try tools.download(parsed_uri, file_name, null, version_data.size); + const new_file = try util_http.download(parsed_uri, file_name, null, version_data.size); defer new_file.close(); - try tools.try_create_path(extract_path); + try util_tool.try_create_path(extract_path); const extract_dir = try std.fs.openDirAbsolute(extract_path, .{}); - try extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, true); + try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) + .zip + else + .tarxz, true); try alias.set_version(true_version, true); std.debug.print( diff --git a/src/main.zig b/src/main.zig index c2b508d..b0b105c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const tools = @import("tools.zig"); +const util_data = @import("util/data.zig"); const command = @import("command.zig"); pub fn main() !void { @@ -8,12 +8,12 @@ pub fn main() !void { defer if (gpa.deinit() == .leak) @panic("memory leaked!"); // init some useful data - try tools.data_init(gpa.allocator()); + try util_data.data_init(gpa.allocator()); // deinit some data - defer tools.data_deinit(); + defer util_data.data_deinit(); // get allocator - const allocator = tools.get_allocator(); + const allocator = util_data.get_allocator(); // get and free args const args = try std.process.argsAlloc(allocator); diff --git a/src/meta.zig b/src/meta.zig index 73224a3..eb986f9 100644 --- a/src/meta.zig +++ b/src/meta.zig @@ -4,14 +4,12 @@ //! We can get version list and version data const std = @import("std"); const config = @import("config.zig"); -const tools = @import("tools.zig"); +const util_tool = @import("util/tool.zig"); const json = std.json; const Allocator = std.mem.Allocator; const jsonValue = std.json.Parsed(std.json.Value); -const eql_str = tools.eql_str; - pub const Zig = struct { data: jsonValue, @@ -53,7 +51,7 @@ pub const Zig = struct { var it = self.data.value.object.iterator(); while (it.next()) |entry| { - if (!eql_str(entry.key_ptr.*, version)) + if (!util_tool.eql_str(entry.key_ptr.*, version)) continue; const now_version = entry.value_ptr; @@ -70,19 +68,19 @@ pub const Zig = struct { // get version // only for "master" it will have "version" field - if (eql_str(version_info_key, "version")) { + if (util_tool.eql_str(version_info_key, "version")) { result.version = try allocator.dupe(u8, version_info_entry_val.string); is_set_version = true; } else // get date - if (eql_str(version_info_key, "date")) { + if (util_tool.eql_str(version_info_key, "date")) { result.date = try allocator.dupe( u8, version_info_entry_val.string, ); } else // skip the useless entry - if (eql_str(version_info_key, platform_str)) { + if (util_tool.eql_str(version_info_key, platform_str)) { var platform_info = version_info_entry_val.object.iterator(); while (platform_info.next()) |playform_info_entry| { @@ -90,15 +88,15 @@ pub const Zig = struct { const playform_info_entry_val = playform_info_entry.value_ptr; // get tarball - if (eql_str(platform_info_entry_key, "tarball")) { + if (util_tool.eql_str(platform_info_entry_key, "tarball")) { result.tarball = try allocator.dupe(u8, playform_info_entry_val.string); } else // get shasum - if (eql_str(platform_info_entry_key, "shasum")) { + if (util_tool.eql_str(platform_info_entry_key, "shasum")) { result.shasum = playform_info_entry_val.string[0..64].*; } else // get size - if (eql_str(platform_info_entry_key, "size")) { + if (util_tool.eql_str(platform_info_entry_key, "size")) { const size = try std.fmt.parseUnsigned( usize, playform_info_entry_val.string, @@ -181,14 +179,14 @@ pub const Zls = struct { const item_obj = item.object; const tag = item_obj.get("tag_name") orelse continue; - if (!tools.eql_str(version, tag.string)) continue; + if (!util_tool.eql_str(version, tag.string)) continue; const assets = item_obj.get("assets") orelse continue; for (assets.array.items) |asset| { const asset_obj = asset.object; const name = asset_obj.get("name") orelse continue; - if (!tools.eql_str(file_name, name.string)) continue; + if (!util_tool.eql_str(file_name, name.string)) continue; const tarball = asset_obj.get("browser_download_url") orelse return null; const id = asset_obj.get("id") orelse return null; diff --git a/src/remove.zig b/src/remove.zig index 10389a3..4c19030 100644 --- a/src/remove.zig +++ b/src/remove.zig @@ -2,7 +2,9 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); -const tools = @import("tools.zig"); +const util_data=@import("util/data.zig"); +const util_tool=@import("util/tool.zig"); + /// try remove specified version pub fn remove(version: []const u8, is_zls: bool) !void { @@ -10,23 +12,23 @@ pub fn remove(version: []const u8, is_zls: bool) !void { if (!is_zls) break :blk version; for (config.zls_list_1, 0..) |val, i| { - if (tools.eql_str(val, version)) + if (util_tool.eql_str(val, version)) break :blk config.zls_list_2[i]; } break :blk version; }; - var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); + var arena = std.heap.ArenaAllocator.init(util_data.get_allocator()); defer arena.deinit(); const allocator = arena.allocator(); - const current_path = try if (is_zls) tools.get_zvm_current_zls(allocator) else tools.get_zvm_current_zig(allocator); + const current_path = try if (is_zls) util_data.get_zvm_current_zls(allocator) else util_data.get_zvm_current_zig(allocator); // try remove current path - if (tools.does_path_exist(current_path)) { - const current_version = try tools.get_current_version(allocator, is_zls); - if (tools.eql_str(current_version, true_version)) { + if (util_tool.does_path_exist(current_path)) { + const current_version = try util_data.get_current_version(allocator, is_zls); + if (util_tool.eql_str(current_version, true_version)) { if (builtin.os.tag == .windows) { try std.fs.deleteTreeAbsolute(current_path); } else { @@ -37,14 +39,14 @@ pub fn remove(version: []const u8, is_zls: bool) !void { const version_path = try std.fs.path.join(allocator, &.{ try if (is_zls) - tools.get_zvm_zls_version(allocator) + util_data.get_zvm_zls_version(allocator) else - tools.get_zvm_zig_version(allocator), + util_data.get_zvm_zig_version(allocator), true_version, }); // try remove version path - if (tools.does_path_exist(version_path)) { + if (util_tool.does_path_exist(version_path)) { try std.fs.deleteTreeAbsolute(version_path); } } diff --git a/src/tools.zig b/src/tools.zig deleted file mode 100644 index 1be0cca..0000000 --- a/src/tools.zig +++ /dev/null @@ -1,346 +0,0 @@ -//! this file just contains util function -//! these functions are generic -const std = @import("std"); -const builtin = @import("builtin"); -const config = @import("config.zig"); - -const sha2 = std.crypto.hash.sha2; -const testing = std.testing; - -/// Initialize the data. -pub fn data_init(tmp_allocator: std.mem.Allocator) !void { - config.allocator = tmp_allocator; - config.home_dir = if (builtin.os.tag == .windows) - try std.process.getEnvVarOwned(config.allocator, "USERPROFILE") - else - std.posix.getenv("HOME") orelse "."; -} - -/// Deinitialize the data. -pub fn data_deinit() void { - if (builtin.os.tag == .windows) - config.allocator.free(config.home_dir); -} - -/// new progress node -pub fn new_progress_node(name: []const u8, estimated_total_items: usize) std.Progress.Node { - return config.progress_root.start(name, estimated_total_items); -} - -/// Get home directory. -pub fn get_home() []const u8 { - return config.home_dir; -} - -/// Get the allocator. -pub fn get_allocator() std.mem.Allocator { - return config.allocator; -} - -/// Get zvm path segment -pub fn get_zvm_path_segment(allocator: std.mem.Allocator, segment: []const u8) ![]u8 { - return try std.fs.path.join(allocator, &[_][]const u8{ get_home(), ".zm", segment }); -} - -pub fn get_zvm_current_zig(allocator: std.mem.Allocator) ![]u8 { - const current = try get_zvm_path_segment(allocator, "current"); - defer allocator.free(current); - return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); -} - -pub fn get_zvm_current_zls(allocator: std.mem.Allocator) ![]u8 { - const current = try get_zvm_path_segment(allocator, "current"); - defer allocator.free(current); - return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); -} - -pub fn get_zvm_store(allocator: std.mem.Allocator) ![]u8 { - return get_zvm_path_segment(allocator, "store"); -} - -pub fn get_zvm_zig_version(allocator: std.mem.Allocator) ![]u8 { - const current = try get_zvm_path_segment(allocator, "version"); - defer allocator.free(current); - return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); -} - -pub fn get_zvm_zls_version(allocator: std.mem.Allocator) ![]u8 { - const current = try get_zvm_path_segment(allocator, "version"); - defer allocator.free(current); - return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); -} - -/// Free str array -pub fn free_str_array(str_arr: []const []const u8, allocator: std.mem.Allocator) void { - for (str_arr) |str| - allocator.free(str); - - allocator.free(str_arr); -} - -/// For verifying hash -pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: [64]u8) bool { - // if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters - - var actual_hash_bytes: [32]u8 = undefined; - var i: usize = 0; - - for (actual_hash_string) |char| { - const byte = switch (char) { - '0'...'9' => char - '0', - 'a'...'f' => char - 'a' + 10, - 'A'...'F' => char - 'A' + 10, - else => return false, // Invalid character in hash string - }; - - if (i % 2 == 0) { - actual_hash_bytes[i / 2] = byte << 4; - } else { - actual_hash_bytes[i / 2] |= byte; - } - - i += 1; - } - - return std.mem.eql(u8, computed_hash[0..], actual_hash_bytes[0..]); -} - -test "verify_hash basic test" { - const sample_hash: [32]u8 = [_]u8{ 0x33, 0x9a, 0x89, 0xdc, 0x08, 0x73, 0x6b, 0x84, 0xc4, 0x75, 0x2b, 0x3d, 0xed, 0xdc, 0x0f, 0x2c, 0x71, 0xb5, 0x0b, 0x66, 0xa2, 0x68, 0x5f, 0x26, 0x77, 0x9c, 0xbb, 0xac, 0x46, 0x11, 0x1b, 0x68 }; - - var sample_hash_hex: [64]u8 = undefined; - _ = std.fmt.bufPrint(&sample_hash_hex, "{}", .{std.fmt.fmtSliceHexLower(sample_hash[0..])}) catch unreachable; - - try testing.expect(verify_hash(sample_hash, &sample_hash_hex)); - try testing.expect(!verify_hash(sample_hash, "incorrect_hash")); -} - -/// http get -pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { - - // create a http client - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - // we ceate a buffer to store the http response - var buf: [2048]u8 = undefined; - - // try open a request - var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); - defer req.deinit(); - - // send request and wait response - try req.send(); - try req.wait(); - - if (req.response.status != .ok) { - return error.HttpRequestFailed; - } - - const res = try req.reader().readAllAlloc(allocator, 256 * 1024); - return res; -} - -/// eql str -pub fn eql_str(str1: []const u8, str2: []const u8) bool { - return std.mem.eql(u8, str1, str2); -} - -/// try to create path -pub fn try_create_path(path: []const u8) !void { - std.fs.cwd().makePath(path) catch |err| - if (err != error.PathAlreadyExists) return err; -} - -/// try to get zig/zls version -pub fn get_current_version(allocator: std.mem.Allocator, is_zls: bool) ![]u8 { - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const arena_allocator = arena.allocator(); - - const current_path = try std.fs.path.join(arena_allocator, &.{ - if (is_zls) - try get_zvm_current_zls(arena_allocator) - else - try get_zvm_current_zig(arena_allocator), - if (is_zls) - config.zls_name - else - config.zig_name, - }); - - // here we must use the absolute path, we can not just use "zig" - // because child process will use environment variable - var child_process = std.process.Child.init(&[_][]const u8{ current_path, if (is_zls) "--version" else "version" }, arena_allocator); - - child_process.stdin_behavior = .Close; - child_process.stdout_behavior = .Pipe; - child_process.stderr_behavior = .Close; - - try child_process.spawn(); - - if (child_process.stdout) |stdout| { - const version = try stdout.reader().readUntilDelimiterOrEofAlloc(allocator, '\n', 100) orelse return error.EmptyVersion; - return version; - } - - return error.FailedToReadVersion; -} - -// check dir exist -pub fn does_path_exist(path: []const u8) bool { - std.fs.accessAbsolute(path, .{}) catch |err| { - if (err == error.FileNotFound) return false; - }; - return true; -} - -// Check if directory path exists -pub fn does_path_exist2(dir: std.fs.Dir, path: []const u8) bool { - dir.access(path, .{}) catch |err| { - if (err == error.FileNotFound) return false; - }; - return true; -} - -/// Nested copy dir -/// only copy dir and file, no including link -pub fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { - var source = try std.fs.openDirAbsolute(source_dir, .{ .iterate = true }); - defer source.close(); - - std.fs.makeDirAbsolute(dest_dir) catch |err| { - if (err != error.PathAlreadyExists) - return err; - }; - - var dest = try std.fs.openDirAbsolute(dest_dir, .{ .iterate = true }); - defer dest.close(); - - var iterate = source.iterate(); - const allocator = get_allocator(); - while (try iterate.next()) |entry| { - const entry_name = entry.name; - - const source_sub_path = try std.fs.path.join(allocator, &.{ source_dir, entry_name }); - defer allocator.free(source_sub_path); - - const dest_sub_path = try std.fs.path.join(allocator, &.{ dest_dir, entry_name }); - defer allocator.free(dest_sub_path); - - switch (entry.kind) { - .directory => try copy_dir(source_sub_path, dest_sub_path), - .file => try std.fs.copyFileAbsolute(source_sub_path, dest_sub_path, .{}), - else => {}, - } - } -} - -/// download the url -/// and verify hashsum (if exist) -pub fn download( - uri: std.Uri, - file_name: []const u8, - shasum: ?[64]u8, - size: ?usize, -) !std.fs.File { - - // whether verify hashsum - const if_hash = shasum != null; - - // allocator - const allocator = get_allocator(); - - // this file store the downloaded src - const zvm_path = try get_zvm_path_segment(allocator, "store"); - defer allocator.free(zvm_path); - - var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); - defer store.close(); - - // if file exist - // and provide shasum - // then calculate hash and verify, return the file if eql - // otherwise delete this file - if (does_path_exist2(store, file_name)) { - if (if_hash) { - var sha256 = sha2.Sha256.init(.{}); - const file = try store.openFile(file_name, .{}); - var buffer: [512]u8 = undefined; - while (true) { - const byte_nums = try file.read(&buffer); - if (byte_nums == 0) - break; - - sha256.update(buffer[0..byte_nums]); - } - var result = std.mem.zeroes([32]u8); - sha256.final(&result); - - if (verify_hash(result, shasum.?)) { - try file.seekTo(0); - return file; - } - } - try store.deleteFile(file_name); - } - - // http client - var client = std.http.Client{ .allocator = allocator }; - defer client.deinit(); - - var header_buffer: [10240]u8 = undefined; // 1024b - - var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); - defer req.deinit(); - - try req.send(); - try req.wait(); - - // ensure req successfully - if (req.response.status != .ok) - return error.DownFailed; - - // Compare file sizes - if (size) |ss| { - const total_size: usize = @intCast(req.response.content_length orelse 0); - if (ss != total_size) - return error.IncorrectSize; - } - - // create a new file - const new_file = try store.createFile(file_name, .{ - .read = true, - }); - - // whether enable hashsum - var sha256 = if (if_hash) sha2.Sha256.init(.{}) else undefined; - - // the tmp buffer to store the receive data - var buffer: [512]u8 = undefined; - // get reader - const reader = req.reader(); - while (true) { - // the read byte number - const byte_nums = try reader.read(&buffer); - if (byte_nums == 0) - break; - if (if_hash) - sha256.update(buffer[0..byte_nums]); - // write to file - try new_file.writeAll(buffer[0..byte_nums]); - } - - // when calculate hashsum - if (if_hash) { - var result = std.mem.zeroes([32]u8); - sha256.final(&result); - - if (!verify_hash(result, shasum.?)) - return error.HashMismatch; - } - - try new_file.seekTo(0); - - return new_file; -} diff --git a/src/architecture.zig b/src/util/arch.zig similarity index 100% rename from src/architecture.zig rename to src/util/arch.zig diff --git a/src/util/data.zig b/src/util/data.zig new file mode 100644 index 0000000..382cff1 --- /dev/null +++ b/src/util/data.zig @@ -0,0 +1,101 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const config = @import("../config.zig"); + +/// Initialize the data. +pub fn data_init(tmp_allocator: std.mem.Allocator) !void { + config.allocator = tmp_allocator; + config.home_dir = if (builtin.os.tag == .windows) + try std.process.getEnvVarOwned(config.allocator, "USERPROFILE") + else + std.posix.getenv("HOME") orelse "."; +} + +/// Deinitialize the data. +pub fn data_deinit() void { + if (builtin.os.tag == .windows) + config.allocator.free(config.home_dir); +} + +/// new progress node +pub fn new_progress_node(name: []const u8, estimated_total_items: usize) std.Progress.Node { + return config.progress_root.start(name, estimated_total_items); +} + +/// Get home directory. +pub fn get_home() []const u8 { + return config.home_dir; +} + +/// Get the allocator. +pub fn get_allocator() std.mem.Allocator { + return config.allocator; +} + +/// Get zvm path segment +pub fn get_zvm_path_segment(allocator: std.mem.Allocator, segment: []const u8) ![]u8 { + return try std.fs.path.join(allocator, &[_][]const u8{ get_home(), ".zm", segment }); +} + +pub fn get_zvm_current_zig(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "current"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); +} + +pub fn get_zvm_current_zls(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "current"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); +} + +pub fn get_zvm_store(allocator: std.mem.Allocator) ![]u8 { + return get_zvm_path_segment(allocator, "store"); +} + +pub fn get_zvm_zig_version(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "version"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zig" }); +} + +pub fn get_zvm_zls_version(allocator: std.mem.Allocator) ![]u8 { + const current = try get_zvm_path_segment(allocator, "version"); + defer allocator.free(current); + return try std.fs.path.join(allocator, &[_][]const u8{ current, "zls" }); +} + +/// try to get zig/zls version +pub fn get_current_version(allocator: std.mem.Allocator, is_zls: bool) ![]u8 { + var arena = std.heap.ArenaAllocator.init(allocator); + defer arena.deinit(); + const arena_allocator = arena.allocator(); + + const current_path = try std.fs.path.join(arena_allocator, &.{ + if (is_zls) + try get_zvm_current_zls(arena_allocator) + else + try get_zvm_current_zig(arena_allocator), + if (is_zls) + config.zls_name + else + config.zig_name, + }); + + // here we must use the absolute path, we can not just use "zig" + // because child process will use environment variable + var child_process = std.process.Child.init(&[_][]const u8{ current_path, if (is_zls) "--version" else "version" }, arena_allocator); + + child_process.stdin_behavior = .Close; + child_process.stdout_behavior = .Pipe; + child_process.stderr_behavior = .Close; + + try child_process.spawn(); + + if (child_process.stdout) |stdout| { + const version = try stdout.reader().readUntilDelimiterOrEofAlloc(allocator, '\n', 100) orelse return error.EmptyVersion; + return version; + } + + return error.FailedToReadVersion; +} diff --git a/src/extract.zig b/src/util/extract.zig similarity index 87% rename from src/extract.zig rename to src/util/extract.zig index 66d241c..266c44d 100644 --- a/src/extract.zig +++ b/src/util/extract.zig @@ -1,7 +1,7 @@ //! This file is used to decompress the file const std = @import("std"); const builtin = @import("builtin"); -const tools = @import("tools.zig"); +const data = @import("data.zig"); const xz = std.compress.xz; const tar = std.tar; @@ -23,7 +23,7 @@ pub fn extract( fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File, is_zls: bool) !void { var buffered_reader = std.io.bufferedReader(file.reader()); - var decompressed = try xz.decompress(tools.get_allocator(), buffered_reader.reader()); + var decompressed = try xz.decompress(data.get_allocator(), buffered_reader.reader()); defer decompressed.deinit(); try tar.pipeToFileSystem( @@ -35,12 +35,12 @@ fn extract_tarxz_to_dir(out_dir: std.fs.Dir, file: std.fs.File, is_zls: bool) !v /// extract zip to directory fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { - var arena = std.heap.ArenaAllocator.init(tools.get_allocator()); + var arena = std.heap.ArenaAllocator.init(data.get_allocator()); defer arena.deinit(); const allocator = arena.allocator(); // for decompressing zig, we need to make a temp directory - const tmp_path = try tools.get_zvm_path_segment(allocator, "tmpdir"); + const tmp_path = try data.get_zvm_path_segment(allocator, "tmpdir"); defer std.fs.deleteDirAbsolute(tmp_path) catch unreachable; try std.fs.makeDirAbsolute(tmp_path); diff --git a/src/util/hash.zig b/src/util/hash.zig new file mode 100644 index 0000000..034b439 --- /dev/null +++ b/src/util/hash.zig @@ -0,0 +1,38 @@ +const std = @import("std"); + +/// For verifying hash +pub fn verify_hash(computed_hash: [32]u8, actual_hash_string: [64]u8) bool { + // if (actual_hash_string.len != 64) return false; // SHA256 hash should be 64 hex characters + + var actual_hash_bytes: [32]u8 = undefined; + var i: usize = 0; + + for (actual_hash_string) |char| { + const byte = switch (char) { + '0'...'9' => char - '0', + 'a'...'f' => char - 'a' + 10, + 'A'...'F' => char - 'A' + 10, + else => return false, // Invalid character in hash string + }; + + if (i % 2 == 0) { + actual_hash_bytes[i / 2] = byte << 4; + } else { + actual_hash_bytes[i / 2] |= byte; + } + + i += 1; + } + + return std.mem.eql(u8, computed_hash[0..], actual_hash_bytes[0..]); +} + +test "verify_hash basic test" { + const sample_hash: [32]u8 = [_]u8{ 0x33, 0x9a, 0x89, 0xdc, 0x08, 0x73, 0x6b, 0x84, 0xc4, 0x75, 0x2b, 0x3d, 0xed, 0xdc, 0x0f, 0x2c, 0x71, 0xb5, 0x0b, 0x66, 0xa2, 0x68, 0x5f, 0x26, 0x77, 0x9c, 0xbb, 0xac, 0x46, 0x11, 0x1b, 0x68 }; + + var sample_hash_hex: [64]u8 = undefined; + _ = std.fmt.bufPrint(&sample_hash_hex, "{}", .{std.fmt.fmtSliceHexLower(sample_hash[0..])}) catch unreachable; + + try std.testing.expect(verify_hash(sample_hash, &sample_hash_hex)); + try std.testing.expect(!verify_hash(sample_hash, "incorrect_hash")); +} diff --git a/src/util/http.zig b/src/util/http.zig new file mode 100644 index 0000000..da16f1f --- /dev/null +++ b/src/util/http.zig @@ -0,0 +1,139 @@ +const std = @import("std"); +const data = @import("data.zig"); +const tool = @import("tool.zig"); +const hash = @import("hash.zig"); + +/// http get +pub fn http_get(allocator: std.mem.Allocator, uri: std.Uri) ![]const u8 { + + // create a http client + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + // we ceate a buffer to store the http response + var buf: [2048]u8 = undefined; + + // try open a request + var req = try client.open(.GET, uri, .{ .server_header_buffer = &buf }); + defer req.deinit(); + + // send request and wait response + try req.send(); + try req.wait(); + + if (req.response.status != .ok) { + return error.HttpRequestFailed; + } + + const res = try req.reader().readAllAlloc(allocator, 256 * 1024); + return res; +} + +/// download the url +/// and verify hashsum (if exist) +pub fn download( + uri: std.Uri, + file_name: []const u8, + shasum: ?[64]u8, + size: ?usize, +) !std.fs.File { + + // whether verify hashsum + const if_hash = shasum != null; + + // allocator + const allocator = data.get_allocator(); + + // this file store the downloaded src + const zvm_path = try data.get_zvm_path_segment(allocator, "store"); + defer allocator.free(zvm_path); + + var store = try std.fs.cwd().makeOpenPath(zvm_path, .{}); + defer store.close(); + + // if file exist + // and provide shasum + // then calculate hash and verify, return the file if eql + // otherwise delete this file + if (tool.does_path_exist2(store, file_name)) { + if (if_hash) { + var sha256 = std.crypto.hash.sha2.Sha256.init(.{}); + const file = try store.openFile(file_name, .{}); + var buffer: [512]u8 = undefined; + while (true) { + const byte_nums = try file.read(&buffer); + if (byte_nums == 0) + break; + + sha256.update(buffer[0..byte_nums]); + } + var result = std.mem.zeroes([32]u8); + sha256.final(&result); + + if (hash.verify_hash(result, shasum.?)) { + try file.seekTo(0); + return file; + } + } + try store.deleteFile(file_name); + } + + // http client + var client = std.http.Client{ .allocator = allocator }; + defer client.deinit(); + + var header_buffer: [10240]u8 = undefined; // 1024b + + var req = try client.open(.GET, uri, .{ .server_header_buffer = &header_buffer }); + defer req.deinit(); + + try req.send(); + try req.wait(); + + // ensure req successfully + if (req.response.status != .ok) + return error.DownFailed; + + // Compare file sizes + if (size) |ss| { + const total_size: usize = @intCast(req.response.content_length orelse 0); + if (ss != total_size) + return error.IncorrectSize; + } + + // create a new file + const new_file = try store.createFile(file_name, .{ + .read = true, + }); + + // whether enable hashsum + var sha256 = if (if_hash) std.crypto.hash.sha2.Sha256.init(.{}) else undefined; + + // the tmp buffer to store the receive data + var buffer: [512]u8 = undefined; + // get reader + const reader = req.reader(); + while (true) { + // the read byte number + const byte_nums = try reader.read(&buffer); + if (byte_nums == 0) + break; + if (if_hash) + sha256.update(buffer[0..byte_nums]); + // write to file + try new_file.writeAll(buffer[0..byte_nums]); + } + + // when calculate hashsum + if (if_hash) { + var result = std.mem.zeroes([32]u8); + sha256.final(&result); + + if (!hash.verify_hash(result, shasum.?)) + return error.HashMismatch; + } + + try new_file.seekTo(0); + + return new_file; +} diff --git a/src/util/tool.zig b/src/util/tool.zig new file mode 100644 index 0000000..8eee436 --- /dev/null +++ b/src/util/tool.zig @@ -0,0 +1,70 @@ +const std = @import("std"); +const data = @import("data.zig"); + +/// Free str array +pub fn free_str_array(str_arr: []const []const u8, allocator: std.mem.Allocator) void { + for (str_arr) |str| + allocator.free(str); + + allocator.free(str_arr); +} + +/// eql str +pub fn eql_str(str1: []const u8, str2: []const u8) bool { + return std.mem.eql(u8, str1, str2); +} + +/// try to create path +pub fn try_create_path(path: []const u8) !void { + std.fs.cwd().makePath(path) catch |err| + if (err != error.PathAlreadyExists) return err; +} + +// check dir exist +pub fn does_path_exist(path: []const u8) bool { + std.fs.accessAbsolute(path, .{}) catch |err| { + if (err == error.FileNotFound) return false; + }; + return true; +} + +// Check if directory path exists +pub fn does_path_exist2(dir: std.fs.Dir, path: []const u8) bool { + dir.access(path, .{}) catch |err| { + if (err == error.FileNotFound) return false; + }; + return true; +} + +/// Nested copy dir +/// only copy dir and file, no including link +pub fn copy_dir(source_dir: []const u8, dest_dir: []const u8) !void { + var source = try std.fs.openDirAbsolute(source_dir, .{ .iterate = true }); + defer source.close(); + + std.fs.makeDirAbsolute(dest_dir) catch |err| { + if (err != error.PathAlreadyExists) + return err; + }; + + var dest = try std.fs.openDirAbsolute(dest_dir, .{ .iterate = true }); + defer dest.close(); + + var iterate = source.iterate(); + const allocator = data.get_allocator(); + while (try iterate.next()) |entry| { + const entry_name = entry.name; + + const source_sub_path = try std.fs.path.join(allocator, &.{ source_dir, entry_name }); + defer allocator.free(source_sub_path); + + const dest_sub_path = try std.fs.path.join(allocator, &.{ dest_dir, entry_name }); + defer allocator.free(dest_sub_path); + + switch (entry.kind) { + .directory => try copy_dir(source_sub_path, dest_sub_path), + .file => try std.fs.copyFileAbsolute(source_sub_path, dest_sub_path, .{}), + else => {}, + } + } +} From 194059981292ce35b138b2296f574b9a6c9c5722 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 8 Aug 2024 13:23:21 +0800 Subject: [PATCH 23/26] remove useless code --- src/install.zig | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/install.zig b/src/install.zig index 08c0922..7846572 100644 --- a/src/install.zig +++ b/src/install.zig @@ -147,19 +147,6 @@ fn install_zls(version: []const u8) !void { .tarxz, true); try alias.set_version(true_version, true); - std.debug.print( - \\zls version data: - \\version: {s} - \\id: {} - \\size: {} - \\tarball: {s} - \\ - , .{ - version_data.version, - version_data.id, - version_data.size, - version_data.tarball, - }); } pub fn build_zls() !void {} From 4487078215b873dcddfc31d36b2cf5dc1724ab53 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Thu, 8 Aug 2024 13:24:05 +0800 Subject: [PATCH 24/26] format code --- src/remove.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/remove.zig b/src/remove.zig index 4c19030..6969e36 100644 --- a/src/remove.zig +++ b/src/remove.zig @@ -2,9 +2,8 @@ const std = @import("std"); const builtin = @import("builtin"); const config = @import("config.zig"); -const util_data=@import("util/data.zig"); -const util_tool=@import("util/tool.zig"); - +const util_data = @import("util/data.zig"); +const util_tool = @import("util/tool.zig"); /// try remove specified version pub fn remove(version: []const u8, is_zls: bool) !void { From 2f5061ac109bc152fda8917ca01523f28b2c0f9c Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Fri, 9 Aug 2024 14:53:53 +0800 Subject: [PATCH 25/26] fix --- src/command.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/command.zig b/src/command.zig index 132de86..78802bb 100644 --- a/src/command.zig +++ b/src/command.zig @@ -131,7 +131,7 @@ pub fn handle_alias(params: []const []const u8) !void { std.fs.accessAbsolute(current_path, .{}) catch |err| { if (err == std.fs.Dir.AccessError.FileNotFound) { - std.debug.print("{s} has not been installed yet, please install it fist!\n", .{if (is_zls) "Zls" else "Zig"}); + std.debug.print("{s} has not been installed yet, please install it fist!\n", .{if (is_zls) "zls" else "Zig"}); std.process.exit(1); } return err; @@ -285,9 +285,9 @@ fn display_help() !void { \\ \\Commands: \\ ls, list List the versions of Zig or zls available to zvm. - \\ i, install Install the specified version of Zig or Zls. - \\ use Use the specified version of Zig Zls. - \\ remove Remove the specified version of Zig or Zls + \\ i, install Install the specified version of Zig or zls. + \\ use Use the specified version of Zig or zls. + \\ remove Remove the specified version of Zig or zls \\ --version Display zvm version. \\ --help Display this help message. \\ From 60450715bf75f152ac456bb5852bf42ac6e2ed65 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Mon, 19 Aug 2024 22:52:44 +0800 Subject: [PATCH 26/26] fix for windows platform --- src/install.zig | 12 ++++++++++++ src/util/extract.zig | 22 +++++++--------------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/install.zig b/src/install.zig index 7846572..8d21553 100644 --- a/src/install.zig +++ b/src/install.zig @@ -80,6 +80,18 @@ fn install_zig(version: []const u8) !void { try util_extract.extract(extract_dir, new_file, if (builtin.os.tag == .windows) .zip else .tarxz, false); + // mv + const sub_path = try std.fs.path.join(arena_allocator, &.{ + extract_path, try std.mem.concat( + arena_allocator, + u8, + &.{ "zig-", reverse_platform_str, "-", version }, + ), + }); + defer std.fs.deleteTreeAbsolute(sub_path) catch unreachable; + + try util_tool.copy_dir(sub_path, extract_path); + try alias.set_version(version, false); } diff --git a/src/util/extract.zig b/src/util/extract.zig index 266c44d..cbf743b 100644 --- a/src/util/extract.zig +++ b/src/util/extract.zig @@ -2,6 +2,7 @@ const std = @import("std"); const builtin = @import("builtin"); const data = @import("data.zig"); +const tool = @import("tool.zig"); const xz = std.compress.xz; const tar = std.tar; @@ -41,26 +42,17 @@ fn extract_zip_dir(out_dir: std.fs.Dir, file: std.fs.File) !void { const allocator = arena.allocator(); // for decompressing zig, we need to make a temp directory const tmp_path = try data.get_zvm_path_segment(allocator, "tmpdir"); - defer std.fs.deleteDirAbsolute(tmp_path) catch unreachable; + defer std.fs.deleteTreeAbsolute(tmp_path) catch unreachable; try std.fs.makeDirAbsolute(tmp_path); var tmp_dir = try std.fs.openDirAbsolute(tmp_path, .{ .iterate = true }); + defer tmp_dir.close(); - // extract zig + // extract zip try std.zip.extract(tmp_dir, file.seekableStream(), .{}); - var iterate = tmp_dir.iterate(); - var sub_dir = blk: { - const entry = try iterate.next() orelse return error.NotFound; - break :blk try tmp_dir.openDir(entry.name, .{ .iterate = true }); - }; - defer sub_dir.close(); + const out_path = try out_dir.realpathAlloc(allocator, ""); + defer allocator.free(out_path); - const sub_path = try sub_dir.realpathAlloc(allocator, ""); - defer std.fs.deleteDirAbsolute(sub_path) catch unreachable; - - var sub_iterate = sub_dir.iterate(); - while (try sub_iterate.next()) |entry| { - try std.fs.rename(sub_dir, entry.name, out_dir, entry.name); - } + try tool.copy_dir(tmp_path, out_path); }