Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: windows support #42

Merged
merged 5 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ zig-out-*
# libarchive
*.o
*.a
.zig-cache
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});

Expand Down
99 changes: 89 additions & 10 deletions src/alias.zig
Original file line number Diff line number Diff line change
@@ -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);
Expand All @@ -14,19 +16,96 @@ 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) {
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 => {
try std.fs.cwd().deleteFile(symlinkPath);
try std.posix.symlink(zigPath, symlinkPath);
// The path already exists and is a directory, nothing to do here
jinzhongjia marked this conversation as resolved.
Show resolved Hide resolved
// std.debug.print("Versions directory already exists: {s}\n", .{version_path});
},
else => return err,
else => {
tools.log.err("make dir failed, dir is {s}", .{dest_dir});
jinzhongjia marked this conversation as resolved.
Show resolved Hide resolved
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 {
Expand Down Expand Up @@ -54,7 +133,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);

Expand Down
38 changes: 22 additions & 16 deletions src/download.zig
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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(.{
Expand All @@ -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
Expand All @@ -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});
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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});

Expand Down Expand Up @@ -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);
Expand Down
48 changes: 44 additions & 4 deletions src/extract.zig
Original file line number Diff line number Diff line change
@@ -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);
}
}
12 changes: 9 additions & 3 deletions src/main.zig
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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);
Expand Down
42 changes: 42 additions & 0 deletions src/tools.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
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 },
);
}

Loading