Compare commits

...

13 commits

Author SHA1 Message Date
12c921d58c
ci: woodpecker -> forgejo actions
All checks were successful
/ release (push) Successful in 1m12s
/ test (push) Successful in 33s
2024-05-10 20:44:36 +02:00
02fd112b65
chore: update to Zig 0.12.0 and bump version to 1.0.0
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-04-28 19:00:30 +02:00
ed72799023
chore: format code
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2024-03-15 21:59:26 +01:00
412287cb40
chore: update Zig & deps
Some checks failed
ci/woodpecker/manual/woodpecker Pipeline failed
2024-02-09 19:19:09 +01:00
3ac0d0f544
chore: update to new Zig API 2023-10-11 14:52:44 +02:00
2a4e7d0f48
docs: be more clear about zig version required
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 23:27:09 +02:00
29a8f8df03
feat: allow installing exact versions
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-09-26 19:26:03 +02:00
36a08d533d
ci: pull zig image
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-16 21:59:32 +02:00
7c805820f8
deps: update getty and getty-json
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-16 20:52:07 +02:00
11f1939fe9
ci: check formatting
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-06 19:00:47 +02:00
593aee055a
deps: update getty
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-01 22:49:08 +02:00
e3e56e8804
ci: install certs
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-01 22:42:42 +02:00
e65e0136bd
ci: update CI config
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-08-01 22:40:19 +02:00
17 changed files with 180 additions and 197 deletions

View file

@ -0,0 +1,38 @@
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: docker-x86_64
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Zig
uses: https://github.com/goto-bus-stop/setup-zig@v2
with:
version: 0.12.0
- name: Setup Packages
run: |
apt update
apt install -y ca-certificates zip libarchive-dev libarchive13
- name: Build
run: |
zig build -Doptimize=ReleaseFast
- name: Zip
run: |
mkdir release
cd zig-out
zip -r ../release/zupper-linux-x86_64.zip .
- name: Upload
uses: actions/forgejo-release@v1
with:
direction: upload
release-dir: release
token: ${{ env.GITHUB_TOKEN }}

View file

@ -0,0 +1,22 @@
on: [push]
jobs:
test:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Zig
uses: https://github.com/goto-bus-stop/setup-zig@v2
with:
version: 0.12.0
- name: Setup Packages
run: |
apt update
apt install -y ca-certificates libarchive-dev libarchive13
- name: Test
run: |
zig fmt --check .

View file

