Skip to content

Commit 50d1618

Browse files
bmeurerDevtools-frontend LUCI CQ
authored andcommitted
Fix npm run build and npm start on Windows.
On Windows the `depot_tools` distributed python is run via a batch script, which requires `child_process.spawn()` with the `shell` option set to `true` argument in order to run. Also-By: [email protected] Fixed: 436517091 Change-Id: Ib21b3d142f51acff65c06c61710d9481a239c091 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6818471 Commit-Queue: Kim-Anh Tran <[email protected]> Auto-Submit: Benedikt Meurer <[email protected]> Reviewed-by: Kim-Anh Tran <[email protected]>
1 parent b06c71d commit 50d1618

File tree

1 file changed

+64
-7
lines changed

1 file changed

+64
-7
lines changed

scripts/devtools_build.mjs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import childProcess from 'node:child_process';
66
import fs from 'node:fs/promises';
77
import path from 'node:path';
88
import {performance} from 'node:perf_hooks';
9-
import util from 'node:util';
109

1110
import {
1211
autoninjaPyPath,
@@ -16,7 +15,65 @@ import {
1615
vpython3ExecutablePath,
1716
} from './devtools_paths.js';
1817

19-
const execFile = util.promisify(childProcess.execFile);
18+
/**
19+
* Errors returned from `spawn()` will have additional `stderr` and `stdout`
20+
* properties, similar to what we'd get from `child_process.execFile()`.
21+
*/
22+
class SpawnError extends Error {
23+
/**
24+
* Constructor for errors generated from `spawn()`.
25+
*
26+
* @param {string} message - The actual error message.
27+
* @param {string} stderr - The child process' error output.
28+
* @param {string} stdout - The child process' regular output.
29+
*/
30+
constructor(message, stderr, stdout) {
31+
super(message);
32+
this.stderr = stderr;
33+
this.stdout = stdout;
34+
}
35+
}
36+
37+
/**
38+
* Promisified wrapper around `child_process.spawn()`.
39+
*
40+
* In addition to forwarding the `options` to `child_process.spawn()`, it'll also
41+
* set the `shell` option to `true`, to ensure that on Windows we can correctly
42+
* invoke `.bat` files (necessary for the Python3 wrapper script).
43+
*
44+
* @param {string} command - The command to run.
45+
* @param {Array<string>} args - List of string arguments to pass to the `command`.
46+
* @param {Object} options - Passed directly to `child_process.spawn()`.
47+
* @returns {Promise<{stdout: string, stderr: string}>}
48+
*/
49+
function spawn(command, args, options = {}) {
50+
return new Promise((resolve, reject) => {
51+
const child = childProcess.spawn(command, args, {...options, shell: true});
52+
53+
let stdout = '';
54+
let stderr = '';
55+
56+
child.stdout.on('data', data => {
57+
stdout += data.toString();
58+
});
59+
60+
child.stderr.on('data', data => {
61+
stderr += data.toString();
62+
});
63+
64+
child.on('exit', (code, signal) => {
65+
if (signal) {
66+
reject(new SpawnError(`Process terminated due to signal ${signal}`, stderr, stdout));
67+
} else if (code) {
68+
reject(new SpawnError(`Process exited with code ${code}`, stderr, stdout));
69+
} else {
70+
resolve({stdout, stderr});
71+
}
72+
});
73+
74+
child.on('error', reject);
75+
});
76+
}
2077

2178
/**
2279
* Representation of the feature set that is configured for Chrome. This
@@ -188,7 +245,7 @@ export class BuildError extends Error {
188245
* Constructs a new `BuildError` with the given parameters.
189246
*
190247
* @param step - the build step that failed.
191-
* @param options - additional options for the `BuildError`.
248+
* @param {object} options - additional options for the `BuildError`.
192249
* @param options.cause - the actual cause for the build error.
193250
* @param options.outDir - the absolute path to the `target` out directory.
194251
* @param options.target - the target relative to `//out`.
@@ -225,7 +282,7 @@ export async function prepareBuild(target) {
225282
try {
226283
const gnExe = vpython3ExecutablePath();
227284
const gnArgs = [gnPyPath(), '-q', 'gen', outDir];
228-
await execFile(gnExe, gnArgs);
285+
await spawn(gnExe, gnArgs);
229286
} catch (cause) {
230287
throw new BuildError(BuildStep.GN, {cause, outDir, target});
231288
}
@@ -246,7 +303,7 @@ function gnArgsForTarget(target) {
246303
const cwd = rootPath();
247304
const gnExe = vpython3ExecutablePath();
248305
const gnArgs = [gnPyPath(), '-q', 'args', outDir, '--json', '--list', '--short'];
249-
const {stdout} = await execFile(gnExe, gnArgs, {cwd});
306+
const {stdout} = await spawn(gnExe, gnArgs, {cwd});
250307
return new Map(JSON.parse(stdout).map(arg => [arg.name, arg.current?.value ?? arg.default?.value]));
251308
} catch {
252309
return new Map();
@@ -273,7 +330,7 @@ function gnRefsForTarget(target, filename) {
273330
const outDir = path.join(rootPath(), 'out', target);
274331
const gnExe = vpython3ExecutablePath();
275332
const gnArgs = [gnPyPath(), 'refs', outDir, '--as=output', filename];
276-
const {stdout} = await execFile(gnExe, gnArgs, {cwd});
333+
const {stdout} = await spawn(gnExe, gnArgs, {cwd});
277334
return stdout.trim().split('\n');
278335
})();
279336
gnRefsPerTarget.set(filename, gnRef);
@@ -325,7 +382,7 @@ export async function build(target, signal, filenames) {
325382
try {
326383
const autoninjaExe = vpython3ExecutablePath();
327384
const autoninjaArgs = [autoninjaPyPath(), '-C', outDir, ...buildTargets];
328-
await execFile(autoninjaExe, autoninjaArgs, {signal});
385+
await spawn(autoninjaExe, autoninjaArgs, {shell: true, signal});
329386
} catch (cause) {
330387
if (cause.name === 'AbortError') {
331388
throw cause;

0 commit comments

Comments
 (0)