Skip to content

Commit 326e0d9

Browse files
committed
add persistent install-dir setting
1 parent a9b48e4 commit 326e0d9

File tree

3 files changed

+184
-22
lines changed

3 files changed

+184
-22
lines changed

build.zig

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,11 +228,13 @@ fn addTests(
228228
.name = "test-usage-h",
229229
.argv = &.{"-h"},
230230
.check = .{ .expect_stderr_match = "Usage" },
231+
.use_test_install_dir = false,
231232
});
232233
tests.addWithClean(.{
233234
.name = "test-usage-help",
234235
.argv = &.{"--help"},
235236
.check = .{ .expect_stderr_match = "Usage" },
237+
.use_test_install_dir = false,
236238
});
237239

238240
tests.addWithClean(.{
@@ -243,6 +245,7 @@ fn addTests(
243245
.{ .expect_stdout_match = "version" },
244246
.{ .expect_stdout_match = "0.13.0" },
245247
},
248+
.use_test_install_dir = false,
246249
});
247250

248251
tests.addWithClean(.{
@@ -251,6 +254,7 @@ fn addTests(
251254
.checks = &.{
252255
.{ .expect_stderr_match = "error: could not download 'this-is-not-a-valid-url': the URL is invalid (InvalidFormat)" },
253256
},
257+
.use_test_install_dir = false,
254258
});
255259

256260
tests.addWithClean(.{
@@ -259,8 +263,49 @@ fn addTests(
259263
.checks = &.{
260264
.{ .expect_stderr_match = "failed to parse JSON content from index url 'https://ziglang.org' with " },
261265
},
266+
.use_test_install_dir = false,
262267
});
263268

269+
tests.addWithClean(.{
270+
.name = "test-get-install-dir",
271+
.argv = &.{"get-install-dir"},
272+
});
273+
tests.addWithClean(.{
274+
.name = "test-get-install-dir2",
275+
.argv = &.{ "--install-dir", "/a/fake/install/dir", "get-install-dir" },
276+
.checks = &.{
277+
.{ .expect_stdout_exact = "/a/fake/install/dir" },
278+
},
279+
.use_test_install_dir = false,
280+
});
281+
tests.addWithClean(.{
282+
.name = "test-set-install-dir-relative",
283+
.argv = &.{ "set-install-dir", "foo/bar" },
284+
.checks = &.{
285+
.{ .expect_stderr_match = "error: set-install-dir requires an absolute path" },
286+
},
287+
.use_test_install_dir = false,
288+
});
289+
290+
{
291+
// just has to be an absolute path that exists
292+
const install_dir = b.build_root.path.?;
293+
const with_install_dir = tests.add(.{
294+
.name = "test-set-install-dir",
295+
.argv = &.{ "set-install-dir", install_dir },
296+
.use_test_install_dir = false,
297+
});
298+
tests.addWithClean(.{
299+
.name = "test-get-install-dir3",
300+
.argv = &.{"get-install-dir"},
301+
.env = .{ .dir = with_install_dir },
302+
.checks = &.{
303+
.{ .expect_stdout_exact = install_dir },
304+
},
305+
.use_test_install_dir = false,
306+
});
307+
}
308+
264309
tests.addWithClean(.{
265310
.name = "test-no-default",
266311
.argv = &.{"default"},
@@ -625,6 +670,7 @@ const TestOptions = struct {
625670
argv: []const []const u8,
626671
check: ?std.Build.Step.Run.StdIo.Check = null,
627672
checks: []const std.Build.Step.Run.StdIo.Check = &.{},
673+
use_test_install_dir: bool = true,
628674
};
629675

630676
const Tests = struct {
@@ -652,6 +698,7 @@ const Tests = struct {
652698
run.addArtifactArg(tests.runtest_exe);
653699
run.addArg(opt.name);
654700
run.addArg(if (opt.add_path) "--with-path" else "--no-path");
701+
run.addArg(if (opt.use_test_install_dir) "--with-test-install-dir" else "--no-test-install-dir");
655702
if (opt.env) |env| {
656703
run.addDirectoryArg(env.dir);
657704
} else {

runtest.zig

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,14 @@ pub fn main() !void {
1717

1818
const test_name = all_args[1];
1919
const add_path_option = all_args[2];
20-
const in_env_dir = all_args[3];
21-
const with_compilers = compilersArg(all_args[4]);
22-
const keep_compilers = compilersArg(all_args[5]);
23-
const out_env_dir = all_args[6];
24-
const setup_option = all_args[7];
25-
const zigup_exe = all_args[8];
26-
const zigup_args = all_args[9..];
20+
const install_dir_option = all_args[3];
21+
const in_env_dir = all_args[4];
22+
const with_compilers = compilersArg(all_args[5]);
23+
const keep_compilers = compilersArg(all_args[6]);
24+
const out_env_dir = all_args[7];
25+
const setup_option = all_args[8];
26+
const zigup_exe = all_args[9];
27+
const zigup_args = all_args[10..];
2728

2829
const add_path = blk: {
2930
if (std.mem.eql(u8, add_path_option, "--with-path")) break :blk true;
@@ -32,6 +33,13 @@ pub fn main() !void {
3233
std.process.exit(0xff);
3334
};
3435

36+
const use_test_install_dir = blk: {
37+
if (std.mem.eql(u8, install_dir_option, "--with-test-install-dir")) break :blk true;
38+
if (std.mem.eql(u8, install_dir_option, "--no-test-install-dir")) break :blk false;
39+
std.log.err("expected '--with-test-install-dir' or '--no-test-install-dir' but got '{s}'", .{install_dir_option});
40+
std.process.exit(0xff);
41+
};
42+
3543
try fixdeletetree.deleteTree(std.fs.cwd(), out_env_dir);
3644
try std.fs.cwd().makeDir(out_env_dir);
3745

@@ -94,10 +102,20 @@ pub fn main() !void {
94102
try argv.append(zigup_exe);
95103
try argv.append("--path-link");
96104
try argv.append(path_link);
97-
try argv.append("--install-dir");
98-
try argv.append(install_dir);
105+
if (use_test_install_dir) {
106+
try argv.append("--install-dir");
107+
try argv.append(install_dir);
108+
}
99109
try argv.appendSlice(zigup_args);
100110

111+
if (true) {
112+
try std.io.getStdErr().writer().writeAll("runtest exec: ");
113+
for (argv.items) |arg| {
114+
try std.io.getStdErr().writer().print(" {s}", .{arg});
115+
}
116+
try std.io.getStdErr().writer().writeAll("\n");
117+
}
118+
101119
var child = std.process.Child.init(argv.items, arena);
102120

103121
if (add_path) {

zigup.zig

Lines changed: 110 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ fn ignoreHttpCallback(request: []const u8) void {
131131
_ = request;
132132
}
133133

134-
fn allocInstallDirStringXdg(allocator: Allocator) ![]const u8 {
134+
fn allocInstallDirStringXdg(allocator: Allocator) error{AlreadyReported}![]const u8 {
135135
// see https://specifications.freedesktop.org/basedir-spec/latest/#variables
136136
// try $XDG_DATA_HOME/zigup first
137137
xdg_var: {
@@ -141,7 +141,7 @@ fn allocInstallDirStringXdg(allocator: Allocator) ![]const u8 {
141141
std.log.err("$XDG_DATA_HOME environment variable '{s}' is not an absolute path", .{xdg_data_home});
142142
return error.AlreadyReported;
143143
}
144-
return std.fs.path.join(allocator, &[_][]const u8{ xdg_data_home, "zigup" });
144+
return std.fs.path.join(allocator, &[_][]const u8{ xdg_data_home, "zigup" }) catch |e| oom(e);
145145
}
146146
// .. then fallback to $HOME/.local/share/zigup
147147
const home = std.posix.getenv("HOME") orelse {
@@ -152,16 +152,77 @@ fn allocInstallDirStringXdg(allocator: Allocator) ![]const u8 {
152152
std.log.err("$HOME environment variable '{s}' is not an absolute path", .{home});
153153
return error.AlreadyReported;
154154
}
155-
return std.fs.path.join(allocator, &[_][]const u8{ home, ".local", "share", "zigup" });
155+
return std.fs.path.join(allocator, &[_][]const u8{ home, ".local", "share", "zigup" }) catch |e| oom(e);
156156
}
157157

158-
fn allocInstallDirString(allocator: Allocator) ![]const u8 {
159-
// TODO: maybe support ZIG_INSTALL_DIR environment variable?
160-
// TODO: maybe support a file on the filesystem to configure install dir?
158+
fn getSettingsDir(allocator: Allocator) ?[]const u8 {
159+
return std.fs.getAppDataDir(allocator, "zigup") catch |err| switch (err) {
160+
error.OutOfMemory => |e| oom(e),
161+
error.AppDataDirUnavailable => return null,
162+
};
163+
}
164+
165+
fn readInstallDir(allocator: Allocator) !?[]const u8 {
166+
const settings_dir_path = getSettingsDir(allocator) orelse return null;
167+
defer allocator.free(settings_dir_path);
168+
const setting_path = std.fs.path.join(allocator, &.{ settings_dir_path, "install-dir" }) catch |e| oom(e);
169+
defer allocator.free(setting_path);
170+
var file = std.fs.cwd().openFile(setting_path, .{}) catch |err| switch (err) {
171+
error.FileNotFound => return null,
172+
else => |e| {
173+
std.log.err("open '{s}' failed with {s}", .{ setting_path, @errorName(e) });
174+
return error.AlreadyReported;
175+
},
176+
};
177+
defer file.close();
178+
return file.readToEndAlloc(allocator, 9999) catch |err| {
179+
std.log.err("read install dir from '{s}' failed with {s}", .{ setting_path, @errorName(err) });
180+
return error.AlreadyReported;
181+
};
182+
}
183+
184+
fn saveInstallDir(allocator: Allocator, maybe_dir: ?[]const u8) !void {
185+
const settings_dir_path = getSettingsDir(allocator) orelse {
186+
std.log.err("cannot save install dir, unable to find a suitable settings directory", .{});
187+
return error.AlreadyReported;
188+
};
189+
defer allocator.free(settings_dir_path);
190+
const setting_path = std.fs.path.join(allocator, &.{ settings_dir_path, "install-dir" }) catch |e| oom(e);
191+
defer allocator.free(setting_path);
192+
if (maybe_dir) |d| {
193+
{
194+
const file = try std.fs.cwd().createFile(setting_path, .{});
195+
defer file.close();
196+
try file.writer().writeAll(d);
197+
}
198+
199+
// sanity check, read it back
200+
const readback = (try readInstallDir(allocator)) orelse {
201+
std.log.err("unable to readback install-dir after saving it", .{});
202+
return error.AlreadyReported;
203+
};
204+
defer allocator.free(readback);
205+
if (!std.mem.eql(u8, readback, d)) {
206+
std.log.err("saved install dir readback mismatch\nwrote: '{s}'\nread : '{s}'\n", .{ d, readback });
207+
return error.AlreadyReported;
208+
}
209+
} else {
210+
std.fs.cwd().deleteFile(setting_path) catch |err| switch (err) {
211+
error.FileNotFound => {},
212+
else => |e| return e,
213+
};
214+
}
215+
}
216+
217+
fn allocInstallDirString(allocator: Allocator) error{AlreadyReported}![]const u8 {
218+
if (try readInstallDir(allocator)) |d| return d;
161219
if (builtin.os.tag == .windows) {
162-
const self_exe_dir = try std.fs.selfExeDirPathAlloc(allocator);
220+
const self_exe_dir = std.fs.selfExeDirPathAlloc(allocator) catch |e| {
221+
std.log.err("failed to get exe dir path with {s}", .{@errorName(e)});
222+
return error.AlreadyReported;
223+
};
163224
defer allocator.free(self_exe_dir);
164-
return std.fs.path.join(allocator, &.{ self_exe_dir, "zig" });
225+
return std.fs.path.join(allocator, &.{ self_exe_dir, "zig" }) catch |e| oom(e);
165226
}
166227
return allocInstallDirStringXdg(allocator);
167228
}
@@ -205,8 +266,12 @@ fn toAbsolute(allocator: Allocator, path: []const u8) ![]u8 {
205266
return std.fs.path.join(allocator, &[_][]const u8{ cwd, path });
206267
}
207268

208-
fn help() void {
209-
std.io.getStdErr().writeAll(
269+
fn help(allocator: Allocator) !void {
270+
const default_install_dir = allocInstallDirString(allocator) catch |err| switch (err) {
271+
error.AlreadyReported => "unknown (see error printed above)",
272+
};
273+
274+
try std.io.getStdErr().writer().print(
210275
\\Download and manage zig compilers.
211276
\\
212277
\\Common Usage:
@@ -220,12 +285,16 @@ fn help() void {
220285
\\ zigup keep VERSION mark a compiler to be kept during clean
221286
\\ zigup run VERSION ARGS... run the given VERSION of the compiler with the given ARGS...
222287
\\
288+
\\ zigup get-install-dir prints the install directory to stdout
289+
\\ zigup set-install-dir PATH set the default install directory
290+
\\
223291
\\Uncommon Usage:
224292
\\
225293
\\ zigup fetch-index download and print the download index json
226294
\\
227295
\\Common Options:
228296
\\ --install-dir DIR override the default install location
297+
\\ default: {s}
229298
\\ --path-link PATH path to the `zig` symlink that points to the default compiler
230299
\\ this will typically be a file path within a PATH directory so
231300
\\ that the user can just run `zig`
@@ -234,7 +303,9 @@ fn help() void {
234303
++ " " ++ default_index_url ++
235304
\\
236305
\\
237-
) catch unreachable;
306+
,
307+
.{default_install_dir},
308+
);
238309
}
239310

240311
fn getCmdOpt(args: [][:0]u8, i: *usize) ![]const u8 {
@@ -287,7 +358,7 @@ pub fn main2() !u8 {
287358
} else if (std.mem.eql(u8, "--index", arg)) {
288359
index_url = try getCmdOpt(args, &i);
289360
} else if (std.mem.eql(u8, "-h", arg) or std.mem.eql(u8, "--help", arg)) {
290-
help();
361+
try help(allocator);
291362
return 0;
292363
} else {
293364
if (newlen == 0 and std.mem.eql(u8, "run", arg)) {
@@ -300,9 +371,35 @@ pub fn main2() !u8 {
300371
args = args[0..newlen];
301372
}
302373
if (args.len == 0) {
303-
help();
374+
try help(allocator);
304375
return 1;
305376
}
377+
if (std.mem.eql(u8, "get-install-dir", args[0])) {
378+
if (args.len != 1) {
379+
std.log.err("get-install-dir does not accept any cmdline arguments", .{});
380+
return 1;
381+
}
382+
const install_dir = getInstallDir(allocator, .{ .create = false }) catch |err| switch (err) {
383+
error.AlreadyReported => return 1,
384+
else => |e| return e,
385+
};
386+
try std.io.getStdOut().writer().writeAll(install_dir);
387+
return 0;
388+
}
389+
if (std.mem.eql(u8, "set-install-dir", args[0])) {
390+
const set_args = args[1..];
391+
if (set_args.len != 1) {
392+
std.log.err("set-install-dir requires 1 cmdline arg but got {}", .{set_args.len});
393+
return 1;
394+
}
395+
const path = set_args[0];
396+
if (!std.fs.path.isAbsolute(path)) {
397+
std.log.err("set-install-dir requires an absolute path", .{});
398+
return 1;
399+
}
400+
try saveInstallDir(allocator, path);
401+
return 0;
402+
}
306403
if (std.mem.eql(u8, "fetch-index", args[0])) {
307404
if (args.len != 1) {
308405
std.log.err("'index' command requires 0 arguments but got {d}", .{args.len - 1});

0 commit comments

Comments
 (0)