os/open: do not wait for commands which do not terminate

Some opener commands (like macOS's open) finish immediately after
running, while others (xdg-open) do not, staying alive until the
application that was opened itself terminates.

For now, we explicitly state whether or not we should wait for a
command. Eventually we may want to do something more generic (e.g. wait
for some predetermined amount of time and if the process does not
complete, give up without collecting stderr).
This commit is contained in:
Gregory Anders
2024-05-31 12:25:17 -05:00
parent 2ca1a7aeed
commit 695bc307ba

View File

@ -7,19 +7,31 @@ const Allocator = std.mem.Allocator;
/// Any output on stderr is logged as a warning in the application logs. /// Any output on stderr is logged as a warning in the application logs.
/// Output on stdout is ignored. /// Output on stdout is ignored.
pub fn open(alloc: Allocator, url: []const u8) !void { pub fn open(alloc: Allocator, url: []const u8) !void {
const argv = switch (builtin.os.tag) { // Some opener commands terminate after opening (macOS open) and some do not
.linux => &.{ "xdg-open", url }, // (xdg-open). For those which do not terminate, we do not want to wait for
.macos => &.{ "open", url }, // the process to exit to collect stderr.
.windows => &.{ "rundll32", "url.dll,FileProtocolHandler", url }, const argv, const wait = switch (builtin.os.tag) {
.linux => .{ &.{ "xdg-open", url }, false },
.macos => .{ &.{ "open", url }, true },
.windows => .{ &.{ "rundll32", "url.dll,FileProtocolHandler", url }, false },
.ios => return error.Unimplemented, .ios => return error.Unimplemented,
else => @compileError("unsupported OS"), else => @compileError("unsupported OS"),
}; };
var exe = std.process.Child.init(argv, alloc); var exe = std.process.Child.init(argv, alloc);
if (comptime wait) {
// Pipe stdout/stderr so we can collect output from the command // Pipe stdout/stderr so we can collect output from the command
exe.stdout_behavior = .Pipe; exe.stdout_behavior = .Pipe;
exe.stderr_behavior = .Pipe; exe.stderr_behavior = .Pipe;
}
try exe.spawn();
if (comptime wait) {
// 50 KiB is the default value used by std.process.Child.run
const output_max_size = 50 * 1024;
var stdout = std.ArrayList(u8).init(alloc); var stdout = std.ArrayList(u8).init(alloc);
var stderr = std.ArrayList(u8).init(alloc); var stderr = std.ArrayList(u8).init(alloc);
defer { defer {
@ -27,14 +39,11 @@ pub fn open(alloc: Allocator, url: []const u8) !void {
stderr.deinit(); stderr.deinit();
} }
// 50 KiB is the default value used by std.process.Child.run
const output_max_size = 50 * 1024;
try exe.spawn();
try exe.collectOutput(&stdout, &stderr, output_max_size); try exe.collectOutput(&stdout, &stderr, output_max_size);
_ = try exe.wait(); _ = try exe.wait();
// If we have any stderr output we log it. This makes it easier for // If we have any stderr output we log it. This makes it easier for
// users to debug why some open commands may not work as expected. // users to debug why some open commands may not work as expected.
if (stderr.items.len > 0) std.log.err("open stderr={s}", .{stderr.items}); if (stderr.items.len > 0) std.log.err("open stderr={s}", .{stderr.items});
}
} }