summaryrefslogtreecommitdiff
path: root/src/Installation.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Installation.zig')
-rw-r--r--src/Installation.zig196
1 files changed, 196 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}