Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
10 changes: 7 additions & 3 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ const Allocator = std.mem.Allocator;
const streql = @import("common").streql;
const Version = @import("main.zig").Version;
const utils = @import("utils.zig");

const message_for_empty_args =
\\Refer below for zigverm usage.
;
const helptext =
\\zigverm - A version manager for Zig
\\
Expand Down Expand Up @@ -66,8 +68,10 @@ pub const Cli = union(enum) {
const trycmd = arg_iter.next();
if (trycmd) |c|
cmd = c
else
incorrectUsage(null);
else {
utils.printStdOut("{s}\n\n{s}\n", .{ message_for_empty_args, helptext });
std.process.exit(0);
}
}

if (streql(cmd, "install")) {
Expand Down
110 changes: 110 additions & 0 deletions src/common/cache.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const std = @import("std");
const builtin = @import("builtin");
// Cache requires a function (lambda) to be called when there's no cache hit and an other function(args_to_u64) that can compute args
// of the function to u64 that becomes the identifier to compare same arguments.

pub fn Cache(lambda: anytype, args_to_u64: fn (anytype) u64) type {
const lambda_info = @typeInfo(@TypeOf(lambda));
if (lambda_info != .Fn) {
@compileError("lambda should be a function type");
}
const return_type = lambda_info.Fn.return_type orelse @compileError("No return type");
const return_type_info = @typeInfo(return_type);
const return_error_union_type = if (return_type_info == .ErrorUnion) blk: {
break :blk .{ return_type_info.ErrorUnion.error_set || std.mem.Allocator.Error, return_type_info.ErrorUnion.payload };
} else blk: {
break :blk .{ std.mem.Allocator.Error, return_type };
};
// @compileLog(@typeName(return_type));
const InnerHashMap = std.HashMap(u64, return_type, struct {
pub fn hash(_: @This(), key: u64) u64 {
return key;
}
pub fn eql(_: @This(), a: u64, b: u64) bool {
return a == b;
}
}, 80);
return struct {
_inner: InnerHashMap,
const Self = @This();
pub fn init(allocator: std.mem.Allocator) Self {
return Self{ ._inner = InnerHashMap.init(allocator) };
}
pub fn deinit(self: *Self) void {
self._inner.deinit();
}

pub fn get(self: *Self, args: anytype) return_error_union_type[0]!struct { bool, return_error_union_type[1] } {
const key = args_to_u64(args);
if (self._inner.get(key)) |value| {
var payload: return_error_union_type[1] = undefined;
if (comptime return_type_info == .ErrorUnion) {
payload = try value;
} else {
payload = value;
}
return .{ true, payload };
} else {
const value = @call(.auto, lambda, args);
try self._inner.put(key, value);
var payload: return_error_union_type[1] = undefined;
if (comptime return_type_info == .ErrorUnion) {
payload = try value;
} else {
payload = value;
}
return .{ false, payload };
}
}
};
}

//// Test
// Below tests to cache addition of two numbers
fn _add(a: u64, b: u64) u64 {
return a + b;
}

const HashAdd = struct {
var allocator: std.mem.Allocator = undefined;
pub fn hash_add(args: anytype) u64 {
const temp = std.fmt.allocPrint(allocator, "{}:{}", .{ args[0], args[1] }) catch unreachable;
defer allocator.free(temp);
return std.hash_map.hashString(temp);
}
};

test "test_cache" {
const allocator = std.testing.allocator;
HashAdd.allocator = allocator;
var cache = Cache(_add, HashAdd.hash_add).init(allocator);
var is_Cache_hit: bool, var value: u64 = try cache.get(.{ 1, 2 });
try std.testing.expectEqual(false, is_Cache_hit);
try std.testing.expectEqual(3, value);
is_Cache_hit, value = try cache.get(.{ 1, 2 });
try std.testing.expectEqual(true, is_Cache_hit);
try std.testing.expectEqual(3, value);
cache._inner.deinit();
}

fn _add_error(a: u64, b: u64) error{Got5}!u64 {
if (a + b == 5) {
return error.Got5;
}
return a + b;
}

test "test_add_error" {
const allocator = std.testing.allocator;
HashAdd.allocator = allocator;
var cache = Cache(_add_error, HashAdd.hash_add).init(allocator);
var is_Cache_hit: bool, var value: u64 = try cache.get(.{ 1, 2 });
try std.testing.expectEqual(false, is_Cache_hit);
try std.testing.expectEqual(3, value);
is_Cache_hit, value = try cache.get(.{ 1, 2 });
try std.testing.expectEqual(true, is_Cache_hit);
try std.testing.expectEqual(3, value);

try std.testing.expectError(error.Got5, cache.get(.{ 1, 4 }));
cache._inner.deinit();
}
5 changes: 5 additions & 0 deletions src/common/error.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pub const RelError = error{
InvalidVersionSpec,
Overflow,
OutOfMemory,
};
63 changes: 63 additions & 0 deletions src/common/mach.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const std = @import("std");
const RelError = @import("error.zig").RelError;

pub const MachVersion = union(enum) {
latest,
calver: []const u8,
semantic: []const u8,

const Self = @This();
pub const IndexData = struct { version: []const u8, date: []const u8, docs: []const u8, stdDocs: []const u8, machDocs: []const u8, machNominated: []const u8, src: struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, bootstrap: struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"x86_64-macos": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"aarch64-macos": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"x86_64-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"aarch64-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"armv7a-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"riscv64-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"powerpc64le-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"x86-linux": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"x86_64-windows": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"aarch64-windows": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 }, @"x86-windows": struct { shasum: []const u8, size: u32, tarball: []const u8, zigTarball: []const u8 } };
const mach_latest_prefix = "mach-latest";
const mach_suffix = "-mach";

pub fn parse_str(version: []const u8) RelError!Self {
if (std.mem.eql(u8, version, mach_latest_prefix)) return Self.latest;

if (!std.mem.endsWith(u8, version, mach_suffix)) return RelError.InvalidVersionSpec;
var first_half_iterator = std.mem.tokenizeSequence(u8, version, mach_suffix);
const prefix = first_half_iterator.next() orelse return RelError.InvalidVersionSpec;

// For calver version
if (is_calver(prefix)) return Self{ .calver = version };
if (is_semantic(prefix)) return Self{ .semantic = version };
return RelError.InvalidVersionSpec;
}
};
fn is_calver(version: []const u8) bool {
var version_iterator = std.mem.tokenizeScalar(u8, version, '.');
inline for (0..3) |_| {
const year = version_iterator.next() orelse return false;
_ = std.fmt.parseInt(u16, year, 10) catch return false;
}
return true;
}
fn is_semantic(version: []const u8) bool {
_ = std.SemanticVersion.parse(version) catch return false;
return true;
}

test "mach_versioning" {
//---- Mach Versions
//mach-latest test
const mach_latest = try MachVersion.parse_str("mach-latest");
try std.testing.expectEqual(mach_latest, MachVersion.latest);

//2024.11.0-mach
const mach_calver = try MachVersion.parse_str("2024.11.0-mach");
try std.testing.expectEqual(mach_calver, MachVersion{ .calver = "2024.11.0-mach" });

//0.4.0-mach
const mach_semantic = try MachVersion.parse_str("0.4.0-mach");
try std.testing.expectEqual(mach_semantic, MachVersion{ .calver = "0.4.0-mach" });

//---- Mach Illformed versions
const ma = MachVersion.parse_str("0.4.0-ma");
try std.testing.expectError(RelError.InvalidVersionSpec, ma);

const semver = MachVersion.parse_str("0.4.0");
try std.testing.expectError(RelError.InvalidVersionSpec, semver);

const mach_semver_mach = MachVersion.parse_str("mach.0.4.0-mach");
try std.testing.expectError(RelError.InvalidVersionSpec, mach_semver_mach);
}
64 changes: 34 additions & 30 deletions src/common/root.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ pub const overrides = @import("overrides.zig");
const Allocator = std.mem.Allocator;
const json = std.json;
const builtin = @import("builtin");

pub const MachVersion = @import("mach.zig").MachVersion;
const RelError = @import("error.zig").RelError;
pub const Cache = @import("cache.zig").Cache;
pub const default_os = builtin.target.os.tag;
pub const default_arch = builtin.target.cpu.arch;

const RelError = error{
InvalidVersionSpec,
Overflow,
OutOfMemory,
};

pub const ReleaseSpec = union(enum) { Master, Stable, MajorMinorVersionSpec: []const u8, FullVersionSpec: []const u8 };
pub const ReleaseSpec = union(enum) { Master, Stable, MajorMinorVersionSpec: []const u8, FullVersionSpec: []const u8, MachVersion: MachVersion };

pub const Release = struct {
spec: ReleaseSpec,
Expand All @@ -26,12 +22,15 @@ pub const Release = struct {
switch (self.spec) {
ReleaseSpec.Master => return try alloc.dupe(u8, "master"),
ReleaseSpec.FullVersionSpec => |v| return try alloc.dupe(u8, v),
ReleaseSpec.MachVersion => return try alloc.dupe(u8, self.releaseName()),
else => {
if (self.actual_version == null) @panic("actual_version() called without resolving");
var buffer = std.ArrayList(u8).init(alloc);
defer buffer.deinit();
try self.actual_version.?.format("", .{}, buffer.writer());
return try alloc.dupe(u8, buffer.items);
if (self.actual_version) |actual_version| {
var buffer = std.ArrayList(u8).init(alloc);
defer buffer.deinit();
try actual_version.format("", .{}, buffer.writer());
return try alloc.dupe(u8, buffer.items);
}
@panic("actual_version() called without resolving");
},
}
}
Expand All @@ -42,6 +41,13 @@ pub const Release = struct {
ReleaseSpec.MajorMinorVersionSpec => |v| return v,
ReleaseSpec.FullVersionSpec => |v| return v,
ReleaseSpec.Stable => return "stable",
ReleaseSpec.MachVersion => |v| {
return switch (v) {
MachVersion.latest => "mach-latest",
MachVersion.calver => |mach_v| return mach_v,
MachVersion.semantic => |mach_v| return mach_v,
};
},
}
}

Expand All @@ -51,6 +57,9 @@ pub const Release = struct {
self.actual_version = std.SemanticVersion.parse(releases.object.get("master").?.object.get("version").?.string) catch unreachable;
return;
}
if (self.spec == .MachVersion) {
return;
}

var base_spec: std.SemanticVersion = undefined;
if (self.spec == .Stable) {
Expand All @@ -76,24 +85,19 @@ pub const Release = struct {
}

pub fn releaseFromVersion(v: []const u8) RelError!Self {
var rel: Release = undefined;
if (streql(v, "master"))
rel = Self{ .spec = .Master }
else if (streql(v, "stable"))
rel = Self{ .spec = .Stable }
else b: {
const count = std.mem.count(u8, v, ".");
if (count == 2) {
rel = Self{ .spec = ReleaseSpec{ .FullVersionSpec = v }, .actual_version = std.SemanticVersion.parse(v) catch return RelError.InvalidVersionSpec };
break :b;
}

const is_valid_version = completeSpec(v);
if (is_valid_version) |_| {
rel = Self{ .spec = ReleaseSpec{ .MajorMinorVersionSpec = v } };
} else |_| _ = try is_valid_version;
if (streql(v, "master")) return Self{ .spec = .Master };
if (streql(v, "stable")) return Self{ .spec = .Stable };
if (MachVersion.parse_str(v)) |mach| {
return Self{ .spec = ReleaseSpec{ .MachVersion = mach } };
} else |_| {}
const count = std.mem.count(u8, v, ".");
if (count == 2) {
return Self{ .spec = ReleaseSpec{ .FullVersionSpec = v }, .actual_version = std.SemanticVersion.parse(v) catch return RelError.InvalidVersionSpec };
}
return rel;
if (completeSpec(v)) |_| {
return Self{ .spec = ReleaseSpec{ .MajorMinorVersionSpec = v } };
} else |_| {}
return RelError.InvalidVersionSpec;
}

pub inline fn completeSpec(spec: []const u8) RelError!std.SemanticVersion {
Expand Down
29 changes: 22 additions & 7 deletions src/install.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const Client = std.http.Client;
const json = std.json;
const ReleaseSpec = common.ReleaseSpec;
const Release = common.Release;
const paths = common.paths;
const CommonPaths = paths.CommonPaths;
Expand All @@ -18,7 +19,7 @@ const time = std.time;
const default_os = builtin.target.os.tag;
const default_arch = builtin.target.cpu.arch;

const JsonResponse = struct {
pub const JsonResponse = struct {
body: [100 * 1024]u8,
length: usize,
};
Expand All @@ -31,10 +32,15 @@ const InstallError = error{

pub fn install_release(alloc: Allocator, client: *Client, releases: json.Value, rel: *Release, cp: CommonPaths) !void {
try rel.resolve(releases);

const release: json.Value = releases.object.get(try rel.actualVersion(alloc)).?;
var tarball_identifier: []const u8 = undefined;
if (rel.spec == .MachVersion) {
tarball_identifier = "zigTarball";
} else {
tarball_identifier = "tarball";
}
const release: json.Value = releases.object.get(try rel.actualVersion(alloc)) orelse return InstallError.ReleaseNotFound;
const target = release.object.get(target_name()) orelse return InstallError.TargetNotAvailable;
const tarball_url = target.object.get("tarball").?.string;
const tarball_url = target.object.get(tarball_identifier).?.string;
const shasum = target.object.get("shasum").?.string[0..64];
const total_size = try std.fmt.parseInt(usize, target.object.get("size").?.string, 10);

Expand Down Expand Up @@ -162,14 +168,23 @@ pub fn download_progress_bar(dlnow: *std.atomic.Value(f32), tarball_size: f64, t
try stderr_writer.print("\n", .{});
}

pub fn get_json_dslist(client: *Client) anyerror!JsonResponse {
pub fn get_index_data(client: *Client, release: Release, allocator: std.mem.Allocator) anyerror!std.json.Value {
const resp = try get_json_dslist(client, release);
return try json.parseFromSliceLeaky(json.Value, allocator, resp.body[0..resp.length], .{});
}

pub fn get_json_dslist(client: *Client, release: Release) anyerror!JsonResponse {
std.log.info("Fetching the latest index", .{});
const uri = try std.Uri.parse("https://ziglang.org/download/index.json");

const url: []const u8 = switch (release.spec) {
ReleaseSpec.MachVersion => "https://machengine.org/zig/index.json",
else => "https://ziglang.org/download/index.json",
};
const uri = try std.Uri.parse(url);
var req = make_request(client, uri);
defer req.?.deinit();
if (req == null) {
std.log.err("Failed fetching the index. Exitting (1)...", .{});
std.log.err("Failed fetching the index from url: {s}. Exitting (1)...", .{url});
std.process.exit(1);
}

Expand Down
Loading
Loading