summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Uko Kokņevičs2022-04-25 05:09:55 +0300
committerGravatar Uko Kokņevičs2022-04-25 23:34:05 +0300
commitd303b53f2ced75703bf022a5d337ee3ba530b288 (patch)
treef2e64057120d01ee8a821596ea01b8fc37c53c2c /src
downloadzup-d303b53f2ced75703bf022a5d337ee3ba530b288.tar.gz
zup-d303b53f2ced75703bf022a5d337ee3ba530b288.tar.xz
zup-d303b53f2ced75703bf022a5d337ee3ba530b288.zip
Initial commit0.1.0
Diffstat (limited to 'src')
-rw-r--r--src/Installation.zig196
-rw-r--r--src/install.zig101
-rw-r--r--src/list.zig103
-rw-r--r--src/main.zig155
-rw-r--r--src/remove.zig50
-rw-r--r--src/subcommand.zig70
-rw-r--r--src/switch.zig38
-rw-r--r--src/update.zig34
8 files changed, 747 insertions, 0 deletions
diff --git a/src/Installation.zig b/src/Installation.zig
new file mode 100644
index 0000000..3b0382a
--- /dev/null
+++ b/src/Installation.zig
@@ -0,0 +1,196 @@
1const builtin = @import("builtin");
2const curl = @import("curl");
3const std = @import("std");
4const xdg = @import("xdg");
5
6const Allocator = std.mem.Allocator;
7const ChildProcess = std.ChildProcess;
8const JsonValue = std.json.Value;
9const SemanticVersion = std.SemanticVersion;
10const StringHashMap = std.StringHashMap;
11
12const Installation = @This();
13
14pub const Installations = StringHashMap(Installation);
15
16allocator: Allocator,
17ver_str: []u8,
18version: SemanticVersion,
19url: ?[:0]u8,
20
21pub fn deinit(self: *Installation) void {
22 self.allocator.free(self.ver_str);
23 if (self.url) |url| self.allocator.free(url);
24
25 self.* = undefined;
26}
27
28pub fn deinitMap(allocator: Allocator, installations: *Installations) void {
29 var it = installations.iterator();
30 while (it.next()) |kv| {
31 allocator.free(kv.key_ptr.*);
32 kv.value_ptr.deinit();
33 }
34
35 installations.deinit();
36
37 installations.* = undefined;
38}
39
40pub fn getActiveName(allocator: Allocator) !?[]u8 {
41 var bin_home = try xdg.openBinHome(allocator);
42 defer bin_home.close();
43
44 var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
45 const linkname = bin_home.readLink("zig", &buf) catch |err| {
46 if (err == error.NotLink or err == error.FileNotFound) {
47 return null;
48 } else {
49 return err;
50 }
51 };
52
53 var linkpath = try bin_home.realpathAlloc(allocator, linkname);
54 defer allocator.free(linkpath);
55
56 var zup_dir = try xdg.getDataHome(allocator, "zup");
57 defer allocator.free(zup_dir);
58
59 const rel_path = try std.fs.path.relative(allocator, zup_dir, linkpath);
60 defer allocator.free(rel_path);
61
62 return try allocator.dupe(u8, std.fs.path.dirname(rel_path).?);
63}
64
65pub fn isInstalled(allocator: Allocator, name: []const u8) !bool {
66 var zup_data = try xdg.openDataHome(allocator, "zup");
67 defer zup_data.close();
68
69 zup_data.access(name, .{}) catch return false;
70 return true;
71}
72
73pub fn getInstalledList(allocator: Allocator) !Installations {
74 var zup_data = try xdg.openDataHome(allocator, "zup");
75 defer zup_data.close();
76
77 var installations = Installations.init(allocator);
78 errdefer Installation.deinitMap(allocator, &installations);
79
80 var it = zup_data.iterate();
81 while (try it.next()) |item| {
82 if (item.kind != .Directory) {
83 continue;
84 }
85
86 var inst_dir = try zup_data.openDir(item.name, .{});
87 defer inst_dir.close();
88
89 var zig_exe = inst_dir.realpathAlloc(allocator, "zig") catch |err| {
90 if (err == error.FileNotFound) {
91 continue;
92 }
93 return err;
94 };
95 defer allocator.free(zig_exe);
96
97 const res = try ChildProcess.exec(.{ .allocator = allocator, .argv = &.{ zig_exe, "version" } });
98 errdefer allocator.free(res.stdout);
99 allocator.free(res.stderr);
100
101 if (res.term != .Exited or res.term.Exited != 0) {
102 std.log.warn("Failed to execute '{s} version'", .{zig_exe});
103 allocator.free(res.stdout);
104 continue;
105 }
106
107 const trimmed_ver_str = std.mem.trim(u8, res.stdout, &std.ascii.spaces);
108 const version = try SemanticVersion.parse(trimmed_ver_str);
109 const name = try allocator.dupe(u8, item.name);
110
111 try installations.putNoClobber(name, Installation{
112 .allocator = allocator,
113 .ver_str = res.stdout,
114 .version = version,
115 .url = null,
116 });
117 }
118
119 return installations;
120}
121
122pub fn getAvailableList(allocator: Allocator) !Installations {
123 var json_str = try curl.easyDownload(allocator, "https://ziglang.org/download/index.json");
124 defer allocator.free(json_str);
125
126 var parser = std.json.Parser.init(allocator, false);
127 defer parser.deinit();
128
129 var vt = try parser.parse(json_str);
130 defer vt.deinit();
131
132 if (vt.root != .Object) {
133 return error.JsonSchema;
134 }
135
136 var installations = Installations.init(allocator);
137 errdefer Installation.deinitMap(allocator, &installations);
138
139 var it = vt.root.Object.iterator();
140 while (it.next()) |kv| {
141 if (try parseInstallation(allocator, kv.key_ptr.*, kv.value_ptr.*)) |*installation| {
142 errdefer installation.deinit();
143
144 var name = try allocator.dupe(u8, kv.key_ptr.*);
145 errdefer allocator.free(name);
146
147 try installations.putNoClobber(name, installation.*);
148 }
149 }
150
151 return installations;
152}
153
154fn parseInstallation(allocator: Allocator, name: []const u8, value: JsonValue) !?Installation {
155 if (value != .Object) {
156 return error.JsonSchema;
157 }
158 const map = value.Object;
159
160 const triple = @tagName(builtin.target.cpu.arch) ++ "-" ++ @tagName(builtin.target.os.tag);
161
162 const url_root = map.get(triple) orelse {
163 return null;
164 };
165 if (url_root != .Object) {
166 return error.JsonSchema;
167 }
168 const url_src = url_root.Object.get("tarball") orelse {
169 return error.JsonSchema;
170 };
171 if (url_src != .String) {
172 return error.JsonSchema;
173 }
174 var url = try allocator.dupeZ(u8, url_src.String);
175 errdefer allocator.free(url);
176
177 const version_src = if (map.get("version")) |ver| blk: {
178 if (ver != .String) {
179 return error.JsonSchema;
180 } else {
181 break :blk ver.String;
182 }
183 } else blk: {
184 break :blk name;
185 };
186 var ver_str = try allocator.dupe(u8, version_src);
187 errdefer allocator.free(ver_str);
188 const version = try SemanticVersion.parse(ver_str);
189
190 return Installation{
191 .allocator = allocator,
192 .ver_str = ver_str,
193 .version = version,
194 .url = url,
195 };
196}
diff --git a/src/install.zig b/src/install.zig
new file mode 100644
index 0000000..7a52d44
--- /dev/null
+++ b/src/install.zig
@@ -0,0 +1,101 @@
1const libarchive = @import("libarchive");
2const curl = @import("curl");
3const std = @import("std");
4const xdg = @import("xdg");
5const zup = @import("zup");
6
7const Allocator = std.mem.Allocator;
8const ArchiveRead = libarchive.Read;
9const Installation = zup.Installation;
10const Installations = zup.Installations;
11
12pub const params =
13 \\-f, --force Install over existing installations
14 \\<NAME>
15;
16pub const description = "Installs a Zig version. Run `zup list -i` to see installed <NAME>s.";
17pub const min_args = 1;
18pub const max_args = 1;
19
20pub fn main(comptime Result: type, allocator: Allocator, res: Result) !void {
21 var available = try Installation.getAvailableList(allocator);
22 defer Installation.deinitMap(allocator, &available);
23
24 return perform(allocator, res.positionals[0], res.args.force, available);
25}
26
27pub fn perform(allocator: Allocator, name: []const u8, force: bool, available: Installations) !void {
28 if (!force and try Installation.isInstalled(allocator, name)) {
29 std.log.err("{s} already installed, not overwriting without --force!", .{name});
30 return error.AlreadyInstalled;
31 }
32
33 const installation = available.get(name) orelse {
34 std.log.err("Installation by name {s} not available!", .{name});
35 return error.InstallationNotFound;
36 };
37
38 if (installation.url == null) {
39 std.log.err("No tarball URL for {s} found!", .{name});
40 }
41
42 const installation_dir = blk: {
43 const data_home = try xdg.getDataHome(allocator, "zup");
44 defer allocator.free(data_home);
45
46 break :blk try std.fs.path.join(allocator, &.{ data_home, name });
47 };
48 defer allocator.free(installation_dir);
49
50 std.log.info("Installing {s}, version {}", .{ name, installation.version });
51 const filename = std.fs.path.basename(installation.url.?);
52
53 // TODO: Platform-agnostic tempfile creation
54 var tmpname = try std.fmt.allocPrintZ(allocator, "/tmp/{s}", .{filename});
55 defer allocator.free(tmpname);
56
57 var tmpdir = try std.fs.openDirAbsolute(std.fs.path.dirname(tmpname).?, .{});
58 defer tmpdir.close();
59
60 var tmpfile = try tmpdir.createFile(filename, .{});
61 defer {
62 tmpfile.close();
63 std.log.info("Deleting /tmp/{s}...", .{filename});
64 tmpdir.deleteFile(filename) catch |err| {
65 std.log.warn("Failed to delete /tmp/{s}: {}", .{ filename, err });
66 };
67 }
68
69 std.log.info("Downloading to /tmp/{s}...", .{filename});
70 try curl.easyDownloadToFile(&tmpfile, installation.url.?);
71
72 std.log.info("Extracting...", .{});
73 var archive = try ArchiveRead.init();
74 defer archive.deinit();
75
76 try archive.supportFilter(.all);
77 try archive.supportFormat(.all);
78 try archive.openFilename(tmpname, 10240);
79
80 while (try archive.nextHeader()) |*entry| {
81 const source = entry.pathname();
82 const dest = try preparePathname(allocator, installation_dir, source);
83 defer allocator.free(dest);
84
85 entry.setPathname(dest);
86 try archive.extract(entry.*, 0);
87 }
88
89 std.log.info("Installed to {s}", .{installation_dir});
90 // TODO: Check if it is already active
91 std.log.info("If you want to use this installation, run `zup switch {s}`", .{name});
92}
93
94fn preparePathname(allocator: Allocator, installation_dir: []const u8, source: []const u8) ![:0]u8 {
95 const stripped = if (std.mem.indexOfScalar(u8, source, '/')) |idx|
96 source[idx + 1 ..]
97 else
98 "";
99
100 return std.fs.path.joinZ(allocator, &.{ installation_dir, stripped });
101}
diff --git a/src/list.zig b/src/list.zig
new file mode 100644
index 0000000..d97ba87
--- /dev/null
+++ b/src/list.zig
@@ -0,0 +1,103 @@
1const std = @import("std");
2const zup = @import("zup");
3
4const Allocator = std.mem.Allocator;
5const ArrayList = std.ArrayList;
6const Installation = zup.Installation;
7const Installations = zup.Installations;
8
9pub const params =
10 \\--active List the active version
11 \\-a, --all List the active, available, and installed versions
12 \\-A, --available List available versions
13 \\-i, --installed List installed versions
14;
15pub const description = "Lists Zig versions. Default is `--active -i`.";
16pub const min_args = 0;
17pub const max_args = 0;
18
19pub fn main(comptime Result: type, allocator: Allocator, res: Result) !void {
20 var list_active = res.args.active;
21 var list_available = res.args.available;
22 var list_installed = res.args.installed;
23
24 if (res.args.all) {
25 list_active = true;
26 list_available = true;
27 list_installed = true;
28 } else if (!list_active and !list_available and !list_installed) {
29 list_active = true;
30 list_installed = true;
31 }
32
33 if (list_active) {
34 var active = try Installation.getActiveName(allocator);
35 defer if (active) |s| allocator.free(s);
36 try printActive(active);
37 }
38
39 if (list_installed) {
40 var installed = try Installation.getInstalledList(allocator);
41 defer Installation.deinitMap(allocator, &installed);
42 try printInstalledList(allocator, installed);
43 }
44
45 if (list_available) {
46 var available = try Installation.getAvailableList(allocator);
47 defer Installation.deinitMap(allocator, &available);
48 try printAvailableList(allocator, available);
49 }
50}
51
52// TODO: zig-bug active should be ?[]const u8
53fn printActive(active: ?[]u8) !void {
54 const writer = std.io.getStdOut().writer();
55 try writer.writeAll("Active installation: ");
56 if (active) |act| {
57 try writer.writeAll(act);
58 } else {
59 try writer.writeAll("NONE");
60 }
61
62 try writer.writeByte('\n');
63}
64
65fn printAvailableList(allocator: Allocator, avail: Installations) !void {
66 const writer = std.io.getStdOut().writer();
67 try writer.writeAll("Available versions:\n");
68 try printList(allocator, avail);
69}
70
71fn printInstalledList(allocator: Allocator, installed: Installations) !void {
72 const writer = std.io.getStdOut().writer();
73 try writer.writeAll("Installed versions:\n");
74 try printList(allocator, installed);
75}
76
77fn printList(allocator: Allocator, installations: Installations) !void {
78 const InstallationAndName = struct {
79 name: []const u8,
80 installation: Installation,
81
82 const Self = @This();
83
84 pub fn lessThan(_: void, lhs: Self, rhs: Self) bool {
85 return lhs.installation.version.order(rhs.installation.version) == .lt;
86 }
87 };
88
89 var list = try ArrayList(InstallationAndName).initCapacity(allocator, installations.unmanaged.size);
90 defer list.deinit();
91
92 var it = installations.iterator();
93 while (it.next()) |kv| {
94 list.appendAssumeCapacity(.{ .name = kv.key_ptr.*, .installation = kv.value_ptr.* });
95 }
96
97 std.sort.sort(InstallationAndName, list.items, {}, InstallationAndName.lessThan);
98
99 const writer = std.io.getStdOut().writer();
100 for (list.items) |item| {
101 try writer.print(" {s}: {}\n", .{ item.name, item.installation.version });
102 }
103}
diff --git a/src/main.zig b/src/main.zig
new file mode 100644
index 0000000..f2868b6
--- /dev/null
+++ b/src/main.zig
@@ -0,0 +1,155 @@
1pub const Installation = @import("Installation.zig");
2pub const Installations = Installation.Installations;
3pub const SubCommand = @import("subcommand.zig").SubCommand;
4
5pub const help = SubCommand(Help);
6pub const install = SubCommand(@import("install.zig"));
7pub const list = SubCommand(@import("list.zig"));
8pub const remove = SubCommand(@import("remove.zig"));
9pub const @"switch" = SubCommand(@import("switch.zig"));
10pub const update = SubCommand(@import("update.zig"));
11pub const version = SubCommand(Version);
12
13const std = @import("std");
14const zup_config = @import("zup-config");
15
16const Allocator = std.mem.Allocator;
17const ArgIterator = std.process.ArgIterator;
18const ComptimeStringMap = std.ComptimeStringMap;
19const GPA = std.heap.GeneralPurposeAllocator(.{});
20const Tuple = std.meta.Tuple;
21
22// TODO: config for supported triples. while smth like x86_64-macos on aarch64-macos can be hardcoded, that won't be
23// the case for someone with qemu-user on linux
24
25pub fn main() !void {
26 var gpa = GPA{};
27 const allocator = gpa.allocator();
28 defer _ = gpa.deinit();
29
30 var args = try std.process.argsWithAllocator(allocator);
31 defer args.deinit();
32
33 _ = args.skip();
34 const cmd = args.next() orelse {
35 return Help.mainHelp();
36 };
37
38 return dispatch(cmd, "main", unknownCmd, .{ cmd, allocator, &args });
39}
40
41pub fn printVersion() !void {
42 return std.io.getStdErr().writer().print("{}\n", .{zup_config.version});
43}
44
45const Command = enum {
46 help,
47 install,
48 list,
49 remove,
50 @"switch",
51 update,
52 version,
53};
54
55const CommandMap = blk: {
56 const Pair = Tuple(&.{ []const u8, Command });
57 const commands = @typeInfo(Command).Enum.fields;
58 var map_data: [commands.len + 2]Pair = undefined;
59 map_data[0] = .{ "--help", .help };
60 map_data[1] = .{ "--version", .version };
61 var idx: usize = 2;
62 inline for (commands) |command| {
63 map_data[idx] = .{ command.name, @intToEnum(Command, command.value) };
64 idx += 1;
65 }
66
67 break :blk ComptimeStringMap(Command, map_data);
68};
69
70fn dispatch(
71 cmd: []const u8,
72 comptime fn_name: []const u8,
73 comptime default_fn: anytype,
74 args: anytype,
75) !void {
76 // TODO: This can still be improved, currently we're looping through all possible values, it could be somehow made
77 // into a switch.
78 const cmd_enum = CommandMap.get(cmd) orelse return @call(.{}, default_fn, args);
79 const commands = @typeInfo(Command).Enum.fields;
80 inline for (commands) |command| {
81 if (@enumToInt(cmd_enum) == command.value) {
82 // TODO: zig-bug it cries about modifying a constant if
83 // I just write `return @call(.{}, fun, args);`
84 const fun = @field(@field(@This(), command.name), fn_name);
85 return call(fun, args);
86 }
87 }
88 unreachable;
89}
90
91inline fn call(fun: anytype, args: anytype) !void {
92 return @call(.{}, fun, args);
93}
94
95fn unknownCmd(cmd: []const u8, _: Allocator, _: *ArgIterator) !void {
96 std.log.err("Unknown subcommand: {s}", .{cmd});
97 return error.ArgError;
98}
99
100const Help = struct {
101 pub const params = "<COMMAND>";
102 pub const description = "Displays help for the specified <COMMAND>.";
103 pub const min_args = 0;
104 pub const max_args = 1;
105
106 pub fn main(comptime Result: type, _: Allocator, res: Result) !void {
107 if (res.positionals.len == 0) {
108 return mainHelp();
109 }
110
111 const cmd = res.positionals[0];
112 return dispatch(cmd, "help", unknownHelp, .{cmd});
113 }
114
115 pub fn mainHelp() !void {
116 const writer = std.io.getStdErr().writer();
117 try writer.writeAll(
118 \\USAGE: zup <COMMAND>
119 \\
120 \\These are the common Zup commands:
121 \\
122 \\ install Install a Zig version
123 \\ help See the help for various topics
124 \\ list List Zig versions
125 \\ remove Remove an installed Zig version
126 \\ switch Switch between installed Zig versions
127 \\ update Update installed Zig versions
128 \\ version Print the version of Zup
129 \\
130 \\You can find out more about a command by running `zup help <command>`.
131 \\
132 );
133 }
134
135 fn unknownHelp(cmd: []const u8) !void {
136 std.log.err("Unknown subcommand: {s}", .{cmd});
137 try mainHelp();
138 return error.ArgError;
139 }
140};
141
142const Version = struct {
143 pub const params = "";
144 pub const description = "Print the version of Zup and exit.";
145 pub const min_args = 0;
146 pub const max_args = 0;
147
148 pub fn main(comptime Result: type, _: Allocator, _: Result) !void {
149 return printVersion();
150 }
151};
152
153test "basic test" {
154 try std.testing.expectEqual(10, 3 + 7);
155}
diff --git a/src/remove.zig b/src/remove.zig
new file mode 100644
index 0000000..c1c35d4
--- /dev/null
+++ b/src/remove.zig
@@ -0,0 +1,50 @@
1const std = @import("std");
2const xdg = @import("xdg");
3const zup = @import("zup");
4
5const Allocator = std.mem.Allocator;
6const Installation = zup.Installation;
7
8pub const params =
9 \\-f, --force Remove even if it is the active installation
10 \\<NAME>
11;
12pub const description = "Removes an installed Zig version. Run `zup list -i` to see installed <NAME>s.";
13pub const min_args = 1;
14pub const max_args = 1;
15
16pub fn main(comptime Result: type, allocator: Allocator, res: Result) !void {
17 const name = res.positionals[0];
18
19 if (!try Installation.isInstalled(allocator, name)) {
20 std.log.err("{s} is not installed!", .{name});
21 return error.InstallationNotFound;
22 }
23
24 const is_active = blk: {
25 const active = try Installation.getActiveName(allocator);
26 defer if (active) |s| allocator.free(s);
27
28 break :blk active != null and std.mem.eql(u8, active.?, name);
29 };
30
31 if (is_active and !res.args.force) {
32 std.log.err("{s} is the active installation, not removing without --force!", .{name});
33 return error.CantRemove;
34 }
35
36 var data_home = try xdg.openDataHome(allocator, "zup");
37 defer data_home.close();
38
39 std.log.info("Removing {s}...", .{name});
40 try data_home.deleteTree(name);
41
42 if (is_active) {
43 var bin_home = try xdg.openBinHome(allocator);
44 defer bin_home.close();
45
46 try bin_home.deleteFile("zig");
47 }
48
49 std.log.info("Done", .{});
50}
diff --git a/src/subcommand.zig b/src/subcommand.zig
new file mode 100644
index 0000000..ebd57ed
--- /dev/null
+++ b/src/subcommand.zig
@@ -0,0 +1,70 @@
1const clap = @import("clap");
2const std = @import("std");
3const zup = @import("zup");
4
5const Allocator = std.mem.Allocator;
6const ArgIterator = std.process.ArgIterator;
7
8const parsers = .{
9 .COMMAND = clap.parsers.string,
10 .NAME = clap.parsers.string,
11};
12
13pub fn SubCommand(comptime template: type) type {
14 return struct {
15 pub const base = template;
16
17 const params = clap.parseParamsComptime(
18 \\-H, --help Display this help and exit
19 \\-V, --version Display the version of Zup and exit
20 \\
21 ++ template.params);
22
23 pub fn help(name: []const u8) !void {
24 const writer = std.io.getStdErr().writer();
25 try writer.print("USAGE: zup {s} ", .{name});
26 try clap.usage(writer, clap.Help, &params);
27 try writer.writeAll("\n\n");
28 try clap.help(writer, clap.Help, &params, .{
29 .description_on_new_line = false,
30 .description_indent = 0,
31 .indent = 2,
32 .max_width = 120,
33 .spacing_between_parameters = 0,
34 });
35 try writer.writeAll("\n" ++ template.description ++ "\n");
36 }
37
38 pub fn main(name: []const u8, allocator: Allocator, args: *ArgIterator) !void {
39 var diag = clap.Diagnostic{};
40 var res = clap.parseEx(clap.Help, &params, parsers, args, .{
41 .allocator = allocator,
42 .diagnostic = &diag,
43 }) catch |err| {
44 diag.report(std.io.getStdErr().writer(), err) catch {};
45 try help(name);
46 return err;
47 };
48 defer res.deinit();
49
50 if (res.args.help) {
51 return help(name);
52 }
53
54 if (res.args.version) {
55 return zup.printVersion();
56 }
57
58 if (res.positionals.len < template.min_args or res.positionals.len > template.max_args) {
59 try help(name);
60 return error.ArgError;
61 }
62
63 return template.main(
64 clap.ResultEx(clap.Help, &params, parsers),
65 allocator,
66 res,
67 );
68 }
69 };
70}
diff --git a/src/switch.zig b/src/switch.zig
new file mode 100644
index 0000000..058067d
--- /dev/null
+++ b/src/switch.zig
@@ -0,0 +1,38 @@
1const std = @import("std");
2const xdg = @import("xdg");
3const zup = @import("zup");
4
5const Allocator = std.mem.Allocator;
6const Installation = zup.Installation;
7
8pub const params = "<NAME>";
9pub const description = "Switches to a Zig version. Run `zup list -i` to see installed <NAME>s.";
10pub const min_args = 1;
11pub const max_args = 1;
12
13pub fn main(comptime Result: type, allocator: Allocator, res: Result) !void {
14 const name = res.positionals[0];
15 if (!try Installation.isInstalled(allocator, name)) {
16 std.log.err(
17 "No installation by name {s} found, run `zup install {s}`",
18 .{ name, name },
19 );
20 return error.InstallationNotFound;
21 }
22
23 const target = blk: {
24 const data_home = try xdg.getDataHome(allocator, "zup");
25 defer allocator.free(data_home);
26
27 break :blk try std.fs.path.join(allocator, &.{ data_home, name, "zig" });
28 };
29 defer allocator.free(target);
30
31 var bin_home = try xdg.openBinHome(allocator);
32 defer bin_home.close();
33
34 bin_home.deleteFile("zig") catch {};
35 try bin_home.symLink(target, "zig", .{});
36
37 std.log.info("Switched to {s}", .{name});
38}
diff --git a/src/update.zig b/src/update.zig
new file mode 100644
index 0000000..a8fd075
--- /dev/null
+++ b/src/update.zig
@@ -0,0 +1,34 @@
1const std = @import("std");
2const zup = @import("zup");
3
4const Allocator = std.mem.Allocator;
5const Installation = zup.Installation;
6
7// TODO: A way to specify a subset?
8
9pub const params = "";
10pub const description = "Updates all installed Zig versions.";
11pub const min_args = 0;
12pub const max_args = 0;
13
14pub fn main(comptime Result: type, allocator: Allocator, _: Result) !void {
15 var installed = try Installation.getInstalledList(allocator);
16 defer Installation.deinitMap(allocator, &installed);
17
18 var available = try Installation.getAvailableList(allocator);
19 defer Installation.deinitMap(allocator, &available);
20
21 var it = installed.iterator();
22 while (it.next()) |kv| {
23 const name = kv.key_ptr.*;
24 const inst = kv.value_ptr.*;
25 if (available.get(name)) |avail| {
26 if (avail.version.order(inst.version) == .gt) {
27 std.log.info("Updating {s} from {} to {}...", .{ name, inst.version, avail.version });
28 try zup.install.base.perform(allocator, name, true, available);
29 }
30 }
31 }
32
33 std.log.info("Done updating", .{});
34}