diff --git a/.gitignore b/.gitignore index 1c6f9e4..edc3e9a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ zig-out-* # libarchive *.o *.a +.zig-cache diff --git a/build.zig b/build.zig index b67c5be..f2710d4 100644 --- a/build.zig +++ b/build.zig @@ -31,7 +31,7 @@ pub fn build(b: *std.Build) void { .name = "zvm", .root_source_file = .{ .src_path = .{ .owner = b, .sub_path = "src/main.zig" } }, .target = target, - .optimize = .ReleaseFast, + .optimize = optimize, .version = version, }); diff --git a/src/alias.zig b/src/alias.zig index 652acb9..5a9408d 100644 --- a/src/alias.zig +++ b/src/alias.zig @@ -1,8 +1,10 @@ const std = @import("std"); +const builtin = @import("builtin"); +const tools = @import("tools.zig"); pub fn setZigVersion(version: []const u8) !void { const allocator = std.heap.page_allocator; - const userHome = getUserHome(); + const userHome = tools.getHome(); const zigPath = try std.fs.path.join(allocator, &[_][]const u8{ userHome, ".zm", "versions", version }); defer allocator.free(zigPath); @@ -14,19 +16,93 @@ pub fn setZigVersion(version: []const u8) !void { try verifyZigVersion(allocator, version); } -fn getUserHome() []const u8 { - return std.posix.getenv("HOME") orelse "."; +fn updateSymlink(zigPath: []const u8, symlinkPath: []const u8) !void { + if (builtin.os.tag == .windows) { + if (std.fs.path.dirname(symlinkPath)) |dirname| { + var parent_dir = try std.fs.openDirAbsolute(dirname, .{ + .iterate = true, + }); + defer parent_dir.close(); + try parent_dir.deleteTree(std.fs.path.basename(symlinkPath)); + } else { + @panic("sorry, dirname is not avaiable!"); + } + if (doesDirExist(symlinkPath)) try std.fs.deleteDirAbsolute(symlinkPath); + try copyDir(zigPath, symlinkPath); + } else { + if (doesFileExist(symlinkPath)) try std.fs.cwd().deleteFile(symlinkPath); + std.posix.symlink(zigPath, symlinkPath) catch |err| switch (err) { + error.PathAlreadyExists => { + try std.fs.cwd().deleteFile(symlinkPath); + try std.posix.symlink(zigPath, symlinkPath); + }, + else => return err, + }; + } } -fn updateSymlink(zigPath: []const u8, symlinkPath: []const u8) !void { - if (doesFileExist(symlinkPath)) try std.fs.cwd().deleteFile(symlinkPath); - std.posix.symlink(zigPath, symlinkPath) catch |err| switch (err) { - error.PathAlreadyExists => { - try std.fs.cwd().deleteFile(symlinkPath); - try std.posix.symlink(zigPath, symlinkPath); +fn copyDir(source_dir: []const u8, dest_dir: []const u8) !void { + var source = try std.fs.openDirAbsolute( + source_dir, + .{ .iterate = true }, + ); + defer source.close(); + + // try make dir + std.fs.makeDirAbsolute(dest_dir) catch |err| switch (err) { + error.PathAlreadyExists => {}, + else => { + tools.log.err("Failed to create directory: {s}", .{dest_dir}); + return err; }, - else => return err, }; + + var dest = try std.fs.openDirAbsolute( + dest_dir, + .{ .iterate = true }, + ); + defer dest.close(); + + var iterate = source.iterate(); + const allocator = tools.getAllocator(); + 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 copyDir(source_sub_path, dest_sub_path); + }, + .file => { + try std.fs.copyFileAbsolute(source_sub_path, dest_sub_path, .{}); + }, + else => {}, + } + } +} + +fn doesDirExist(path: []const u8) bool { + const result = blk: { + _ = std.fs.openDirAbsolute(path, .{}) catch |err| { + switch (err) { + error.FileNotFound => break :blk false, + else => break :blk true, + } + }; + break :blk true; + }; + return result; } fn doesFileExist(path: []const u8) bool { @@ -54,7 +130,7 @@ fn verifyZigVersion(allocator: std.mem.Allocator, expectedVersion: []const u8) ! } fn retrieveZigVersion(allocator: std.mem.Allocator) ![]u8 { - const userHome = getUserHome(); + const userHome = tools.getHome(); const symlinkPath = try std.fs.path.join(allocator, &[_][]const u8{ userHome, ".zm", "current" }); defer allocator.free(symlinkPath); diff --git a/src/download.zig b/src/download.zig index ba2048a..9001de9 100644 --- a/src/download.zig +++ b/src/download.zig @@ -1,5 +1,6 @@ const std = @import("std"); const builtin = @import("builtin"); +const tools = @import("tools.zig"); const sha2 = @import("std").crypto.hash.sha2; const architecture = @import("architecture.zig"); const Progress = std.Progress; @@ -10,11 +11,6 @@ const crypto = std.crypto; const archive_ext = if (builtin.os.tag == .windows) "zip" else "tar.xz"; -fn getZvmPathSegment(segment: []const u8) ![]u8 { - const user_home = std.posix.getenv("HOME") orelse "."; - return std.fs.path.join(std.heap.page_allocator, &[_][]const u8{ user_home, ".zm", segment }); -} - pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u8) !?[32]u8 { // Initialize the Progress structure const root_node = Progress.start(.{ @@ -24,11 +20,11 @@ pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u defer root_node.end(); + const data_allocator = tools.getAllocator(); // Ensure version directory exists before any operation - const version_path = try getZvmPathSegment("versions"); - defer allocator.free(version_path); + const version_path = try tools.getZvmPathSegment(data_allocator, "versions"); + defer data_allocator.free(version_path); - defer allocator.free(version_path); std.fs.cwd().makePath(version_path) catch |err| switch (err) { error.PathAlreadyExists => { // The path already exists and is a directory, nothing to do here @@ -44,8 +40,8 @@ pub fn content(allocator: std.mem.Allocator, version: []const u8, url: []const u const version_folder_name = try std.fmt.allocPrint(allocator, "versions/{s}", .{version}); defer allocator.free(version_folder_name); - const version_folder_path = try getZvmPathSegment(version_folder_name); - defer allocator.free(version_folder_path); + const version_folder_path = try tools.getZvmPathSegment(data_allocator, version_folder_name); + defer data_allocator.free(version_folder_path); if (checkExistingVersion(version_folder_path)) { std.debug.print("→ Version {s} is already installed.\n", .{version}); @@ -91,7 +87,13 @@ fn confirmUserChoice() bool { return std.ascii.toLower(buffer[0]) == 'y'; } -fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path: []const u8, version: []const u8, root_node: std.Progress.Node) ![32]u8 { +fn downloadAndExtract( + allocator: std.mem.Allocator, + uri: std.Uri, + version_path: []const u8, + version: []const u8, + root_node: std.Progress.Node, +) ![32]u8 { var client = std.http.Client{ .allocator = allocator }; defer client.deinit(); @@ -149,9 +151,13 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path: const c_allocator = std.heap.c_allocator; // ~/.zm/versions/zig-macos-x86_64-0.10.0.tar.xz - const zvm_path = try getZvmPathSegment(""); - const downloaded_file_path = try std.fs.path.join(allocator, &.{ zvm_path, file_name }); - defer allocator.free(downloaded_file_path); + const data_allocator = tools.getAllocator(); + + const zvm_path = try tools.getZvmPathSegment(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); std.debug.print("Downloaded file path: {s}\n", .{downloaded_file_path}); @@ -179,8 +185,8 @@ fn downloadAndExtract(allocator: std.mem.Allocator, uri: std.Uri, version_path: } fn openOrCreateZvmDir() !std.fs.Dir { - const zvm_path = try getZvmPathSegment(""); - defer std.heap.page_allocator.free(zvm_path); + const zvm_path = try tools.getZvmPathSegment(tools.getAllocator(), ""); + defer tools.getAllocator().free(zvm_path); const openDirOptions = .{ .access_sub_paths = true, .no_follow = false }; const potentialDir = std.fs.cwd().openDir(zvm_path, openDirOptions); diff --git a/src/extract.zig b/src/extract.zig index 6108133..6175342 100644 --- a/src/extract.zig +++ b/src/extract.zig @@ -1,8 +1,48 @@ const std = @import("std"); +const builtin = @import("builtin"); +const tools = @import("tools.zig"); pub fn extract_tarxz_to_dir(allocator: std.mem.Allocator, outDir: std.fs.Dir, file: std.fs.File) !void { - var buffered_reader = std.io.bufferedReader(file.reader()); - var decompressed = try std.compress.xz.decompress(allocator, buffered_reader.reader()); - defer decompressed.deinit(); - try std.tar.pipeToFileSystem(outDir, decompressed.reader(), .{ .mode_mode = .executable_bit_only, .strip_components = 1 }); + if (builtin.os.tag == .windows) { + try extract_zip_dir(outDir, file); + } else { + var buffered_reader = std.io.bufferedReader(file.reader()); + var decompressed = try std.compress.xz.decompress(allocator, buffered_reader.reader()); + defer decompressed.deinit(); + try std.tar.pipeToFileSystem(outDir, decompressed.reader(), .{ .mode_mode = .executable_bit_only, .strip_components = 1 }); + } +} + +pub fn extract_zip_dir(outDir: std.fs.Dir, file: std.fs.File) !void { + var arena = std.heap.ArenaAllocator.init(tools.getAllocator()); + defer arena.deinit(); + + const allocator = arena.allocator(); + + const tmp_path = try tools.getZvmPathSegment(allocator, "tmpdir"); + defer std.fs.deleteDirAbsolute(tmp_path) catch unreachable; + + // make tmp dir + try std.fs.makeDirAbsolute(tmp_path); + var tmp_dir = try std.fs.openDirAbsolute(tmp_path, .{ .iterate = true }); + + 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 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, outDir, entry.name); + } } diff --git a/src/main.zig b/src/main.zig index b6b13c2..8a204ff 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const tools = @import("tools.zig"); const Command = @import("command.zig").Command; const handleCommands = @import("command.zig").handleCommands; @@ -14,9 +15,14 @@ const CommandOption = struct { }; pub fn main() !void { - const allocator = std.heap.page_allocator; - const args = try std.process.argsAlloc(allocator); - defer std.process.argsFree(allocator, args); + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + defer if (gpa.deinit() == .leak) @panic("memory leaked!"); + + try tools.dataInit(gpa.allocator()); + defer tools.dataDeinit(); + + const args = try std.process.argsAlloc(tools.getAllocator()); + defer std.process.argsFree(tools.getAllocator(), args); const cmd_data = try parseArgs(args); try handleCommands(cmd_data.cmd, cmd_data.params); diff --git a/src/tools.zig b/src/tools.zig new file mode 100644 index 0000000..49473af --- /dev/null +++ b/src/tools.zig @@ -0,0 +1,41 @@ +const std = @import("std"); +const builtin = @import("builtin"); + +var allocator: std.mem.Allocator = undefined; + +var home_dir: []const u8 = undefined; + +pub const log = std.log.scoped(.zvm); + +/// init the data +pub fn dataInit(tmp_allocator: std.mem.Allocator) !void { + allocator = tmp_allocator; + // setting the home dir + home_dir = if (builtin.os.tag == .windows) + try std.process.getEnvVarOwned(allocator, "USERPROFILE") + else + std.posix.getenv("HOME") orelse "."; +} + +/// deinit the data +pub fn dataDeinit() void { + if (builtin.os.tag == .windows) + allocator.free(home_dir); +} + +/// get home dir +pub fn getHome() []const u8 { + return home_dir; +} + +/// get the allocator +pub fn getAllocator() std.mem.Allocator { + return allocator; +} + +pub fn getZvmPathSegment(_allocator: std.mem.Allocator, segment: []const u8) ![]u8 { + return std.fs.path.join( + _allocator, + &[_][]const u8{ getHome(), ".zm", segment }, + ); +}