From c6f8e6878a90114d4fad3896db7dc78e70246241 Mon Sep 17 00:00:00 2001 From: jinzhongjia Date: Sun, 4 Aug 2024 16:52:10 +0800 Subject: [PATCH] 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..190b615 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