@ -1,24 +0,0 @@
pipeline:
build:
image: debian # not using alpine so we get a glibc build
commands:
- apt update
- apt install -y curl jq xz-utils libarchive-dev libarchive13
# Install Zig
- curl $(curl https://ziglang.org/download/index.json | jq -r '.master."x86_64-linux".tarball') -o /tmp/zig.tar.xz
- tar xf /tmp/zig.tar.xz --directory=/tmp
- export PATH="$(echo /tmp/zig-*):$PATH"
- zig build -Doptimize=ReleaseFast
publish:
image: woodpeckerci/plugin-gitea-release
settings:
base_url: https://mzte.de/git
api_key:
from_secret: forgejo_key
title: tag-${CI_COMMIT_TAG}
files: zig-out/bin/zupper
when:
event: tag

View file

@ -2,8 +2,6 @@
A Zig version manager, written in zig.
WARNING: zupper is alpha software! Expect bugs!
## features
- managing multiple zig installations
@ -46,7 +44,8 @@ apt install \
2. Install the zig toolchain
Zupper requires the development version of Zig (0.11.0). Make sure you install one of the latest versions of Zig.
Zupper requires the 0.12.0 version of Zig. You can get a compiler from the
[Zig website](https://ziglang.org), or your distribution's package manager.
3. Build it!

View file

@ -4,18 +4,27 @@ pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
//const opts = b.addOptions();
//opts.addOption([]const u8, "version", ?);
const args_dep = b.dependency("args", .{ .target = target, .optimize = optimize });
const known_folders_dep = b.dependency("known_folders", .{});
const ansi_term_dep = b.dependency("ansi_term", .{ .target = target, .optimize = optimize });
const getty_dep = b.dependency("getty", .{ .target = target, .optimize = optimize });
const getty_json_dep = b.dependency("getty_json", .{ .target = target, .optimize = optimize });
const exe = b.addExecutable(.{
.name = "zupper",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
.link_libc = true,
});
exe.strip = optimize != .Debug and optimize != .ReleaseSafe;
addDeps(b, exe);
exe.root_module.linkSystemLibrary("archive", .{});
exe.root_module.addImport("zig-args", args_dep.module("args"));
exe.root_module.addImport("known-folders", known_folders_dep.module("known-folders"));
exe.root_module.addImport("ansi-term", ansi_term_dep.module("ansi-term"));
exe.root_module.addImport("getty", getty_dep.module("getty"));
exe.root_module.addImport("json", getty_json_dep.module("json"));
b.installArtifact(exe);
@ -28,32 +37,3 @@ pub fn build(b: *std.Build) !void {
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}
fn addDeps(b: *std.Build, exe: *std.Build.CompileStep) void {
const module_opts = .{ .target = exe.target, .optimize = exe.optimize };
exe.addModule(
"zig-args",
b.dependency("args", module_opts).module("args"),
);
exe.addModule(
"known-folders",
b.dependency("known_folders", .{}).module("known-folders"),
);
exe.addModule(
"ansi-term",
b.dependency("ansi_term", module_opts).module("ansi-term"),
);
exe.addModule(
"getty",
b.dependency("getty", module_opts).module("getty"),
);
exe.addModule(
"json",
b.dependency("getty_json", module_opts).module("json"),
);
exe.linkLibC();
// used to unpack downloaded zig packages
exe.linkSystemLibrary("archive");
}

View file

@ -1,27 +1,29 @@
.{
.name = "zupper",
.version = "0.3.0",
.version = "1.0.0",
.paths = .{""},
.dependencies = .{
.args = .{
.url = "https://mzte.de/git/mirrors/zig-args/archive/91d1e89fb89a4d01dec7c9aec95b0a324080ebcc.tar.gz",
.hash = "12203d04cafc97f952d74cdb077e74c0ab3414f9f6b5fbd159112c62bfa584a0dbed",
.url = "git+https://git.mzte.de/mirrors/zig-args.git#89f18a104d9c13763b90e97d6b4ce133da8a3e2b",
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
},
.known_folders = .{
.url = "https://github.com/ziglibs/known-folders/archive/fa75e1bc672952efa0cf06160bbd942b47f6d59b.tar.gz",
.hash = "122048992ca58a78318b6eba4f65c692564be5af3b30fbef50cd4abeda981b2e7fa5",
.url = "git+https://github.com/ziglibs/known-folders.git#2aa7f2e9855d45b20072e15107fb379b9380adbe",
.hash = "12209925016f4b5486a713828ead3bcc900fa4f039c93de1894aa7d5253f7633b92c",
},
.ansi_term = .{
.url = "https://github.com/ziglibs/ansi-term/archive/1614b61486d567b59abe11a097d11aa6ce679819.tar.gz",
.hash = "1220647eea49d2c48d5e59354291e975f813be3cc5a9d9920a50bbfaa40a891a06ee",
.url = "git+https://github.com/LordMZTE/ansi-term.git#73c03175068679685535111dbea72cade075719e",
.hash = "1220ea86ace34b38e49c1d737c5f857d88346af10695a992b38e10cb0a73b6a19ef7",
},
.getty = .{
.url = "https://github.com/getty-zig/getty/archive/6bdd2307748525ba98f5538234cf4768074e8a18.tar.gz",
.hash = "122032aa72f6a4f58d4e0b7c381dbb855423189fa94e96b290c9bdfbe6e2752045c7",
.url = "https://github.com/getty-zig/getty/archive/d27b9c72c553ee7e9fb383aa955ae1a705cde215.tar.gz",
.hash = "12208cf585a316dddfb837fb7397b0cc4a9974716adf63f856171fbd6e99cb9b131d",
},
.getty_json = .{
.url = "https://github.com/getty-zig/json/archive/3e3cf7bbc9894fa58978f3615524cdbf635aa6b7.tar.gz",
.hash = "122017ccb426b5f5690fdda438134852e940796b0ac619eb2648782a7879146f4fcd",
.url = "git+https://github.com/getty-zig/json.git#11946ff9d2f159cb06aaf423ce13bd8aa2a481e7",
.hash = "1220829a91cb0804b35dadb6eb453cd9694b16d624e02493dc712ad43fd8681095f1",
},
},
}

View file

@ -3,8 +3,12 @@ const dirs = @import("dirs.zig");
const json = @import("json");
const getty = @import("getty");
versions: std.StringHashMap(std.SemanticVersion),
in_use: ?[]u8 = null,
const Data = struct {
versions: std.StringHashMap(std.SemanticVersion),
in_use: ?[]u8 = null,
};
data: getty.de.Result(Data),
const Self = @This();
@ -16,7 +20,8 @@ pub fn path(alloc: std.mem.Allocator) ![]const u8 {
}
pub fn deinit(self: *Self) void {
json.de.free(self.versions.allocator, self.*, null);
self.data.deinit();
self.* = undefined;
}
@ -39,7 +44,7 @@ pub fn load(alloc: std.mem.Allocator) !Self {
const data = try f.readToEndAlloc(alloc, std.math.maxInt(usize));
defer alloc.free(data);
const self = json.fromSlice(alloc, Self, data) catch |e| {
const deser = json.fromSlice(alloc, Data, data) catch |e| {
std.log.err(
\\failed to deserialize manifest! someone messed with my files! (how dare you!): {s}
\\try deleting '{s}'
@ -49,24 +54,23 @@ pub fn load(alloc: std.mem.Allocator) !Self {
return error.Explained;
};
return self;
return .{ .data = deser };
} else {
return .{
.versions = std.StringHashMap(std.SemanticVersion).init(alloc),
};
var arena = try alloc.create(std.heap.ArenaAllocator);
arena.* = std.heap.ArenaAllocator.init(alloc);
return .{ .data = .{
.value = .{
.versions = std.StringHashMap(std.SemanticVersion).init(arena.allocator()),
},
.arena = arena,
} };
}
}
pub fn setVersion(self: *Self, k: []const u8, v: ?[]const u8) !void {
const k_d = try self.versions.allocator.dupe(u8, k);
errdefer self.versions.allocator.free(k_d);
const k_d = try self.data.arena.allocator().dupe(u8, k);
const gopr = try self.versions.getOrPut(k_d);
if (gopr.found_existing) {
self.versions.allocator.free(k_d);
json.de.free(self.versions.allocator, gopr.value_ptr.*, null);
}
const gopr = try self.data.value.versions.getOrPut(k_d);
// this is the actual version. this is used in the manifest
// to keep the version of master up to date.
@ -76,39 +80,29 @@ pub fn setVersion(self: *Self, k: []const u8, v: ?[]const u8) !void {
};
// we move the strings in the semver to the manifest allocator so getty can free them
if (semver.pre) |x| semver.pre = try self.versions.allocator.dupe(u8, x);
errdefer if (semver.pre) |x| self.versions.allocator.free(x);
if (semver.build) |x| semver.build = try self.versions.allocator.dupe(u8, x);
errdefer if (semver.build) |x| self.versions.allocator.free(x);
if (semver.pre) |x| semver.pre = try self.data.arena.allocator().dupe(u8, x);
if (semver.build) |x| semver.build = try self.data.arena.allocator().dupe(u8, x);
gopr.value_ptr.* = semver;
}
pub fn removeVersion(self: *Self, version: []const u8) void {
if (self.versions.fetchRemove(version)) |kv| {
json.de.free(self.versions.allocator, kv.key, null);
json.de.free(self.versions.allocator, kv.value, null);
}
_ = self.data.value.versions.remove(version);
}
pub fn setInUse(self: *Self, in_use: ?[]const u8) !void {
if (self.in_use) |old| {
self.versions.allocator.free(old);
}
if (in_use) |v| {
const dup = try self.versions.allocator.dupe(u8, v);
self.in_use = dup;
const dup = try self.data.arena.allocator().dupe(u8, v);
self.data.value.in_use = dup;
}
}
pub fn save(self: *Self) !void {
const mpath = try path(self.versions.allocator);
defer self.versions.allocator.free(mpath);
const mpath = try path(self.data.arena.child_allocator);
defer self.data.arena.child_allocator.free(mpath);
var outfile = try std.fs.cwd().createFile(mpath, .{});
defer outfile.close();
try json.toWriter(self.versions.allocator, self, outfile.writer());
try json.toWriter(self.data.arena.child_allocator, self.data.value, outfile.writer());
}

View file

@ -1,6 +1,6 @@
const std = @import("std");
pub const version = "0.3.0";
pub const version = "1.0.0";
pub const Info = struct {};

View file

@ -25,12 +25,12 @@ pub fn run(
.{ args.version, @tagName(@import("builtin").os.tag) },
);
if (manifest.versions.count() == 0) {
if (manifest.data.value.versions.count() == 0) {
try stdout.writeAll("no installed versions\n");
} else {
try stdout.writeAll("installed versions:\n");
var iter = manifest.versions.iterator();
var iter = manifest.data.value.versions.iterator();
while (iter.next()) |kv| {
try stdout.print("\t{s}\t{}\n", .{ kv.key_ptr.*, kv.value_ptr.* });
}

View file

@ -44,16 +44,43 @@ pub fn run(
defer version_index.deinit();
for (positionals) |version_arg| {
const version = version_index.data.get(version_arg) orelse {
std.log.err("no such version available!", .{});
var exactv_buf: [1024]u8 = undefined;
const version = version_index.value.get(version_arg) orelse exactv: {
std.log.warn("no such version in index! trying exact version...", .{});
std.log.info("available versions: ", .{});
var kiter = version_index.data.keyIterator();
while (kiter.next()) |v| {
std.log.info("\t{s}", .{v.*});
const exactv_url = try std.fmt.bufPrint(
&exactv_buf,
"https://ziglang.org/builds/zig-{s}-{s}-{s}.tar.xz",
.{
@tagName(@import("builtin").os.tag),
@tagName(@import("builtin").target.cpu.arch),
version_arg,
},
);
std.log.info("exact version URL: '{s}'", .{exactv_url});
const exactv_result = http.fetch(.{
.location = .{ .url = exactv_url },
.method = .HEAD,
}) catch |e| {
std.log.err("checking exact version: {}", .{e});
return error.Explained;
};
if (exactv_result.status.class() != .success) {
std.log.info("no such exact version available ({} {s})! available versions: ", .{
@intFromEnum(exactv_result.status),
exactv_result.status.phrase() orelse "<unknown>",
});
var kiter = version_index.value.keyIterator();
while (kiter.next()) |v| {
std.log.info("\t{s}", .{v.*});
}
return error.Explained;
}
return error.Explained;
break :exactv net.Version{ .file = .{ .tarball = exactv_url } };
};
const version_file = version.file orelse {

View file

@ -33,7 +33,7 @@ pub fn run(
manifest.removeVersion(version);
try std.fs.cwd().deleteTree(ver_path);
if (manifest.in_use != null and std.mem.eql(u8, manifest.in_use.?, version)) {
if (manifest.data.value.in_use != null and std.mem.eql(u8, manifest.data.value.in_use.?, version)) {
try dirs.unuseCurrentVersion(alloc, manifest);
}
}

View file

@ -16,7 +16,7 @@ pub fn run(
return error.Explained;
}
if (manifest.in_use == null) {
if (manifest.data.value.in_use == null) {
std.log.err("no version currently in use!", .{});
return error.Explained;
}

View file

@ -26,7 +26,7 @@ pub fn run(
var updated_any = false;
var manifest_version_iter = manifest.versions.iterator();
var manifest_version_iter = manifest.data.value.versions.iterator();
while (manifest_version_iter.next()) |version| {
// if the user provided arguments, only update those versions
if (positionals.len != 0) {
@ -42,7 +42,7 @@ pub fn run(
continue;
}
const idx_version = version_index.data.get(version.key_ptr.*) orelse {
const idx_version = version_index.value.get(version.key_ptr.*) orelse {
std.log.warn(
"the installed version '{s}' is not in the version index",
.{version.key_ptr.*},
@ -95,22 +95,10 @@ pub fn run(
try net.downloadVersionTo(&http, version_file.tarball, install_path);
json.de.free(
manifest.versions.allocator,
version.value_ptr.*,
null,
);
// move the strings to the manifest allocator so it
// complies with what getty.de.free expects
//
// we don't use manifest.setVersion here because that'd be kinda dodgy as we're
// iterating the map here
if (idx_semver.pre) |x| idx_semver.pre = try manifest.versions.allocator.dupe(u8, x);
errdefer if (idx_semver.pre) |x| manifest.versions.allocator.free(x);
if (idx_semver.build) |x| idx_semver.build = try manifest.versions.allocator.dupe(u8, x);
errdefer if (idx_semver.build) |x| manifest.versions.allocator.free(x);
if (idx_semver.pre) |x| idx_semver.pre = try manifest.data.arena.allocator().dupe(u8, x);
if (idx_semver.build) |x| idx_semver.build = try manifest.data.arena.allocator().dupe(u8, x);
version.value_ptr.* = idx_semver;
}

View file

@ -50,7 +50,7 @@ pub fn useVersion(
home: []const u8,
version: []const u8,
) !void {
if (manifest.in_use != null and std.mem.eql(u8, manifest.in_use.?, version)) {
if (manifest.data.value.in_use != null and std.mem.eql(u8, manifest.data.value.in_use.?, version)) {
std.log.err("version '{s}' is already in use!", .{version});
return error.Explained;
}
@ -133,9 +133,5 @@ pub fn unuseCurrentVersion(
std.log.info("unusing zig installation @ {s}", .{target_binary});
try std.fs.cwd().deleteFile(target_binary);
if (manifest.in_use) |old| {
manifest.versions.allocator.free(old);
}
manifest.in_use = null;
manifest.data.value.in_use = null;
}

View file

@ -3,9 +3,9 @@ const zig_args = @import("zig-args");
const args = @import("args.zig");
const Manifest = @import("Manifest.zig");
pub const std_options = struct {
pub const logFn = @import("log.zig").log;
pub const log_level = .debug;
pub const std_options = std.Options{
.log_level = .debug,
.logFn = @import("log.zig").log,
};
pub fn main() !u8 {

View file

@ -3,11 +3,10 @@ const c = @import("ffi.zig").c;
const ansiterm = @import("ansi-term");
const getty = @import("getty");
const json = @import("json");
const OwnedDeserData = @import("owned_deser_data.zig").OwnedDeserData;
const Manifest = @import("Manifest.zig");
pub const VersionFile = struct {
tarball: [:0]const u8,
tarball: []const u8,
pub const @"getty.db" = struct {
pub const attributes = .{
@ -38,23 +37,23 @@ pub const Version = struct {
pub const VersionMap = std.StringHashMap(Version);
pub fn getVersionIndex(http: *std.http.Client) !OwnedDeserData(VersionMap) {
pub fn getVersionIndex(http: *std.http.Client) !getty.de.Result(VersionMap) {
const alloc = http.allocator;
std.log.info("getting version index", .{});
var req = try http.request(
var header_buf: [1024 * 4]u8 = undefined;
var req = try http.open(
.GET,
comptime std.Uri.parse("https://ziglang.org/download/index.json") catch unreachable,
std.http.Headers.init(alloc), // freed by req.deinit
.{},
.{ .server_header_buffer = &header_buf },
);
defer req.deinit();
try req.start();
try req.finish();
try req.send();
try req.wait();
return try OwnedDeserData(VersionMap).fromJsonReader(alloc, req.reader());
return try json.fromReader(alloc, VersionMap, req.reader());
}
/// concurrently downloads and unpacks a version to the given path
@ -64,8 +63,6 @@ pub fn downloadVersionTo(
url_str: []const u8,
target: []const u8,
) !void {
const alloc = http.allocator;
const url = try std.Uri.parse(url_str);
std.log.info(
@ -76,11 +73,16 @@ pub fn downloadVersionTo(
.{ url_str, target },
);
var req = try http.request(.GET, url, std.http.Headers.init(alloc), .{});
var header_buf: [1024 * 4]u8 = undefined;
var req = try http.open(
.GET,
url,
.{ .server_header_buffer = &header_buf },
);
defer req.deinit();
try req.start();
try req.finish();
try req.send();
try req.wait();
const DLState = struct {

View file

@ -1,41 +0,0 @@
const std = @import("std");
const getty = @import("getty");
const json = @import("json");
/// a super handy data type that wraps some data deserialized using getty-json with
/// an arena allocator for memory management.
pub fn OwnedDeserData(comptime T: type) type {
return struct {
data: T,
arena: std.heap.ArenaAllocator,
const Self = @This();
pub fn deinit(self: *Self) void {
self.arena.deinit();
self.* = undefined;
}
pub fn fromJsonReader(alloc: std.mem.Allocator, reader: anytype) !Self {
// we use an arena here, even though getty does provide it's own deinit function for
// deserialized data, I've never observed that actually clean everything up :P
var arena = std.heap.ArenaAllocator.init(alloc);
errdefer arena.deinit();
var deser = json.Deserializer(null, @TypeOf(reader)).init(alloc, reader);
defer deser.deinit();
const data = try getty.deserialize(arena.allocator(), T, deser.deserializer());
return .{
.data = data,
.arena = arena,
};
}
pub fn fromJson(alloc: std.mem.Allocator, json_data: []const u8) !Self {
var fbs = std.io.fixedBufferStream(json_data);
return try fromJsonReader(alloc, fbs.reader());
}
};
}