@take-out/scripts 0.1.37 → 0.1.38-1772433507984
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/run-group +0 -0
- package/package.json +3 -2
- package/src/helpers/run.ts +79 -6
- package/src/run-group/.zig-cache/z/31f4371889dc4e502f87d8ad03235caa +0 -0
- package/src/run-group/build.zig +24 -0
- package/src/run-group/main.zig +327 -0
- package/src/run-group/run-group +0 -0
- package/src/run-group/run-group.c +497 -0
- package/src/run.ts +161 -226
package/bin/run-group
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@take-out/scripts",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.38-1772433507984",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./src/run.ts",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
}
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
|
+
"postinstall": "cd src/run-group && (clang -O2 -o run-group run-group.c 2>/dev/null || gcc -O2 -o run-group run-group.c 2>/dev/null || true)",
|
|
23
24
|
"lint": "oxlint src",
|
|
24
25
|
"lint:fix": "oxfmt src && oxlint --fix --fix-suggestions src",
|
|
25
26
|
"typecheck": "tko run typecheck"
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
"dependencies": {
|
|
31
32
|
"@clack/prompts": "^0.8.2",
|
|
32
33
|
"@lydell/node-pty": "^1.2.0-beta.3",
|
|
33
|
-
"@take-out/helpers": "0.1.
|
|
34
|
+
"@take-out/helpers": "0.1.38-1772433507984",
|
|
34
35
|
"picocolors": "^1.1.1"
|
|
35
36
|
},
|
|
36
37
|
"peerDependencies": {
|
package/src/helpers/run.ts
CHANGED
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import { spawn, type ChildProcess } from 'node:child_process'
|
|
2
|
+
import { existsSync } from 'node:fs'
|
|
2
3
|
import { cpus } from 'node:os'
|
|
4
|
+
import { dirname, resolve } from 'node:path'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
3
6
|
|
|
4
7
|
import type { Timer } from '@take-out/helpers'
|
|
5
8
|
|
|
9
|
+
// find run-group binary for proper process group management
|
|
10
|
+
let _runGroupBin: string | null | undefined
|
|
11
|
+
function findRunGroupBinary(): string | null {
|
|
12
|
+
if (_runGroupBin !== undefined) return _runGroupBin
|
|
13
|
+
|
|
14
|
+
// check relative to this file (in packages/scripts/bin/)
|
|
15
|
+
try {
|
|
16
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
17
|
+
const localBin = resolve(__dirname, '..', 'bin', 'run-group')
|
|
18
|
+
if (existsSync(localBin)) {
|
|
19
|
+
_runGroupBin = localBin
|
|
20
|
+
return _runGroupBin
|
|
21
|
+
}
|
|
22
|
+
} catch {}
|
|
23
|
+
|
|
24
|
+
// check in node_modules
|
|
25
|
+
try {
|
|
26
|
+
const nmBin = resolve(process.cwd(), 'node_modules', '@take-out', 'scripts', 'bin', 'run-group')
|
|
27
|
+
if (existsSync(nmBin)) {
|
|
28
|
+
_runGroupBin = nmBin
|
|
29
|
+
return _runGroupBin
|
|
30
|
+
}
|
|
31
|
+
} catch {}
|
|
32
|
+
|
|
33
|
+
_runGroupBin = null
|
|
34
|
+
return null
|
|
35
|
+
}
|
|
36
|
+
|
|
6
37
|
export type ProcessType = ChildProcess
|
|
7
38
|
export type ProcessHandler = (process: ProcessType) => void
|
|
8
39
|
|
|
@@ -19,6 +50,39 @@ export function getIsExiting() {
|
|
|
19
50
|
|
|
20
51
|
const processHandlers = new Set<ProcessHandler>()
|
|
21
52
|
|
|
53
|
+
// track all spawned child processes for cleanup on exit
|
|
54
|
+
const activeProcesses = new Set<ChildProcess>()
|
|
55
|
+
|
|
56
|
+
// install cleanup handlers once
|
|
57
|
+
let cleanupInstalled = false
|
|
58
|
+
function installCleanupHandlers() {
|
|
59
|
+
if (cleanupInstalled) return
|
|
60
|
+
cleanupInstalled = true
|
|
61
|
+
|
|
62
|
+
const cleanup = () => {
|
|
63
|
+
for (const proc of activeProcesses) {
|
|
64
|
+
try {
|
|
65
|
+
// kill the process group to ensure child processes are also killed
|
|
66
|
+
if (proc.pid) {
|
|
67
|
+
process.kill(-proc.pid, 'SIGTERM')
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// process may already be dead
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
process.on('exit', cleanup)
|
|
76
|
+
process.on('SIGINT', () => {
|
|
77
|
+
cleanup()
|
|
78
|
+
process.exit(130)
|
|
79
|
+
})
|
|
80
|
+
process.on('SIGTERM', () => {
|
|
81
|
+
cleanup()
|
|
82
|
+
process.exit(143)
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
|
|
22
86
|
const colors = [
|
|
23
87
|
'\x1b[36m', // cyan
|
|
24
88
|
'\x1b[35m', // magenta
|
|
@@ -93,13 +157,22 @@ export async function run(
|
|
|
93
157
|
let timeoutId: Timer | undefined
|
|
94
158
|
let didTimeOut = false
|
|
95
159
|
|
|
160
|
+
// find run-group binary (handles process group cleanup properly)
|
|
161
|
+
const runGroupBin = findRunGroupBinary()
|
|
162
|
+
|
|
96
163
|
try {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
164
|
+
// use run-group to wrap the command - it handles process groups and cleanup
|
|
165
|
+
const shell = runGroupBin
|
|
166
|
+
? spawn(runGroupBin, ['bash', '-c', command], {
|
|
167
|
+
env: { ...process.env, ...env },
|
|
168
|
+
cwd,
|
|
169
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
170
|
+
})
|
|
171
|
+
: spawn('bash', ['-c', command], {
|
|
172
|
+
env: { ...process.env, ...env },
|
|
173
|
+
cwd,
|
|
174
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
175
|
+
})
|
|
103
176
|
|
|
104
177
|
if (detached) {
|
|
105
178
|
shell.unref()
|
|
Binary file
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
pub fn build(b: *std.Build) void {
|
|
4
|
+
const target = b.standardTargetOptions(.{});
|
|
5
|
+
const optimize = b.standardOptimizeOption(.{});
|
|
6
|
+
|
|
7
|
+
const exe = b.addExecutable(.{
|
|
8
|
+
.name = "run-group",
|
|
9
|
+
.root_source_file = b.path("main.zig"),
|
|
10
|
+
.target = target,
|
|
11
|
+
.optimize = optimize,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
b.installArtifact(exe);
|
|
15
|
+
|
|
16
|
+
const run_cmd = b.addRunArtifact(exe);
|
|
17
|
+
run_cmd.step.dependOn(b.getInstallStep());
|
|
18
|
+
if (b.args) |args| {
|
|
19
|
+
run_cmd.addArgs(args);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const run_step = b.step("run", "Run the app");
|
|
23
|
+
run_step.dependOn(&run_cmd.step);
|
|
24
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const posix = std.posix;
|
|
3
|
+
const Allocator = std.mem.Allocator;
|
|
4
|
+
|
|
5
|
+
// run-group: run commands with proper process group management
|
|
6
|
+
//
|
|
7
|
+
// usage:
|
|
8
|
+
// run-group <command> [args...] # single command
|
|
9
|
+
// run-group -p <cmd1> --- <cmd2> --- ... # parallel commands
|
|
10
|
+
//
|
|
11
|
+
// flags:
|
|
12
|
+
// -p, --parallel run commands in parallel (separated by ---)
|
|
13
|
+
// -k, --keep-going don't kill others on failure
|
|
14
|
+
// -q, --quiet suppress output
|
|
15
|
+
// -t, --timing show timing info
|
|
16
|
+
// --prefix <name> prefix output with [name]
|
|
17
|
+
|
|
18
|
+
const colors = [_][]const u8{
|
|
19
|
+
"\x1b[36m", // cyan
|
|
20
|
+
"\x1b[35m", // magenta
|
|
21
|
+
"\x1b[32m", // green
|
|
22
|
+
"\x1b[33m", // yellow
|
|
23
|
+
"\x1b[34m", // blue
|
|
24
|
+
"\x1b[31m", // red
|
|
25
|
+
};
|
|
26
|
+
const reset = "\x1b[0m";
|
|
27
|
+
const bold = "\x1b[1m";
|
|
28
|
+
const dim = "\x1b[2m";
|
|
29
|
+
|
|
30
|
+
const Child = struct {
|
|
31
|
+
pid: posix.pid_t,
|
|
32
|
+
name: []const u8,
|
|
33
|
+
color_idx: usize,
|
|
34
|
+
start_time: i64,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
var children: std.ArrayList(Child) = undefined;
|
|
38
|
+
var keep_going = false;
|
|
39
|
+
var quiet = false;
|
|
40
|
+
var show_timing = false;
|
|
41
|
+
var any_failed = false;
|
|
42
|
+
var shutting_down = false;
|
|
43
|
+
|
|
44
|
+
fn killAllChildren() void {
|
|
45
|
+
for (children.items) |child| {
|
|
46
|
+
// kill process group
|
|
47
|
+
posix.kill(-child.pid, posix.SIG.TERM) catch {};
|
|
48
|
+
}
|
|
49
|
+
// brief wait then force kill
|
|
50
|
+
std.time.sleep(50 * std.time.ns_per_ms);
|
|
51
|
+
for (children.items) |child| {
|
|
52
|
+
posix.kill(-child.pid, posix.SIG.KILL) catch {};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
fn signalHandler(sig: c_int) callconv(.C) void {
|
|
57
|
+
shutting_down = true;
|
|
58
|
+
killAllChildren();
|
|
59
|
+
// re-raise to get proper exit
|
|
60
|
+
const default_action = posix.Sigaction{
|
|
61
|
+
.handler = .{ .handler = posix.SIG.DFL },
|
|
62
|
+
.mask = posix.empty_sigset,
|
|
63
|
+
.flags = 0,
|
|
64
|
+
};
|
|
65
|
+
posix.sigaction(@intCast(sig), &default_action, null) catch {};
|
|
66
|
+
_ = posix.raise(@intCast(sig)) catch {};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fn getColor(idx: usize) []const u8 {
|
|
70
|
+
return colors[idx % colors.len];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
fn formatDuration(ms: i64) [32]u8 {
|
|
74
|
+
var buf: [32]u8 = undefined;
|
|
75
|
+
const secs = @divFloor(ms, 1000);
|
|
76
|
+
const mins = @divFloor(secs, 60);
|
|
77
|
+
const rem_secs = @rem(secs, 60);
|
|
78
|
+
|
|
79
|
+
if (mins > 0) {
|
|
80
|
+
_ = std.fmt.bufPrint(&buf, "{}m {}s", .{ mins, rem_secs }) catch "?";
|
|
81
|
+
} else if (secs > 0) {
|
|
82
|
+
_ = std.fmt.bufPrint(&buf, "{}s", .{secs}) catch "?";
|
|
83
|
+
} else {
|
|
84
|
+
_ = std.fmt.bufPrint(&buf, "{}ms", .{ms}) catch "?";
|
|
85
|
+
}
|
|
86
|
+
return buf;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
fn runSingle(allocator: Allocator, argv: []const []const u8, prefix: ?[]const u8, color_idx: usize) !u8 {
|
|
90
|
+
const start = std.time.milliTimestamp();
|
|
91
|
+
|
|
92
|
+
const pid = try posix.fork();
|
|
93
|
+
|
|
94
|
+
if (pid == 0) {
|
|
95
|
+
// child: create new process group
|
|
96
|
+
_ = posix.setpgid(0, 0) catch {};
|
|
97
|
+
|
|
98
|
+
// exec
|
|
99
|
+
const argv_z = try allocator.allocSentinel(?[*:0]const u8, argv.len, null);
|
|
100
|
+
for (argv, 0..) |arg, i| {
|
|
101
|
+
argv_z[i] = try allocator.dupeZ(u8, arg);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const env = @as([*:null]const ?[*:0]const u8, @ptrCast(std.c.environ));
|
|
105
|
+
const err = posix.execvpeZ(argv_z[0].?, argv_z, env);
|
|
106
|
+
_ = err;
|
|
107
|
+
std.posix.exit(127);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// parent: set pgid from parent side too
|
|
111
|
+
_ = posix.setpgid(pid, pid) catch {};
|
|
112
|
+
|
|
113
|
+
// wait for child
|
|
114
|
+
const result = posix.waitpid(pid, 0);
|
|
115
|
+
|
|
116
|
+
// cleanup any stragglers in the group
|
|
117
|
+
posix.kill(-pid, posix.SIG.TERM) catch {};
|
|
118
|
+
|
|
119
|
+
const duration = std.time.milliTimestamp() - start;
|
|
120
|
+
const status = result.status;
|
|
121
|
+
|
|
122
|
+
const exit_code: u8 = if (posix.W.IFEXITED(status))
|
|
123
|
+
posix.W.EXITSTATUS(status)
|
|
124
|
+
else if (posix.W.IFSIGNALED(status))
|
|
125
|
+
128 + @as(u8, @intCast(posix.W.TERMSIG(status)))
|
|
126
|
+
else
|
|
127
|
+
1;
|
|
128
|
+
|
|
129
|
+
if (show_timing and prefix != null) {
|
|
130
|
+
_ = color_idx;
|
|
131
|
+
const dur_buf = formatDuration(duration);
|
|
132
|
+
const dur_str = std.mem.sliceTo(&dur_buf, 0);
|
|
133
|
+
const stdout = std.io.getStdOut().writer();
|
|
134
|
+
if (exit_code == 0) {
|
|
135
|
+
stdout.print("{s}✓{s} {s}{s}{s} completed in {s}{s}{s}\n", .{
|
|
136
|
+
"\x1b[32m", reset, bold, prefix.?, reset, "\x1b[33m", dur_str, reset,
|
|
137
|
+
}) catch {};
|
|
138
|
+
} else {
|
|
139
|
+
stdout.print("{s}✗{s} {s}{s}{s} failed after {s}{s}{s}\n", .{
|
|
140
|
+
"\x1b[31m", reset, bold, prefix.?, reset, "\x1b[33m", dur_str, reset,
|
|
141
|
+
}) catch {};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return exit_code;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
fn spawnChild(allocator: Allocator, argv: []const []const u8, name: []const u8, color_idx: usize) !void {
|
|
149
|
+
const start = std.time.milliTimestamp();
|
|
150
|
+
const pid = try posix.fork();
|
|
151
|
+
|
|
152
|
+
if (pid == 0) {
|
|
153
|
+
// child
|
|
154
|
+
_ = posix.setpgid(0, 0) catch {};
|
|
155
|
+
|
|
156
|
+
const argv_z = try allocator.allocSentinel(?[*:0]const u8, argv.len, null);
|
|
157
|
+
for (argv, 0..) |arg, i| {
|
|
158
|
+
argv_z[i] = try allocator.dupeZ(u8, arg);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const env = @as([*:null]const ?[*:0]const u8, @ptrCast(std.c.environ));
|
|
162
|
+
const err = posix.execvpeZ(argv_z[0].?, argv_z, env);
|
|
163
|
+
_ = err;
|
|
164
|
+
std.posix.exit(127);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// parent
|
|
168
|
+
_ = posix.setpgid(pid, pid) catch {};
|
|
169
|
+
|
|
170
|
+
try children.append(.{
|
|
171
|
+
.pid = pid,
|
|
172
|
+
.name = name,
|
|
173
|
+
.color_idx = color_idx,
|
|
174
|
+
.start_time = start,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
fn waitForChildren() u8 {
|
|
179
|
+
var max_exit: u8 = 0;
|
|
180
|
+
|
|
181
|
+
while (children.items.len > 0) {
|
|
182
|
+
const result = posix.waitpid(-1, 0);
|
|
183
|
+
const pid = result.pid;
|
|
184
|
+
|
|
185
|
+
// find and remove child
|
|
186
|
+
for (children.items, 0..) |child, i| {
|
|
187
|
+
if (child.pid == pid) {
|
|
188
|
+
const duration = std.time.milliTimestamp() - child.start_time;
|
|
189
|
+
const status = result.status;
|
|
190
|
+
|
|
191
|
+
const exit_code: u8 = if (posix.W.IFEXITED(status))
|
|
192
|
+
posix.W.EXITSTATUS(status)
|
|
193
|
+
else if (posix.W.IFSIGNALED(status))
|
|
194
|
+
128 + @as(u8, @intCast(posix.W.TERMSIG(status)))
|
|
195
|
+
else
|
|
196
|
+
1;
|
|
197
|
+
|
|
198
|
+
if (exit_code > max_exit) max_exit = exit_code;
|
|
199
|
+
|
|
200
|
+
if (show_timing) {
|
|
201
|
+
const stdout = std.io.getStdOut().writer();
|
|
202
|
+
const dur_buf = formatDuration(duration);
|
|
203
|
+
const dur_str = std.mem.sliceTo(&dur_buf, 0);
|
|
204
|
+
if (exit_code == 0) {
|
|
205
|
+
stdout.print("{s}✓{s} {s}{s}{s} completed in {s}{s}{s}\n", .{
|
|
206
|
+
"\x1b[32m", reset, bold, child.name, reset, "\x1b[33m", dur_str, reset,
|
|
207
|
+
}) catch {};
|
|
208
|
+
} else {
|
|
209
|
+
stdout.print("{s}✗{s} {s}{s}{s} failed after {s}{s}{s}\n", .{
|
|
210
|
+
"\x1b[31m", reset, bold, child.name, reset, "\x1b[33m", dur_str, reset,
|
|
211
|
+
}) catch {};
|
|
212
|
+
any_failed = true;
|
|
213
|
+
if (!keep_going and !shutting_down) {
|
|
214
|
+
shutting_down = true;
|
|
215
|
+
killAllChildren();
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// cleanup process group
|
|
221
|
+
posix.kill(-pid, posix.SIG.TERM) catch {};
|
|
222
|
+
|
|
223
|
+
_ = children.swapRemove(i);
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return max_exit;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
pub fn main() !u8 {
|
|
233
|
+
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
234
|
+
const allocator = gpa.allocator();
|
|
235
|
+
|
|
236
|
+
children = .{};
|
|
237
|
+
defer children.deinit(allocator);
|
|
238
|
+
|
|
239
|
+
const args = try std.process.argsAlloc(allocator);
|
|
240
|
+
defer std.process.argsFree(allocator, args);
|
|
241
|
+
|
|
242
|
+
if (args.len < 2) {
|
|
243
|
+
const stderr = std.io.getStdErr().writer();
|
|
244
|
+
try stderr.print(
|
|
245
|
+
\\usage: run-group [flags] <command> [args...]
|
|
246
|
+
\\ run-group -p <cmd1> --- <cmd2> --- ...
|
|
247
|
+
\\
|
|
248
|
+
\\flags:
|
|
249
|
+
\\ -p, --parallel run commands in parallel (separated by ---)
|
|
250
|
+
\\ -k, --keep-going don't kill others on failure
|
|
251
|
+
\\ -q, --quiet suppress output
|
|
252
|
+
\\ -t, --timing show timing info
|
|
253
|
+
\\
|
|
254
|
+
, .{});
|
|
255
|
+
return 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// set up signal handlers
|
|
259
|
+
const handler = posix.Sigaction{
|
|
260
|
+
.handler = .{ .handler = signalHandler },
|
|
261
|
+
.mask = posix.empty_sigset,
|
|
262
|
+
.flags = 0,
|
|
263
|
+
};
|
|
264
|
+
try posix.sigaction(posix.SIG.INT, &handler, null);
|
|
265
|
+
try posix.sigaction(posix.SIG.TERM, &handler, null);
|
|
266
|
+
try posix.sigaction(posix.SIG.HUP, &handler, null);
|
|
267
|
+
|
|
268
|
+
// parse flags
|
|
269
|
+
var parallel = false;
|
|
270
|
+
var cmd_start: usize = 1;
|
|
271
|
+
|
|
272
|
+
for (args[1..], 1..) |arg, i| {
|
|
273
|
+
if (std.mem.eql(u8, arg, "-p") or std.mem.eql(u8, arg, "--parallel")) {
|
|
274
|
+
parallel = true;
|
|
275
|
+
cmd_start = i + 1;
|
|
276
|
+
} else if (std.mem.eql(u8, arg, "-k") or std.mem.eql(u8, arg, "--keep-going")) {
|
|
277
|
+
keep_going = true;
|
|
278
|
+
cmd_start = i + 1;
|
|
279
|
+
} else if (std.mem.eql(u8, arg, "-q") or std.mem.eql(u8, arg, "--quiet")) {
|
|
280
|
+
quiet = true;
|
|
281
|
+
cmd_start = i + 1;
|
|
282
|
+
} else if (std.mem.eql(u8, arg, "-t") or std.mem.eql(u8, arg, "--timing")) {
|
|
283
|
+
show_timing = true;
|
|
284
|
+
cmd_start = i + 1;
|
|
285
|
+
} else if (arg[0] != '-') {
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (cmd_start >= args.len) {
|
|
291
|
+
const stderr = std.io.getStdErr().writer();
|
|
292
|
+
try stderr.print("error: no command specified\n", .{});
|
|
293
|
+
return 1;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (parallel) {
|
|
297
|
+
// split on --- and run in parallel
|
|
298
|
+
var cmds = std.ArrayList([]const []const u8).init(allocator);
|
|
299
|
+
var current_cmd = std.ArrayList([]const u8).init(allocator);
|
|
300
|
+
|
|
301
|
+
for (args[cmd_start..]) |arg| {
|
|
302
|
+
if (std.mem.eql(u8, arg, "---")) {
|
|
303
|
+
if (current_cmd.items.len > 0) {
|
|
304
|
+
try cmds.append(try current_cmd.toOwnedSlice());
|
|
305
|
+
current_cmd = std.ArrayList([]const u8).init(allocator);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
try current_cmd.append(arg);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (current_cmd.items.len > 0) {
|
|
312
|
+
try cmds.append(try current_cmd.toOwnedSlice());
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// spawn all
|
|
316
|
+
for (cmds.items, 0..) |cmd, i| {
|
|
317
|
+
const name = cmd[0];
|
|
318
|
+
try spawnChild(allocator, cmd, name, i);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
return waitForChildren();
|
|
322
|
+
} else {
|
|
323
|
+
// single command
|
|
324
|
+
const cmd = args[cmd_start..];
|
|
325
|
+
return try runSingle(allocator, cmd, cmd[0], 0);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
Binary file
|