Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions kuri-mobile/src/ios/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,13 @@ pub fn run(gpa: std.mem.Allocator, args: []const []const u8) !u8 {
}
const udid = udid_opt orelse try resolveBootedSim(gpa);
defer if (udid_opt == null) gpa.free(udid);
try simctl.Sim.init(udid).screenshot(gpa, path);
simctl.Sim.init(udid).screenshot(gpa, path) catch |err| {
if (err == error.SimctlCommandFailed) {
io.writeStderr("screenshot failed: the target must be a booted iOS Simulator. Physical-device screenshots require XCUITest and are not supported in v1.\n");
return 3;
}
return err;
};
return 0;
}
if (std.mem.eql(u8, sub, "list-apps")) {
Expand Down Expand Up @@ -323,7 +329,7 @@ fn printUsage() !void {
\\ navigate [--udid U] <url> alias for openurl
\\ launch --udid U [--simulator|--device] <bundle-id>
\\ terminate --udid U [--simulator|--device] <bundle-id>
\\ screenshot [--udid U] [path.png] defaults to the booted sim if --udid omitted
\\ screenshot [--simulator] [--udid U] [path.png] Simulator only; defaults to booted sim
\\ list-apps --udid U --simulator
\\
\\Simulator-only input (macOS, device-pixel coords matching screenshot):
Expand Down
36 changes: 30 additions & 6 deletions kuri-mobile/src/ios/simctl.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,26 @@ pub const Sim = struct {

pub fn screenshot(self: Sim, gpa: std.mem.Allocator, path: []const u8) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "io", self.udid, "screenshot", path }, 64 * 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}

pub fn launch(self: Sim, gpa: std.mem.Allocator, bundle_id: []const u8) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "launch", self.udid, bundle_id }, 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}

pub fn terminate(self: Sim, gpa: std.mem.Allocator, bundle_id: []const u8) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "terminate", self.udid, bundle_id }, 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}

pub fn listApps(self: Sim, gpa: std.mem.Allocator) ![]u8 {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "listapps", self.udid }, 16 * 1024 * 1024);
errdefer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
return r.stdout;
}

Expand All @@ -35,20 +40,32 @@ pub const Sim = struct {
/// you tell Safari to load a page without typing in the address bar.
pub fn openUrl(self: Sim, gpa: std.mem.Allocator, url: []const u8) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "openurl", self.udid, url }, 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}

pub fn boot(self: Sim, gpa: std.mem.Allocator) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "boot", self.udid }, 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}

pub fn shutdown(self: Sim, gpa: std.mem.Allocator) !void {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "shutdown", self.udid }, 1024 * 1024);
gpa.free(r.stdout);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);
}
};

fn ensureSuccess(term: i32, output: []const u8) !void {
if (term == 0) return;
if (output.len != 0) {
io.writeStderr(output);
if (output[output.len - 1] != '\n') io.writeStderr("\n");
}
return error.SimctlCommandFailed;
}

pub const SimDevice = struct {
udid: []const u8,
name: []const u8,
Expand All @@ -58,6 +75,7 @@ pub const SimDevice = struct {
pub fn listDevices(gpa: std.mem.Allocator) ![]SimDevice {
const r = try io.runCommand(gpa, &.{ "xcrun", "simctl", "list", "devices", "--json" }, 16 * 1024 * 1024);
defer gpa.free(r.stdout);
try ensureSuccess(r.term, r.stdout);

var parsed = try std.json.parseFromSlice(std.json.Value, gpa, r.stdout, .{});
defer parsed.deinit();
Expand Down Expand Up @@ -90,6 +108,12 @@ pub fn listDevices(gpa: std.mem.Allocator) ![]SimDevice {
return try list.toOwnedSlice(gpa);
}

test "simctl command status is not silently ignored" {
try ensureSuccess(0, "");
try std.testing.expectError(error.SimctlCommandFailed, ensureSuccess(256, "simctl failed"));
try std.testing.expectError(error.SimctlCommandFailed, ensureSuccess(9, "terminated"));
}

pub fn freeSimDevices(gpa: std.mem.Allocator, devs: []const SimDevice) void {
for (devs) |d| {
gpa.free(d.udid);
Expand Down
Loading