xref: /aosp_15_r20/external/perfetto/ui/build.js (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker// Copyright (C) 2021 The Android Open Source Project
2*6dbdd20aSAndroid Build Coastguard Worker//
3*6dbdd20aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License");
4*6dbdd20aSAndroid Build Coastguard Worker// you may not use this file except in compliance with the License.
5*6dbdd20aSAndroid Build Coastguard Worker// You may obtain a copy of the License at
6*6dbdd20aSAndroid Build Coastguard Worker//
7*6dbdd20aSAndroid Build Coastguard Worker//      http://www.apache.org/licenses/LICENSE-2.0
8*6dbdd20aSAndroid Build Coastguard Worker//
9*6dbdd20aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software
10*6dbdd20aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS,
11*6dbdd20aSAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*6dbdd20aSAndroid Build Coastguard Worker// See the License for the specific language governing permissions and
13*6dbdd20aSAndroid Build Coastguard Worker// limitations under the License.
14*6dbdd20aSAndroid Build Coastguard Worker
15*6dbdd20aSAndroid Build Coastguard Worker'use strict';
16*6dbdd20aSAndroid Build Coastguard Worker
17*6dbdd20aSAndroid Build Coastguard Worker
18*6dbdd20aSAndroid Build Coastguard Worker// This script takes care of:
19*6dbdd20aSAndroid Build Coastguard Worker// - The build process for the whole UI and the chrome extension.
20*6dbdd20aSAndroid Build Coastguard Worker// - The HTTP dev-server with live-reload capabilities.
21*6dbdd20aSAndroid Build Coastguard Worker// The reason why this is a hand-rolled script rather than a conventional build
22*6dbdd20aSAndroid Build Coastguard Worker// system is keeping incremental build fast and maintaining the set of
23*6dbdd20aSAndroid Build Coastguard Worker// dependencies contained.
24*6dbdd20aSAndroid Build Coastguard Worker// The only way to keep incremental build fast (i.e. O(seconds) for the
25*6dbdd20aSAndroid Build Coastguard Worker// edit-one-line -> reload html cycles) is to run both the TypeScript compiler
26*6dbdd20aSAndroid Build Coastguard Worker// and the rollup bundler in --watch mode. Any other attempt, leads to O(10s)
27*6dbdd20aSAndroid Build Coastguard Worker// incremental-build times.
28*6dbdd20aSAndroid Build Coastguard Worker// This script allows mixing build tools that support --watch mode (tsc and
29*6dbdd20aSAndroid Build Coastguard Worker// rollup) and auto-triggering-on-file-change rules via fs.watch.
30*6dbdd20aSAndroid Build Coastguard Worker// When invoked without any argument (e.g., for production builds), this script
31*6dbdd20aSAndroid Build Coastguard Worker// just runs all the build tasks serially. It doesn't to do any mtime-based
32*6dbdd20aSAndroid Build Coastguard Worker// check, it always re-runs all the tasks.
33*6dbdd20aSAndroid Build Coastguard Worker// When invoked with --watch, it mounts a pipeline of tasks based on fs.watch
34*6dbdd20aSAndroid Build Coastguard Worker// and runs them together with tsc --watch and rollup --watch.
35*6dbdd20aSAndroid Build Coastguard Worker// The output directory structure is carefully crafted so that any change to UI
36*6dbdd20aSAndroid Build Coastguard Worker// sources causes cascading triggers of the next steps.
37*6dbdd20aSAndroid Build Coastguard Worker// The overall build graph looks as follows:
38*6dbdd20aSAndroid Build Coastguard Worker// +----------------+      +-----------------------------+
39*6dbdd20aSAndroid Build Coastguard Worker// | protos/*.proto |----->| pbjs out/tsc/gen/protos.js  |--+
40*6dbdd20aSAndroid Build Coastguard Worker// +----------------+      +-----------------------------+  |
41*6dbdd20aSAndroid Build Coastguard Worker//                         +-----------------------------+  |
42*6dbdd20aSAndroid Build Coastguard Worker//                         | pbts out/tsc/gen/protos.d.ts|<-+
43*6dbdd20aSAndroid Build Coastguard Worker//                         +-----------------------------+
44*6dbdd20aSAndroid Build Coastguard Worker//                             |
45*6dbdd20aSAndroid Build Coastguard Worker//                             V      +-------------------------+
46*6dbdd20aSAndroid Build Coastguard Worker// +---------+              +-----+   |  out/tsc/frontend/*.js  |
47*6dbdd20aSAndroid Build Coastguard Worker// | ui/*.ts |------------->| tsc |-> +-------------------------+   +--------+
48*6dbdd20aSAndroid Build Coastguard Worker// +---------+              +-----+   | out/tsc/controller/*.js |-->| rollup |
49*6dbdd20aSAndroid Build Coastguard Worker//                            ^       +-------------------------+   +--------+
50*6dbdd20aSAndroid Build Coastguard Worker//                +------------+      |   out/tsc/engine/*.js   |       |
51*6dbdd20aSAndroid Build Coastguard Worker// +-----------+  |*.wasm.js   |      +-------------------------+       |
52*6dbdd20aSAndroid Build Coastguard Worker// |ninja *.cc |->|*.wasm.d.ts |                                        |
53*6dbdd20aSAndroid Build Coastguard Worker// +-----------+  |*.wasm      |-----------------+                      |
54*6dbdd20aSAndroid Build Coastguard Worker//                +------------+                 |                      |
55*6dbdd20aSAndroid Build Coastguard Worker//                                               V                      V
56*6dbdd20aSAndroid Build Coastguard Worker// +-----------+  +------+    +------------------------------------------------+
57*6dbdd20aSAndroid Build Coastguard Worker// | ui/*.scss |->| scss |--->|              Final out/dist/ dir               |
58*6dbdd20aSAndroid Build Coastguard Worker// +-----------+  +------+    +------------------------------------------------+
59*6dbdd20aSAndroid Build Coastguard Worker// +----------------------+   | +----------+ +---------+ +--------------------+|
60*6dbdd20aSAndroid Build Coastguard Worker// | src/assets/*.png     |   | | assets/  | |*.wasm.js| | frontend_bundle.js ||
61*6dbdd20aSAndroid Build Coastguard Worker// +----------------------+   | |  *.css   | |*.wasm   | +--------------------+|
62*6dbdd20aSAndroid Build Coastguard Worker// | buildtools/typefaces |-->| |  *.png   | +---------+ |  engine_bundle.js  ||
63*6dbdd20aSAndroid Build Coastguard Worker// +----------------------+   | |  *.woff2 |             +--------------------+|
64*6dbdd20aSAndroid Build Coastguard Worker// | buildtools/legacy_tv |   | |  tv.html |             |traceconv_bundle.js ||
65*6dbdd20aSAndroid Build Coastguard Worker// +----------------------+   | +----------+             +--------------------+|
66*6dbdd20aSAndroid Build Coastguard Worker//                            +------------------------------------------------+
67*6dbdd20aSAndroid Build Coastguard Worker
68*6dbdd20aSAndroid Build Coastguard Workerconst argparse = require('argparse');
69*6dbdd20aSAndroid Build Coastguard Workerconst childProcess = require('child_process');
70*6dbdd20aSAndroid Build Coastguard Workerconst crypto = require('crypto');
71*6dbdd20aSAndroid Build Coastguard Workerconst fs = require('fs');
72*6dbdd20aSAndroid Build Coastguard Workerconst http = require('http');
73*6dbdd20aSAndroid Build Coastguard Workerconst path = require('path');
74*6dbdd20aSAndroid Build Coastguard Workerconst pjoin = path.join;
75*6dbdd20aSAndroid Build Coastguard Worker
76*6dbdd20aSAndroid Build Coastguard Workerconst ROOT_DIR = path.dirname(__dirname);  // The repo root.
77*6dbdd20aSAndroid Build Coastguard Workerconst VERSION_SCRIPT = pjoin(ROOT_DIR, 'tools/write_version_header.py');
78*6dbdd20aSAndroid Build Coastguard Workerconst GEN_IMPORTS_SCRIPT = pjoin(ROOT_DIR, 'tools/gen_ui_imports');
79*6dbdd20aSAndroid Build Coastguard Worker
80*6dbdd20aSAndroid Build Coastguard Workerconst cfg = {
81*6dbdd20aSAndroid Build Coastguard Worker  minifyJs: '',
82*6dbdd20aSAndroid Build Coastguard Worker  watch: false,
83*6dbdd20aSAndroid Build Coastguard Worker  verbose: false,
84*6dbdd20aSAndroid Build Coastguard Worker  debug: false,
85*6dbdd20aSAndroid Build Coastguard Worker  bigtrace: false,
86*6dbdd20aSAndroid Build Coastguard Worker  startHttpServer: false,
87*6dbdd20aSAndroid Build Coastguard Worker  httpServerListenHost: '127.0.0.1',
88*6dbdd20aSAndroid Build Coastguard Worker  httpServerListenPort: 10000,
89*6dbdd20aSAndroid Build Coastguard Worker  wasmModules: ['trace_processor', 'traceconv', 'trace_config_utils'],
90*6dbdd20aSAndroid Build Coastguard Worker  crossOriginIsolation: false,
91*6dbdd20aSAndroid Build Coastguard Worker  testFilter: '',
92*6dbdd20aSAndroid Build Coastguard Worker  noOverrideGnArgs: false,
93*6dbdd20aSAndroid Build Coastguard Worker
94*6dbdd20aSAndroid Build Coastguard Worker  // The fields below will be changed by main() after cmdline parsing.
95*6dbdd20aSAndroid Build Coastguard Worker  // Directory structure:
96*6dbdd20aSAndroid Build Coastguard Worker  // out/xxx/    -> outDir         : Root build dir, for both ninja/wasm and UI.
97*6dbdd20aSAndroid Build Coastguard Worker  //   ui/       -> outUiDir       : UI dir. All outputs from this script.
98*6dbdd20aSAndroid Build Coastguard Worker  //    tsc/     -> outTscDir      : Transpiled .ts -> .js.
99*6dbdd20aSAndroid Build Coastguard Worker  //      gen/   -> outGenDir      : Auto-generated .ts/.js (e.g. protos).
100*6dbdd20aSAndroid Build Coastguard Worker  //    dist/    -> outDistRootDir : Only index.html and service_worker.js
101*6dbdd20aSAndroid Build Coastguard Worker  //      v1.2/  -> outDistDir     : JS bundles and assets
102*6dbdd20aSAndroid Build Coastguard Worker  //    chrome_extension/          : Chrome extension.
103*6dbdd20aSAndroid Build Coastguard Worker  outDir: pjoin(ROOT_DIR, 'out/ui'),
104*6dbdd20aSAndroid Build Coastguard Worker  version: '',  // v1.2.3, derived from the CHANGELOG + git.
105*6dbdd20aSAndroid Build Coastguard Worker  outUiDir: '',
106*6dbdd20aSAndroid Build Coastguard Worker  outUiTestArtifactsDir: '',
107*6dbdd20aSAndroid Build Coastguard Worker  outDistRootDir: '',
108*6dbdd20aSAndroid Build Coastguard Worker  outTscDir: '',
109*6dbdd20aSAndroid Build Coastguard Worker  outGenDir: '',
110*6dbdd20aSAndroid Build Coastguard Worker  outDistDir: '',
111*6dbdd20aSAndroid Build Coastguard Worker  outExtDir: '',
112*6dbdd20aSAndroid Build Coastguard Worker  outBigtraceDistDir: '',
113*6dbdd20aSAndroid Build Coastguard Worker  outOpenPerfettoTraceDistDir: '',
114*6dbdd20aSAndroid Build Coastguard Worker};
115*6dbdd20aSAndroid Build Coastguard Worker
116*6dbdd20aSAndroid Build Coastguard Workerconst RULES = [
117*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/assets\/index.html/, f: copyIndexHtml},
118*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/assets\/bigtrace.html/, f: copyBigtraceHtml},
119*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/open_perfetto_trace\/index.html/, f: copyOpenPerfettoTraceHtml},
120*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/assets\/((.*)[.]png)/, f: copyAssets},
121*6dbdd20aSAndroid Build Coastguard Worker  {r: /buildtools\/typefaces\/(.+[.]woff2)/, f: copyAssets},
122*6dbdd20aSAndroid Build Coastguard Worker  {r: /buildtools\/catapult_trace_viewer\/(.+(js|html))/, f: copyAssets},
123*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/assets\/.+[.]scss/, f: compileScss},
124*6dbdd20aSAndroid Build Coastguard Worker  {r: /ui\/src\/chrome_extension\/.*/, f: copyExtensionAssets},
125*6dbdd20aSAndroid Build Coastguard Worker  {r: /.*\/dist\/.+\/(?!manifest\.json).*/, f: genServiceWorkerManifestJson},
126*6dbdd20aSAndroid Build Coastguard Worker  {r: /.*\/dist\/.*[.](js|html|css|wasm)$/, f: notifyLiveServer},
127*6dbdd20aSAndroid Build Coastguard Worker];
128*6dbdd20aSAndroid Build Coastguard Worker
129*6dbdd20aSAndroid Build Coastguard Workerconst tasks = [];
130*6dbdd20aSAndroid Build Coastguard Workerlet tasksTot = 0;
131*6dbdd20aSAndroid Build Coastguard Workerlet tasksRan = 0;
132*6dbdd20aSAndroid Build Coastguard Workerconst httpWatches = [];
133*6dbdd20aSAndroid Build Coastguard Workerconst tStart = Date.now();
134*6dbdd20aSAndroid Build Coastguard Workerconst subprocesses = [];
135*6dbdd20aSAndroid Build Coastguard Worker
136*6dbdd20aSAndroid Build Coastguard Workerasync function main() {
137*6dbdd20aSAndroid Build Coastguard Worker  const parser = new argparse.ArgumentParser();
138*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--out', {help: 'Output directory'});
139*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--minify-js', {
140*6dbdd20aSAndroid Build Coastguard Worker    help: 'Minify js files',
141*6dbdd20aSAndroid Build Coastguard Worker    choices: ['preserve_comments', 'all'],
142*6dbdd20aSAndroid Build Coastguard Worker  });
143*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--watch', '-w', {action: 'store_true'});
144*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--serve', '-s', {action: 'store_true'});
145*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--serve-host', {help: '--serve bind host'});
146*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--serve-port', {help: '--serve bind port', type: 'int'});
147*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--verbose', '-v', {action: 'store_true'});
148*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-build', '-n', {action: 'store_true'});
149*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-wasm', '-W', {action: 'store_true'});
150*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--run-unittests', '-t', {action: 'store_true'});
151*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--debug', '-d', {action: 'store_true'});
152*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--bigtrace', {action: 'store_true'});
153*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--open-perfetto-trace', {action: 'store_true'});
154*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--interactive', '-i', {action: 'store_true'});
155*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--rebaseline', '-r', {action: 'store_true'});
156*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-depscheck', {action: 'store_true'});
157*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--cross-origin-isolation', {action: 'store_true'});
158*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--test-filter', '-f', {
159*6dbdd20aSAndroid Build Coastguard Worker    help: 'filter Jest tests by regex, e.g. \'chrome_render\'',
160*6dbdd20aSAndroid Build Coastguard Worker  });
161*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument('--no-override-gn-args', {action: 'store_true'});
162*6dbdd20aSAndroid Build Coastguard Worker
163*6dbdd20aSAndroid Build Coastguard Worker  const args = parser.parse_args();
164*6dbdd20aSAndroid Build Coastguard Worker  const clean = !args.no_build;
165*6dbdd20aSAndroid Build Coastguard Worker  cfg.outDir = path.resolve(ensureDir(args.out || cfg.outDir));
166*6dbdd20aSAndroid Build Coastguard Worker  cfg.outUiDir = ensureDir(pjoin(cfg.outDir, 'ui'), clean);
167*6dbdd20aSAndroid Build Coastguard Worker  cfg.outUiTestArtifactsDir = ensureDir(pjoin(cfg.outDir, 'ui-test-artifacts'));
168*6dbdd20aSAndroid Build Coastguard Worker  cfg.outExtDir = ensureDir(pjoin(cfg.outUiDir, 'chrome_extension'));
169*6dbdd20aSAndroid Build Coastguard Worker  cfg.outDistRootDir = ensureDir(pjoin(cfg.outUiDir, 'dist'));
170*6dbdd20aSAndroid Build Coastguard Worker  const proc = exec('python3', [VERSION_SCRIPT, '--stdout'], {stdout: 'pipe'});
171*6dbdd20aSAndroid Build Coastguard Worker  cfg.version = proc.stdout.toString().trim();
172*6dbdd20aSAndroid Build Coastguard Worker  cfg.outDistDir = ensureDir(pjoin(cfg.outDistRootDir, cfg.version));
173*6dbdd20aSAndroid Build Coastguard Worker  cfg.outTscDir = ensureDir(pjoin(cfg.outUiDir, 'tsc'));
174*6dbdd20aSAndroid Build Coastguard Worker  cfg.outGenDir = ensureDir(pjoin(cfg.outUiDir, 'tsc/gen'));
175*6dbdd20aSAndroid Build Coastguard Worker  cfg.testFilter = args.test_filter || '';
176*6dbdd20aSAndroid Build Coastguard Worker  cfg.watch = !!args.watch;
177*6dbdd20aSAndroid Build Coastguard Worker  cfg.verbose = !!args.verbose;
178*6dbdd20aSAndroid Build Coastguard Worker  cfg.debug = !!args.debug;
179*6dbdd20aSAndroid Build Coastguard Worker  cfg.bigtrace = !!args.bigtrace;
180*6dbdd20aSAndroid Build Coastguard Worker  cfg.openPerfettoTrace = !!args.open_perfetto_trace;
181*6dbdd20aSAndroid Build Coastguard Worker  cfg.startHttpServer = args.serve;
182*6dbdd20aSAndroid Build Coastguard Worker  cfg.noOverrideGnArgs = !!args.no_override_gn_args;
183*6dbdd20aSAndroid Build Coastguard Worker  if (args.minify_js) {
184*6dbdd20aSAndroid Build Coastguard Worker    cfg.minifyJs = args.minify_js;
185*6dbdd20aSAndroid Build Coastguard Worker  }
186*6dbdd20aSAndroid Build Coastguard Worker  if (args.bigtrace) {
187*6dbdd20aSAndroid Build Coastguard Worker    cfg.outBigtraceDistDir = ensureDir(pjoin(cfg.outDistDir, 'bigtrace'));
188*6dbdd20aSAndroid Build Coastguard Worker  }
189*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.openPerfettoTrace) {
190*6dbdd20aSAndroid Build Coastguard Worker    cfg.outOpenPerfettoTraceDistDir = ensureDir(pjoin(cfg.outDistRootDir,
191*6dbdd20aSAndroid Build Coastguard Worker                                                      'open_perfetto_trace'));
192*6dbdd20aSAndroid Build Coastguard Worker  }
193*6dbdd20aSAndroid Build Coastguard Worker  if (args.serve_host) {
194*6dbdd20aSAndroid Build Coastguard Worker    cfg.httpServerListenHost = args.serve_host;
195*6dbdd20aSAndroid Build Coastguard Worker  }
196*6dbdd20aSAndroid Build Coastguard Worker  if (args.serve_port) {
197*6dbdd20aSAndroid Build Coastguard Worker    cfg.httpServerListenPort = args.serve_port;
198*6dbdd20aSAndroid Build Coastguard Worker  }
199*6dbdd20aSAndroid Build Coastguard Worker  if (args.interactive) {
200*6dbdd20aSAndroid Build Coastguard Worker    process.env.PERFETTO_UI_TESTS_INTERACTIVE = '1';
201*6dbdd20aSAndroid Build Coastguard Worker  }
202*6dbdd20aSAndroid Build Coastguard Worker  if (args.rebaseline) {
203*6dbdd20aSAndroid Build Coastguard Worker    process.env.PERFETTO_UI_TESTS_REBASELINE = '1';
204*6dbdd20aSAndroid Build Coastguard Worker  }
205*6dbdd20aSAndroid Build Coastguard Worker  if (args.cross_origin_isolation) {
206*6dbdd20aSAndroid Build Coastguard Worker    cfg.crossOriginIsolation = true;
207*6dbdd20aSAndroid Build Coastguard Worker  }
208*6dbdd20aSAndroid Build Coastguard Worker
209*6dbdd20aSAndroid Build Coastguard Worker  process.on('SIGINT', () => {
210*6dbdd20aSAndroid Build Coastguard Worker    console.log('\nSIGINT received. Killing all child processes and exiting');
211*6dbdd20aSAndroid Build Coastguard Worker    for (const proc of subprocesses) {
212*6dbdd20aSAndroid Build Coastguard Worker      if (proc) proc.kill('SIGINT');
213*6dbdd20aSAndroid Build Coastguard Worker    }
214*6dbdd20aSAndroid Build Coastguard Worker    process.exit(130);  // 130 -> Same behavior of bash when killed by SIGINT.
215*6dbdd20aSAndroid Build Coastguard Worker  });
216*6dbdd20aSAndroid Build Coastguard Worker
217*6dbdd20aSAndroid Build Coastguard Worker  if (!args.no_depscheck) {
218*6dbdd20aSAndroid Build Coastguard Worker    // Check that deps are current before starting.
219*6dbdd20aSAndroid Build Coastguard Worker    const installBuildDeps = pjoin(ROOT_DIR, 'tools/install-build-deps');
220*6dbdd20aSAndroid Build Coastguard Worker    const checkDepsPath = pjoin(cfg.outDir, '.check_deps');
221*6dbdd20aSAndroid Build Coastguard Worker    let args = [installBuildDeps, `--check-only=${checkDepsPath}`, '--ui'];
222*6dbdd20aSAndroid Build Coastguard Worker
223*6dbdd20aSAndroid Build Coastguard Worker    if (process.platform === 'darwin') {
224*6dbdd20aSAndroid Build Coastguard Worker      const result = childProcess.spawnSync('arch', ['-arm64', 'true']);
225*6dbdd20aSAndroid Build Coastguard Worker      const isArm64Capable = result.status === 0;
226*6dbdd20aSAndroid Build Coastguard Worker      if (isArm64Capable) {
227*6dbdd20aSAndroid Build Coastguard Worker        const archArgs = [
228*6dbdd20aSAndroid Build Coastguard Worker          'arch',
229*6dbdd20aSAndroid Build Coastguard Worker          '-arch',
230*6dbdd20aSAndroid Build Coastguard Worker          'arm64',
231*6dbdd20aSAndroid Build Coastguard Worker        ];
232*6dbdd20aSAndroid Build Coastguard Worker        args = archArgs.concat(args);
233*6dbdd20aSAndroid Build Coastguard Worker      }
234*6dbdd20aSAndroid Build Coastguard Worker    }
235*6dbdd20aSAndroid Build Coastguard Worker    const cmd = args.shift();
236*6dbdd20aSAndroid Build Coastguard Worker    exec(cmd, args);
237*6dbdd20aSAndroid Build Coastguard Worker  }
238*6dbdd20aSAndroid Build Coastguard Worker
239*6dbdd20aSAndroid Build Coastguard Worker  console.log('Entering', cfg.outDir);
240*6dbdd20aSAndroid Build Coastguard Worker  process.chdir(cfg.outDir);
241*6dbdd20aSAndroid Build Coastguard Worker
242*6dbdd20aSAndroid Build Coastguard Worker  // Enqueue empty task. This is needed only for --no-build --serve. The HTTP
243*6dbdd20aSAndroid Build Coastguard Worker  // server is started when the task queue reaches quiescence, but it takes at
244*6dbdd20aSAndroid Build Coastguard Worker  // least one task for that.
245*6dbdd20aSAndroid Build Coastguard Worker  addTask(() => {});
246*6dbdd20aSAndroid Build Coastguard Worker
247*6dbdd20aSAndroid Build Coastguard Worker  if (!args.no_build) {
248*6dbdd20aSAndroid Build Coastguard Worker    updateSymlinks();  // Links //ui/out -> //out/xxx/ui/
249*6dbdd20aSAndroid Build Coastguard Worker
250*6dbdd20aSAndroid Build Coastguard Worker    buildWasm(args.no_wasm);
251*6dbdd20aSAndroid Build Coastguard Worker    generateImports('ui/src/core_plugins', 'all_core_plugins');
252*6dbdd20aSAndroid Build Coastguard Worker    generateImports('ui/src/plugins', 'all_plugins');
253*6dbdd20aSAndroid Build Coastguard Worker    scanDir('ui/src/assets');
254*6dbdd20aSAndroid Build Coastguard Worker    scanDir('ui/src/chrome_extension');
255*6dbdd20aSAndroid Build Coastguard Worker    scanDir('buildtools/typefaces');
256*6dbdd20aSAndroid Build Coastguard Worker    scanDir('buildtools/catapult_trace_viewer');
257*6dbdd20aSAndroid Build Coastguard Worker    compileProtos();
258*6dbdd20aSAndroid Build Coastguard Worker    genVersion();
259*6dbdd20aSAndroid Build Coastguard Worker    generateStdlibDocs();
260*6dbdd20aSAndroid Build Coastguard Worker
261*6dbdd20aSAndroid Build Coastguard Worker    const tsProjects = [
262*6dbdd20aSAndroid Build Coastguard Worker      'ui',
263*6dbdd20aSAndroid Build Coastguard Worker      'ui/src/service_worker'
264*6dbdd20aSAndroid Build Coastguard Worker    ];
265*6dbdd20aSAndroid Build Coastguard Worker    if (cfg.bigtrace) tsProjects.push('ui/src/bigtrace');
266*6dbdd20aSAndroid Build Coastguard Worker    if (cfg.openPerfettoTrace) {
267*6dbdd20aSAndroid Build Coastguard Worker      scanDir('ui/src/open_perfetto_trace');
268*6dbdd20aSAndroid Build Coastguard Worker      tsProjects.push('ui/src/open_perfetto_trace');
269*6dbdd20aSAndroid Build Coastguard Worker    }
270*6dbdd20aSAndroid Build Coastguard Worker
271*6dbdd20aSAndroid Build Coastguard Worker
272*6dbdd20aSAndroid Build Coastguard Worker    for (const prj of tsProjects) {
273*6dbdd20aSAndroid Build Coastguard Worker      transpileTsProject(prj);
274*6dbdd20aSAndroid Build Coastguard Worker    }
275*6dbdd20aSAndroid Build Coastguard Worker
276*6dbdd20aSAndroid Build Coastguard Worker    if (cfg.watch) {
277*6dbdd20aSAndroid Build Coastguard Worker      for (const prj of tsProjects) {
278*6dbdd20aSAndroid Build Coastguard Worker        transpileTsProject(prj, {watch: cfg.watch});
279*6dbdd20aSAndroid Build Coastguard Worker      }
280*6dbdd20aSAndroid Build Coastguard Worker    }
281*6dbdd20aSAndroid Build Coastguard Worker
282*6dbdd20aSAndroid Build Coastguard Worker    bundleJs('rollup.config.js');
283*6dbdd20aSAndroid Build Coastguard Worker    genServiceWorkerManifestJson();
284*6dbdd20aSAndroid Build Coastguard Worker
285*6dbdd20aSAndroid Build Coastguard Worker    // Watches the /dist. When changed:
286*6dbdd20aSAndroid Build Coastguard Worker    // - Notifies the HTTP live reload clients.
287*6dbdd20aSAndroid Build Coastguard Worker    // - Regenerates the ServiceWorker file map.
288*6dbdd20aSAndroid Build Coastguard Worker    scanDir(cfg.outDistRootDir);
289*6dbdd20aSAndroid Build Coastguard Worker  }
290*6dbdd20aSAndroid Build Coastguard Worker
291*6dbdd20aSAndroid Build Coastguard Worker  // We should enter the loop only in watch mode, where tsc and rollup are
292*6dbdd20aSAndroid Build Coastguard Worker  // asynchronous because they run in watch mode.
293*6dbdd20aSAndroid Build Coastguard Worker  if (args.no_build && !isDistComplete()) {
294*6dbdd20aSAndroid Build Coastguard Worker    console.log('No build was requested, but artifacts are not available.');
295*6dbdd20aSAndroid Build Coastguard Worker    console.log('In case of execution error, re-run without --no-build.');
296*6dbdd20aSAndroid Build Coastguard Worker  }
297*6dbdd20aSAndroid Build Coastguard Worker  if (!args.no_build) {
298*6dbdd20aSAndroid Build Coastguard Worker    const tStart = Date.now();
299*6dbdd20aSAndroid Build Coastguard Worker    while (!isDistComplete()) {
300*6dbdd20aSAndroid Build Coastguard Worker      const secs = Math.ceil((Date.now() - tStart) / 1000);
301*6dbdd20aSAndroid Build Coastguard Worker      process.stdout.write(
302*6dbdd20aSAndroid Build Coastguard Worker          `\t\tWaiting for first build to complete... ${secs} s\r`);
303*6dbdd20aSAndroid Build Coastguard Worker      await new Promise((r) => setTimeout(r, 500));
304*6dbdd20aSAndroid Build Coastguard Worker    }
305*6dbdd20aSAndroid Build Coastguard Worker  }
306*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.watch) console.log('\nFirst build completed!');
307*6dbdd20aSAndroid Build Coastguard Worker
308*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.startHttpServer) {
309*6dbdd20aSAndroid Build Coastguard Worker    startServer();
310*6dbdd20aSAndroid Build Coastguard Worker  }
311*6dbdd20aSAndroid Build Coastguard Worker  if (args.run_unittests) {
312*6dbdd20aSAndroid Build Coastguard Worker    runTests('jest.unittest.config.js');
313*6dbdd20aSAndroid Build Coastguard Worker  }
314*6dbdd20aSAndroid Build Coastguard Worker}
315*6dbdd20aSAndroid Build Coastguard Worker
316*6dbdd20aSAndroid Build Coastguard Worker// -----------
317*6dbdd20aSAndroid Build Coastguard Worker// Build rules
318*6dbdd20aSAndroid Build Coastguard Worker// -----------
319*6dbdd20aSAndroid Build Coastguard Worker
320*6dbdd20aSAndroid Build Coastguard Workerfunction runTests(cfgFile) {
321*6dbdd20aSAndroid Build Coastguard Worker  const args = [
322*6dbdd20aSAndroid Build Coastguard Worker    '--rootDir',
323*6dbdd20aSAndroid Build Coastguard Worker    cfg.outTscDir,
324*6dbdd20aSAndroid Build Coastguard Worker    '--verbose',
325*6dbdd20aSAndroid Build Coastguard Worker    '--runInBand',
326*6dbdd20aSAndroid Build Coastguard Worker    '--detectOpenHandles',
327*6dbdd20aSAndroid Build Coastguard Worker    '--forceExit',
328*6dbdd20aSAndroid Build Coastguard Worker    '--projects',
329*6dbdd20aSAndroid Build Coastguard Worker    pjoin(ROOT_DIR, 'ui/config', cfgFile),
330*6dbdd20aSAndroid Build Coastguard Worker  ];
331*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.testFilter.length > 0) {
332*6dbdd20aSAndroid Build Coastguard Worker    args.push('-t', cfg.testFilter);
333*6dbdd20aSAndroid Build Coastguard Worker  }
334*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.watch) {
335*6dbdd20aSAndroid Build Coastguard Worker    args.push('--watchAll');
336*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['jest', args, {async: true}]);
337*6dbdd20aSAndroid Build Coastguard Worker  } else {
338*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['jest', args]);
339*6dbdd20aSAndroid Build Coastguard Worker  }
340*6dbdd20aSAndroid Build Coastguard Worker}
341*6dbdd20aSAndroid Build Coastguard Worker
342*6dbdd20aSAndroid Build Coastguard Workerfunction cpHtml(src, filename) {
343*6dbdd20aSAndroid Build Coastguard Worker  let html = fs.readFileSync(src).toString();
344*6dbdd20aSAndroid Build Coastguard Worker  // First copy the html as-is into the dist/v1.2.3/ directory. This is
345*6dbdd20aSAndroid Build Coastguard Worker  // only used for archival purporses, so one can open
346*6dbdd20aSAndroid Build Coastguard Worker  // ui.perfetto.dev/v1.2.3/ to skip the auto-update and channel logic.
347*6dbdd20aSAndroid Build Coastguard Worker  fs.writeFileSync(pjoin(cfg.outDistDir, filename), html);
348*6dbdd20aSAndroid Build Coastguard Worker
349*6dbdd20aSAndroid Build Coastguard Worker  // Then copy it into the dist/ root by patching the version code.
350*6dbdd20aSAndroid Build Coastguard Worker  // TODO(primiano): in next CLs, this script should take a
351*6dbdd20aSAndroid Build Coastguard Worker  // --release_map=xxx.json argument, to populate this with multiple channels.
352*6dbdd20aSAndroid Build Coastguard Worker  const versionMap = JSON.stringify({'stable': cfg.version});
353*6dbdd20aSAndroid Build Coastguard Worker  const bodyRegex = /data-perfetto_version='[^']*'/;
354*6dbdd20aSAndroid Build Coastguard Worker  html = html.replace(bodyRegex, `data-perfetto_version='${versionMap}'`);
355*6dbdd20aSAndroid Build Coastguard Worker  fs.writeFileSync(pjoin(cfg.outDistRootDir, filename), html);
356*6dbdd20aSAndroid Build Coastguard Worker}
357*6dbdd20aSAndroid Build Coastguard Worker
358*6dbdd20aSAndroid Build Coastguard Workerfunction copyIndexHtml(src) {
359*6dbdd20aSAndroid Build Coastguard Worker  addTask(cpHtml, [src, 'index.html']);
360*6dbdd20aSAndroid Build Coastguard Worker}
361*6dbdd20aSAndroid Build Coastguard Worker
362*6dbdd20aSAndroid Build Coastguard Workerfunction copyBigtraceHtml(src) {
363*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.bigtrace) {
364*6dbdd20aSAndroid Build Coastguard Worker    addTask(cpHtml, [src, 'bigtrace.html']);
365*6dbdd20aSAndroid Build Coastguard Worker  }
366*6dbdd20aSAndroid Build Coastguard Worker}
367*6dbdd20aSAndroid Build Coastguard Worker
368*6dbdd20aSAndroid Build Coastguard Workerfunction copyOpenPerfettoTraceHtml(src) {
369*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.openPerfettoTrace) {
370*6dbdd20aSAndroid Build Coastguard Worker    addTask(cp, [src, pjoin(cfg.outOpenPerfettoTraceDistDir, 'index.html')]);
371*6dbdd20aSAndroid Build Coastguard Worker  }
372*6dbdd20aSAndroid Build Coastguard Worker}
373*6dbdd20aSAndroid Build Coastguard Worker
374*6dbdd20aSAndroid Build Coastguard Workerfunction copyAssets(src, dst) {
375*6dbdd20aSAndroid Build Coastguard Worker  addTask(cp, [src, pjoin(cfg.outDistDir, 'assets', dst)]);
376*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.bigtrace) {
377*6dbdd20aSAndroid Build Coastguard Worker    addTask(cp, [src, pjoin(cfg.outBigtraceDistDir, 'assets', dst)]);
378*6dbdd20aSAndroid Build Coastguard Worker  }
379*6dbdd20aSAndroid Build Coastguard Worker}
380*6dbdd20aSAndroid Build Coastguard Worker
381*6dbdd20aSAndroid Build Coastguard Workerfunction copyUiTestArtifactsAssets(src, dst) {
382*6dbdd20aSAndroid Build Coastguard Worker  addTask(cp, [src, pjoin(cfg.outUiTestArtifactsDir, dst)]);
383*6dbdd20aSAndroid Build Coastguard Worker}
384*6dbdd20aSAndroid Build Coastguard Worker
385*6dbdd20aSAndroid Build Coastguard Workerfunction compileScss() {
386*6dbdd20aSAndroid Build Coastguard Worker  const src = pjoin(ROOT_DIR, 'ui/src/assets/perfetto.scss');
387*6dbdd20aSAndroid Build Coastguard Worker  const dst = pjoin(cfg.outDistDir, 'perfetto.css');
388*6dbdd20aSAndroid Build Coastguard Worker  // In watch mode, don't exit(1) if scss fails. It can easily happen by
389*6dbdd20aSAndroid Build Coastguard Worker  // having a typo in the css. It will still print an error.
390*6dbdd20aSAndroid Build Coastguard Worker  const noErrCheck = !!cfg.watch;
391*6dbdd20aSAndroid Build Coastguard Worker  const args = [src, dst];
392*6dbdd20aSAndroid Build Coastguard Worker  if (!cfg.verbose) {
393*6dbdd20aSAndroid Build Coastguard Worker    args.unshift('--quiet');
394*6dbdd20aSAndroid Build Coastguard Worker  }
395*6dbdd20aSAndroid Build Coastguard Worker  addTask(execModule, ['sass', args, {noErrCheck}]);
396*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.bigtrace) {
397*6dbdd20aSAndroid Build Coastguard Worker    addTask(cp, [dst, pjoin(cfg.outBigtraceDistDir, 'perfetto.css')]);
398*6dbdd20aSAndroid Build Coastguard Worker  }
399*6dbdd20aSAndroid Build Coastguard Worker}
400*6dbdd20aSAndroid Build Coastguard Worker
401*6dbdd20aSAndroid Build Coastguard Workerfunction compileProtos() {
402*6dbdd20aSAndroid Build Coastguard Worker  const dstJs = pjoin(cfg.outGenDir, 'protos.js');
403*6dbdd20aSAndroid Build Coastguard Worker  const dstTs = pjoin(cfg.outGenDir, 'protos.d.ts');
404*6dbdd20aSAndroid Build Coastguard Worker  const inputs = [
405*6dbdd20aSAndroid Build Coastguard Worker    'protos/perfetto/ipc/consumer_port.proto',
406*6dbdd20aSAndroid Build Coastguard Worker    'protos/perfetto/ipc/wire_protocol.proto',
407*6dbdd20aSAndroid Build Coastguard Worker    'protos/perfetto/trace/perfetto/perfetto_metatrace.proto',
408*6dbdd20aSAndroid Build Coastguard Worker    'protos/perfetto/trace_processor/trace_processor.proto',
409*6dbdd20aSAndroid Build Coastguard Worker  ];
410*6dbdd20aSAndroid Build Coastguard Worker  // Can't put --no-comments here - The comments are load bearing for
411*6dbdd20aSAndroid Build Coastguard Worker  // the pbts invocation which follows.
412*6dbdd20aSAndroid Build Coastguard Worker  const pbjsArgs = [
413*6dbdd20aSAndroid Build Coastguard Worker    '--no-beautify',
414*6dbdd20aSAndroid Build Coastguard Worker    '--force-number',
415*6dbdd20aSAndroid Build Coastguard Worker    '--no-delimited',
416*6dbdd20aSAndroid Build Coastguard Worker    '--no-verify',
417*6dbdd20aSAndroid Build Coastguard Worker    '-t',
418*6dbdd20aSAndroid Build Coastguard Worker    'static-module',
419*6dbdd20aSAndroid Build Coastguard Worker    '-w',
420*6dbdd20aSAndroid Build Coastguard Worker    'commonjs',
421*6dbdd20aSAndroid Build Coastguard Worker    '-p',
422*6dbdd20aSAndroid Build Coastguard Worker    ROOT_DIR,
423*6dbdd20aSAndroid Build Coastguard Worker    '-o',
424*6dbdd20aSAndroid Build Coastguard Worker    dstJs,
425*6dbdd20aSAndroid Build Coastguard Worker  ].concat(inputs);
426*6dbdd20aSAndroid Build Coastguard Worker  addTask(execModule, ['pbjs', pbjsArgs]);
427*6dbdd20aSAndroid Build Coastguard Worker
428*6dbdd20aSAndroid Build Coastguard Worker  // Note: If you are looking into slowness of pbts it is not pbts
429*6dbdd20aSAndroid Build Coastguard Worker  // itself that is slow. It invokes jsdoc to parse the comments out of
430*6dbdd20aSAndroid Build Coastguard Worker  // the |dstJs| with https://github.com/hegemonic/catharsis which is
431*6dbdd20aSAndroid Build Coastguard Worker  // pinning a CPU core the whole time.
432*6dbdd20aSAndroid Build Coastguard Worker  const pbtsArgs = ['--no-comments', '-p', ROOT_DIR, '-o', dstTs, dstJs];
433*6dbdd20aSAndroid Build Coastguard Worker  addTask(execModule, ['pbts', pbtsArgs]);
434*6dbdd20aSAndroid Build Coastguard Worker}
435*6dbdd20aSAndroid Build Coastguard Worker
436*6dbdd20aSAndroid Build Coastguard Workerfunction generateImports(dir, name) {
437*6dbdd20aSAndroid Build Coastguard Worker  // We have to use the symlink (ui/src/gen) rather than cfg.outGenDir
438*6dbdd20aSAndroid Build Coastguard Worker  // below since we want to generate the correct relative imports. For example:
439*6dbdd20aSAndroid Build Coastguard Worker  // ui/src/frontend/foo.ts
440*6dbdd20aSAndroid Build Coastguard Worker  //    import '../gen/all_plugins.ts';
441*6dbdd20aSAndroid Build Coastguard Worker  // ui/src/gen/all_plugins.ts (aka ui/out/tsc/gen/all_plugins.ts)
442*6dbdd20aSAndroid Build Coastguard Worker  //    import '../frontend/some_plugin.ts';
443*6dbdd20aSAndroid Build Coastguard Worker  const dstTs = pjoin(ROOT_DIR, 'ui/src/gen', name);
444*6dbdd20aSAndroid Build Coastguard Worker  const inputDir = pjoin(ROOT_DIR, dir);
445*6dbdd20aSAndroid Build Coastguard Worker  const args = [GEN_IMPORTS_SCRIPT, inputDir, '--out', dstTs];
446*6dbdd20aSAndroid Build Coastguard Worker  addTask(exec, ['python3', args]);
447*6dbdd20aSAndroid Build Coastguard Worker}
448*6dbdd20aSAndroid Build Coastguard Worker
449*6dbdd20aSAndroid Build Coastguard Worker// Generates a .ts source that defines the VERSION and SCM_REVISION constants.
450*6dbdd20aSAndroid Build Coastguard Workerfunction genVersion() {
451*6dbdd20aSAndroid Build Coastguard Worker  const cmd = 'python3';
452*6dbdd20aSAndroid Build Coastguard Worker  const args =
453*6dbdd20aSAndroid Build Coastguard Worker      [VERSION_SCRIPT, '--ts_out', pjoin(cfg.outGenDir, 'perfetto_version.ts')];
454*6dbdd20aSAndroid Build Coastguard Worker  addTask(exec, [cmd, args]);
455*6dbdd20aSAndroid Build Coastguard Worker}
456*6dbdd20aSAndroid Build Coastguard Worker
457*6dbdd20aSAndroid Build Coastguard Workerfunction generateStdlibDocs() {
458*6dbdd20aSAndroid Build Coastguard Worker  const cmd = pjoin(ROOT_DIR, 'tools/gen_stdlib_docs_json.py');
459*6dbdd20aSAndroid Build Coastguard Worker  const stdlibDir = pjoin(ROOT_DIR, 'src/trace_processor/perfetto_sql/stdlib');
460*6dbdd20aSAndroid Build Coastguard Worker
461*6dbdd20aSAndroid Build Coastguard Worker  const stdlibFiles =
462*6dbdd20aSAndroid Build Coastguard Worker    listFilesRecursive(stdlibDir)
463*6dbdd20aSAndroid Build Coastguard Worker    .filter((filePath) => path.extname(filePath) === '.sql');
464*6dbdd20aSAndroid Build Coastguard Worker
465*6dbdd20aSAndroid Build Coastguard Worker  addTask(exec, [
466*6dbdd20aSAndroid Build Coastguard Worker    cmd,
467*6dbdd20aSAndroid Build Coastguard Worker    [
468*6dbdd20aSAndroid Build Coastguard Worker      '--json-out',
469*6dbdd20aSAndroid Build Coastguard Worker      pjoin(cfg.outDistDir, 'stdlib_docs.json'),
470*6dbdd20aSAndroid Build Coastguard Worker      '--minify',
471*6dbdd20aSAndroid Build Coastguard Worker      ...stdlibFiles,
472*6dbdd20aSAndroid Build Coastguard Worker    ],
473*6dbdd20aSAndroid Build Coastguard Worker  ]);
474*6dbdd20aSAndroid Build Coastguard Worker}
475*6dbdd20aSAndroid Build Coastguard Worker
476*6dbdd20aSAndroid Build Coastguard Workerfunction updateSymlinks() {
477*6dbdd20aSAndroid Build Coastguard Worker  // /ui/out -> /out/ui.
478*6dbdd20aSAndroid Build Coastguard Worker  mklink(cfg.outUiDir, pjoin(ROOT_DIR, 'ui/out'));
479*6dbdd20aSAndroid Build Coastguard Worker
480*6dbdd20aSAndroid Build Coastguard Worker  // /ui/src/gen -> /out/ui/ui/tsc/gen)
481*6dbdd20aSAndroid Build Coastguard Worker  mklink(cfg.outGenDir, pjoin(ROOT_DIR, 'ui/src/gen'));
482*6dbdd20aSAndroid Build Coastguard Worker
483*6dbdd20aSAndroid Build Coastguard Worker  // /out/ui/test/data -> /test/data (For UI tests).
484*6dbdd20aSAndroid Build Coastguard Worker  mklink(
485*6dbdd20aSAndroid Build Coastguard Worker      pjoin(ROOT_DIR, 'test/data'),
486*6dbdd20aSAndroid Build Coastguard Worker      pjoin(ensureDir(pjoin(cfg.outDir, 'test')), 'data'));
487*6dbdd20aSAndroid Build Coastguard Worker
488*6dbdd20aSAndroid Build Coastguard Worker  // Creates a out/dist_version -> out/dist/v1.2.3 symlink, so rollup config
489*6dbdd20aSAndroid Build Coastguard Worker  // can point to that without having to know the current version number.
490*6dbdd20aSAndroid Build Coastguard Worker  mklink(
491*6dbdd20aSAndroid Build Coastguard Worker      path.relative(cfg.outUiDir, cfg.outDistDir),
492*6dbdd20aSAndroid Build Coastguard Worker      pjoin(cfg.outUiDir, 'dist_version'));
493*6dbdd20aSAndroid Build Coastguard Worker
494*6dbdd20aSAndroid Build Coastguard Worker  mklink(
495*6dbdd20aSAndroid Build Coastguard Worker      pjoin(ROOT_DIR, 'ui/node_modules'), pjoin(cfg.outTscDir, 'node_modules'));
496*6dbdd20aSAndroid Build Coastguard Worker}
497*6dbdd20aSAndroid Build Coastguard Worker
498*6dbdd20aSAndroid Build Coastguard Worker// Invokes ninja for building the {trace_processor, traceconv} Wasm modules.
499*6dbdd20aSAndroid Build Coastguard Worker// It copies the .wasm directly into the out/dist/ dir, and the .js/.ts into
500*6dbdd20aSAndroid Build Coastguard Worker// out/tsc/, so the typescript compiler and the bundler can pick them up.
501*6dbdd20aSAndroid Build Coastguard Workerfunction buildWasm(skipWasmBuild) {
502*6dbdd20aSAndroid Build Coastguard Worker  if (!skipWasmBuild) {
503*6dbdd20aSAndroid Build Coastguard Worker    if (!cfg.noOverrideGnArgs) {
504*6dbdd20aSAndroid Build Coastguard Worker      let gnVars = `is_debug=${cfg.debug}`;
505*6dbdd20aSAndroid Build Coastguard Worker      if (childProcess.spawnSync('which', ['ccache']).status === 0) {
506*6dbdd20aSAndroid Build Coastguard Worker        gnVars += ` cc_wrapper="ccache"`;
507*6dbdd20aSAndroid Build Coastguard Worker      }
508*6dbdd20aSAndroid Build Coastguard Worker      const gnArgs = ['gen', `--args=${gnVars}`, cfg.outDir];
509*6dbdd20aSAndroid Build Coastguard Worker      addTask(exec, [pjoin(ROOT_DIR, 'tools/gn'), gnArgs]);
510*6dbdd20aSAndroid Build Coastguard Worker    }
511*6dbdd20aSAndroid Build Coastguard Worker
512*6dbdd20aSAndroid Build Coastguard Worker    const ninjaArgs = ['-C', cfg.outDir];
513*6dbdd20aSAndroid Build Coastguard Worker    ninjaArgs.push(...cfg.wasmModules.map((x) => `${x}_wasm`));
514*6dbdd20aSAndroid Build Coastguard Worker    addTask(exec, [pjoin(ROOT_DIR, 'tools/ninja'), ninjaArgs]);
515*6dbdd20aSAndroid Build Coastguard Worker  }
516*6dbdd20aSAndroid Build Coastguard Worker
517*6dbdd20aSAndroid Build Coastguard Worker  const wasmOutDir = pjoin(cfg.outDir, 'wasm');
518*6dbdd20aSAndroid Build Coastguard Worker  for (const wasmMod of cfg.wasmModules) {
519*6dbdd20aSAndroid Build Coastguard Worker    // The .wasm file goes directly into the dist dir (also .map in debug)
520*6dbdd20aSAndroid Build Coastguard Worker    for (const ext of ['.wasm'].concat(cfg.debug ? ['.wasm.map'] : [])) {
521*6dbdd20aSAndroid Build Coastguard Worker      const src = `${wasmOutDir}/${wasmMod}${ext}`;
522*6dbdd20aSAndroid Build Coastguard Worker      addTask(cp, [src, pjoin(cfg.outDistDir, wasmMod + ext)]);
523*6dbdd20aSAndroid Build Coastguard Worker    }
524*6dbdd20aSAndroid Build Coastguard Worker    // The .js / .ts go into intermediates, they will be bundled by rollup.
525*6dbdd20aSAndroid Build Coastguard Worker    for (const ext of ['.js', '.d.ts']) {
526*6dbdd20aSAndroid Build Coastguard Worker      const fname = `${wasmMod}${ext}`;
527*6dbdd20aSAndroid Build Coastguard Worker      addTask(cp, [pjoin(wasmOutDir, fname), pjoin(cfg.outGenDir, fname)]);
528*6dbdd20aSAndroid Build Coastguard Worker    }
529*6dbdd20aSAndroid Build Coastguard Worker  }
530*6dbdd20aSAndroid Build Coastguard Worker}
531*6dbdd20aSAndroid Build Coastguard Worker
532*6dbdd20aSAndroid Build Coastguard Worker// This transpiles all the sources (frontend, controller, engine, extension) in
533*6dbdd20aSAndroid Build Coastguard Worker// one go. The only project that has a dedicated invocation is service_worker.
534*6dbdd20aSAndroid Build Coastguard Workerfunction transpileTsProject(project, options) {
535*6dbdd20aSAndroid Build Coastguard Worker  const args = ['--project', pjoin(ROOT_DIR, project)];
536*6dbdd20aSAndroid Build Coastguard Worker
537*6dbdd20aSAndroid Build Coastguard Worker  if (options !== undefined && options.watch) {
538*6dbdd20aSAndroid Build Coastguard Worker    args.push('--watch', '--preserveWatchOutput');
539*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['tsc', args, {async: true}]);
540*6dbdd20aSAndroid Build Coastguard Worker  } else {
541*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['tsc', args]);
542*6dbdd20aSAndroid Build Coastguard Worker  }
543*6dbdd20aSAndroid Build Coastguard Worker}
544*6dbdd20aSAndroid Build Coastguard Worker
545*6dbdd20aSAndroid Build Coastguard Worker// Creates the three {frontend, controller, engine}_bundle.js in one invocation.
546*6dbdd20aSAndroid Build Coastguard Workerfunction bundleJs(cfgName) {
547*6dbdd20aSAndroid Build Coastguard Worker  const rcfg = pjoin(ROOT_DIR, 'ui/config', cfgName);
548*6dbdd20aSAndroid Build Coastguard Worker  const args = ['-c', rcfg, '--no-indent'];
549*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.bigtrace) {
550*6dbdd20aSAndroid Build Coastguard Worker    args.push('--environment', 'ENABLE_BIGTRACE:true');
551*6dbdd20aSAndroid Build Coastguard Worker  }
552*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.openPerfettoTrace) {
553*6dbdd20aSAndroid Build Coastguard Worker    args.push('--environment', 'ENABLE_OPEN_PERFETTO_TRACE:true');
554*6dbdd20aSAndroid Build Coastguard Worker  }
555*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.minifyJs) {
556*6dbdd20aSAndroid Build Coastguard Worker    args.push('--environment', `MINIFY_JS:${cfg.minifyJs}`);
557*6dbdd20aSAndroid Build Coastguard Worker  }
558*6dbdd20aSAndroid Build Coastguard Worker  args.push(...(cfg.verbose ? [] : ['--silent']));
559*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.watch) {
560*6dbdd20aSAndroid Build Coastguard Worker    // --waitForBundleInput is sadly quite busted so it is required ts
561*6dbdd20aSAndroid Build Coastguard Worker    // has build at least once before invoking this.
562*6dbdd20aSAndroid Build Coastguard Worker    args.push('--watch', '--no-watch.clearScreen');
563*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['rollup', args, {async: true}]);
564*6dbdd20aSAndroid Build Coastguard Worker  } else {
565*6dbdd20aSAndroid Build Coastguard Worker    addTask(execModule, ['rollup', args]);
566*6dbdd20aSAndroid Build Coastguard Worker  }
567*6dbdd20aSAndroid Build Coastguard Worker}
568*6dbdd20aSAndroid Build Coastguard Worker
569*6dbdd20aSAndroid Build Coastguard Workerfunction genServiceWorkerManifestJson() {
570*6dbdd20aSAndroid Build Coastguard Worker  function makeManifest() {
571*6dbdd20aSAndroid Build Coastguard Worker    const manifest = {resources: {}};
572*6dbdd20aSAndroid Build Coastguard Worker    // When building the subresource manifest skip source maps, the manifest
573*6dbdd20aSAndroid Build Coastguard Worker    // itself and the copy of the index.html which is copied under /v1.2.3/.
574*6dbdd20aSAndroid Build Coastguard Worker    // The root /index.html will be fetched by service_worker.js separately.
575*6dbdd20aSAndroid Build Coastguard Worker    const skipRegex = /(\.map|manifest\.json|index.html)$/;
576*6dbdd20aSAndroid Build Coastguard Worker    walk(cfg.outDistDir, (absPath) => {
577*6dbdd20aSAndroid Build Coastguard Worker      const contents = fs.readFileSync(absPath);
578*6dbdd20aSAndroid Build Coastguard Worker      const relPath = path.relative(cfg.outDistDir, absPath);
579*6dbdd20aSAndroid Build Coastguard Worker      const b64 = crypto.createHash('sha256').update(contents).digest('base64');
580*6dbdd20aSAndroid Build Coastguard Worker      manifest.resources[relPath] = 'sha256-' + b64;
581*6dbdd20aSAndroid Build Coastguard Worker    }, skipRegex);
582*6dbdd20aSAndroid Build Coastguard Worker    const manifestJson = JSON.stringify(manifest, null, 2);
583*6dbdd20aSAndroid Build Coastguard Worker    fs.writeFileSync(pjoin(cfg.outDistDir, 'manifest.json'), manifestJson);
584*6dbdd20aSAndroid Build Coastguard Worker  }
585*6dbdd20aSAndroid Build Coastguard Worker  addTask(makeManifest, []);
586*6dbdd20aSAndroid Build Coastguard Worker}
587*6dbdd20aSAndroid Build Coastguard Worker
588*6dbdd20aSAndroid Build Coastguard Workerfunction startServer() {
589*6dbdd20aSAndroid Build Coastguard Worker  const host = cfg.httpServerListenHost == '127.0.0.1' ? 'localhost' : cfg.httpServerListenHost;
590*6dbdd20aSAndroid Build Coastguard Worker  console.log(
591*6dbdd20aSAndroid Build Coastguard Worker      'Starting HTTP server on',
592*6dbdd20aSAndroid Build Coastguard Worker      `http://${host}:${cfg.httpServerListenPort}`);
593*6dbdd20aSAndroid Build Coastguard Worker  http.createServer(function(req, res) {
594*6dbdd20aSAndroid Build Coastguard Worker        console.debug(req.method, req.url);
595*6dbdd20aSAndroid Build Coastguard Worker        let uri = req.url.split('?', 1)[0];
596*6dbdd20aSAndroid Build Coastguard Worker        if (uri.endsWith('/')) {
597*6dbdd20aSAndroid Build Coastguard Worker          uri += 'index.html';
598*6dbdd20aSAndroid Build Coastguard Worker        }
599*6dbdd20aSAndroid Build Coastguard Worker
600*6dbdd20aSAndroid Build Coastguard Worker        if (uri === '/live_reload') {
601*6dbdd20aSAndroid Build Coastguard Worker          // Implements the Server-Side-Events protocol.
602*6dbdd20aSAndroid Build Coastguard Worker          const head = {
603*6dbdd20aSAndroid Build Coastguard Worker            'Content-Type': 'text/event-stream',
604*6dbdd20aSAndroid Build Coastguard Worker            'Connection': 'keep-alive',
605*6dbdd20aSAndroid Build Coastguard Worker            'Cache-Control': 'no-cache',
606*6dbdd20aSAndroid Build Coastguard Worker          };
607*6dbdd20aSAndroid Build Coastguard Worker          res.writeHead(200, head);
608*6dbdd20aSAndroid Build Coastguard Worker          const arrayIdx = httpWatches.length;
609*6dbdd20aSAndroid Build Coastguard Worker          // We never remove from the array, the delete leaves an undefined item
610*6dbdd20aSAndroid Build Coastguard Worker          // around. It makes keeping track of the index easier at the cost of a
611*6dbdd20aSAndroid Build Coastguard Worker          // small leak.
612*6dbdd20aSAndroid Build Coastguard Worker          httpWatches.push(res);
613*6dbdd20aSAndroid Build Coastguard Worker          req.on('close', () => delete httpWatches[arrayIdx]);
614*6dbdd20aSAndroid Build Coastguard Worker          return;
615*6dbdd20aSAndroid Build Coastguard Worker        }
616*6dbdd20aSAndroid Build Coastguard Worker
617*6dbdd20aSAndroid Build Coastguard Worker        let absPath = path.normalize(path.join(cfg.outDistRootDir, uri));
618*6dbdd20aSAndroid Build Coastguard Worker        // We want to be able to use the data in '/test/' for e2e tests.
619*6dbdd20aSAndroid Build Coastguard Worker        // However, we don't want do create a symlink into the 'dist/' dir,
620*6dbdd20aSAndroid Build Coastguard Worker        // because 'dist/' gets shipped on the production server.
621*6dbdd20aSAndroid Build Coastguard Worker        if (uri.startsWith('/test/')) {
622*6dbdd20aSAndroid Build Coastguard Worker          absPath = pjoin(ROOT_DIR, uri);
623*6dbdd20aSAndroid Build Coastguard Worker        }
624*6dbdd20aSAndroid Build Coastguard Worker
625*6dbdd20aSAndroid Build Coastguard Worker        // Don't serve contents outside of the project root (b/221101533).
626*6dbdd20aSAndroid Build Coastguard Worker        if (path.relative(ROOT_DIR, absPath).startsWith('..')) {
627*6dbdd20aSAndroid Build Coastguard Worker          res.writeHead(403);
628*6dbdd20aSAndroid Build Coastguard Worker          res.end('403 Forbidden - Request path outside of the repo root');
629*6dbdd20aSAndroid Build Coastguard Worker          return;
630*6dbdd20aSAndroid Build Coastguard Worker        }
631*6dbdd20aSAndroid Build Coastguard Worker
632*6dbdd20aSAndroid Build Coastguard Worker        fs.readFile(absPath, function(err, data) {
633*6dbdd20aSAndroid Build Coastguard Worker          if (err) {
634*6dbdd20aSAndroid Build Coastguard Worker            res.writeHead(404);
635*6dbdd20aSAndroid Build Coastguard Worker            res.end(JSON.stringify(err));
636*6dbdd20aSAndroid Build Coastguard Worker            return;
637*6dbdd20aSAndroid Build Coastguard Worker          }
638*6dbdd20aSAndroid Build Coastguard Worker
639*6dbdd20aSAndroid Build Coastguard Worker          const mimeMap = {
640*6dbdd20aSAndroid Build Coastguard Worker            'html': 'text/html',
641*6dbdd20aSAndroid Build Coastguard Worker            'css': 'text/css',
642*6dbdd20aSAndroid Build Coastguard Worker            'js': 'application/javascript',
643*6dbdd20aSAndroid Build Coastguard Worker            'wasm': 'application/wasm',
644*6dbdd20aSAndroid Build Coastguard Worker          };
645*6dbdd20aSAndroid Build Coastguard Worker          const ext = uri.split('.').pop();
646*6dbdd20aSAndroid Build Coastguard Worker          const cType = mimeMap[ext] || 'octect/stream';
647*6dbdd20aSAndroid Build Coastguard Worker          const head = {
648*6dbdd20aSAndroid Build Coastguard Worker            'Content-Type': cType,
649*6dbdd20aSAndroid Build Coastguard Worker            'Content-Length': data.length,
650*6dbdd20aSAndroid Build Coastguard Worker            'Last-Modified': fs.statSync(absPath).mtime.toUTCString(),
651*6dbdd20aSAndroid Build Coastguard Worker            'Cache-Control': 'no-cache',
652*6dbdd20aSAndroid Build Coastguard Worker          };
653*6dbdd20aSAndroid Build Coastguard Worker          if (cfg.crossOriginIsolation) {
654*6dbdd20aSAndroid Build Coastguard Worker            head['Cross-Origin-Opener-Policy'] = 'same-origin';
655*6dbdd20aSAndroid Build Coastguard Worker            head['Cross-Origin-Embedder-Policy'] = 'require-corp';
656*6dbdd20aSAndroid Build Coastguard Worker          }
657*6dbdd20aSAndroid Build Coastguard Worker          res.writeHead(200, head);
658*6dbdd20aSAndroid Build Coastguard Worker          res.write(data);
659*6dbdd20aSAndroid Build Coastguard Worker          res.end();
660*6dbdd20aSAndroid Build Coastguard Worker        });
661*6dbdd20aSAndroid Build Coastguard Worker      })
662*6dbdd20aSAndroid Build Coastguard Worker      .listen(cfg.httpServerListenPort, cfg.httpServerListenHost);
663*6dbdd20aSAndroid Build Coastguard Worker}
664*6dbdd20aSAndroid Build Coastguard Worker
665*6dbdd20aSAndroid Build Coastguard Workerfunction isDistComplete() {
666*6dbdd20aSAndroid Build Coastguard Worker  const requiredArtifacts = [
667*6dbdd20aSAndroid Build Coastguard Worker    'frontend_bundle.js',
668*6dbdd20aSAndroid Build Coastguard Worker    'engine_bundle.js',
669*6dbdd20aSAndroid Build Coastguard Worker    'traceconv_bundle.js',
670*6dbdd20aSAndroid Build Coastguard Worker    'trace_processor.wasm',
671*6dbdd20aSAndroid Build Coastguard Worker    'perfetto.css',
672*6dbdd20aSAndroid Build Coastguard Worker  ];
673*6dbdd20aSAndroid Build Coastguard Worker  const relPaths = new Set();
674*6dbdd20aSAndroid Build Coastguard Worker  walk(cfg.outDistDir, (absPath) => {
675*6dbdd20aSAndroid Build Coastguard Worker    relPaths.add(path.relative(cfg.outDistDir, absPath));
676*6dbdd20aSAndroid Build Coastguard Worker  });
677*6dbdd20aSAndroid Build Coastguard Worker  for (const fName of requiredArtifacts) {
678*6dbdd20aSAndroid Build Coastguard Worker    if (!relPaths.has(fName)) return false;
679*6dbdd20aSAndroid Build Coastguard Worker  }
680*6dbdd20aSAndroid Build Coastguard Worker  return true;
681*6dbdd20aSAndroid Build Coastguard Worker}
682*6dbdd20aSAndroid Build Coastguard Worker
683*6dbdd20aSAndroid Build Coastguard Worker// Called whenever a change in the out/dist directory is detected. It sends a
684*6dbdd20aSAndroid Build Coastguard Worker// Server-Side-Event to the live_reload.ts script.
685*6dbdd20aSAndroid Build Coastguard Workerfunction notifyLiveServer(changedFile) {
686*6dbdd20aSAndroid Build Coastguard Worker  for (const cli of httpWatches) {
687*6dbdd20aSAndroid Build Coastguard Worker    if (cli === undefined) continue;
688*6dbdd20aSAndroid Build Coastguard Worker    cli.write(
689*6dbdd20aSAndroid Build Coastguard Worker        'data: ' + path.relative(cfg.outDistRootDir, changedFile) + '\n\n');
690*6dbdd20aSAndroid Build Coastguard Worker  }
691*6dbdd20aSAndroid Build Coastguard Worker}
692*6dbdd20aSAndroid Build Coastguard Worker
693*6dbdd20aSAndroid Build Coastguard Workerfunction copyExtensionAssets() {
694*6dbdd20aSAndroid Build Coastguard Worker  addTask(cp, [
695*6dbdd20aSAndroid Build Coastguard Worker    pjoin(ROOT_DIR, 'ui/src/assets/logo-128.png'),
696*6dbdd20aSAndroid Build Coastguard Worker    pjoin(cfg.outExtDir, 'logo-128.png'),
697*6dbdd20aSAndroid Build Coastguard Worker  ]);
698*6dbdd20aSAndroid Build Coastguard Worker  addTask(cp, [
699*6dbdd20aSAndroid Build Coastguard Worker    pjoin(ROOT_DIR, 'ui/src/chrome_extension/manifest.json'),
700*6dbdd20aSAndroid Build Coastguard Worker    pjoin(cfg.outExtDir, 'manifest.json'),
701*6dbdd20aSAndroid Build Coastguard Worker  ]);
702*6dbdd20aSAndroid Build Coastguard Worker}
703*6dbdd20aSAndroid Build Coastguard Worker
704*6dbdd20aSAndroid Build Coastguard Worker// -----------------------
705*6dbdd20aSAndroid Build Coastguard Worker// Task chaining functions
706*6dbdd20aSAndroid Build Coastguard Worker// -----------------------
707*6dbdd20aSAndroid Build Coastguard Worker
708*6dbdd20aSAndroid Build Coastguard Workerfunction addTask(func, args) {
709*6dbdd20aSAndroid Build Coastguard Worker  const task = new Task(func, args);
710*6dbdd20aSAndroid Build Coastguard Worker  for (const t of tasks) {
711*6dbdd20aSAndroid Build Coastguard Worker    if (t.identity === task.identity) {
712*6dbdd20aSAndroid Build Coastguard Worker      return;
713*6dbdd20aSAndroid Build Coastguard Worker    }
714*6dbdd20aSAndroid Build Coastguard Worker  }
715*6dbdd20aSAndroid Build Coastguard Worker  tasks.push(task);
716*6dbdd20aSAndroid Build Coastguard Worker  setTimeout(runTasks, 0);
717*6dbdd20aSAndroid Build Coastguard Worker}
718*6dbdd20aSAndroid Build Coastguard Worker
719*6dbdd20aSAndroid Build Coastguard Workerfunction runTasks() {
720*6dbdd20aSAndroid Build Coastguard Worker  const snapTasks = tasks.splice(0);  // snap = std::move(tasks).
721*6dbdd20aSAndroid Build Coastguard Worker  tasksTot += snapTasks.length;
722*6dbdd20aSAndroid Build Coastguard Worker  for (const task of snapTasks) {
723*6dbdd20aSAndroid Build Coastguard Worker    const DIM = '\u001b[2m';
724*6dbdd20aSAndroid Build Coastguard Worker    const BRT = '\u001b[37m';
725*6dbdd20aSAndroid Build Coastguard Worker    const RST = '\u001b[0m';
726*6dbdd20aSAndroid Build Coastguard Worker    const ms = (new Date(Date.now() - tStart)).toISOString().slice(17, -1);
727*6dbdd20aSAndroid Build Coastguard Worker    const ts = `[${DIM}${ms}${RST}]`;
728*6dbdd20aSAndroid Build Coastguard Worker    const descr = task.description.substr(0, 80);
729*6dbdd20aSAndroid Build Coastguard Worker    console.log(`${ts} ${BRT}${++tasksRan}/${tasksTot}${RST}\t${descr}`);
730*6dbdd20aSAndroid Build Coastguard Worker    task.func.apply(/* this=*/ undefined, task.args);
731*6dbdd20aSAndroid Build Coastguard Worker  }
732*6dbdd20aSAndroid Build Coastguard Worker}
733*6dbdd20aSAndroid Build Coastguard Worker
734*6dbdd20aSAndroid Build Coastguard Worker// Executes all the RULES that match the given |absPath|.
735*6dbdd20aSAndroid Build Coastguard Workerfunction scanFile(absPath) {
736*6dbdd20aSAndroid Build Coastguard Worker  console.assert(fs.existsSync(absPath));
737*6dbdd20aSAndroid Build Coastguard Worker  console.assert(path.isAbsolute(absPath));
738*6dbdd20aSAndroid Build Coastguard Worker  const normPath = path.relative(ROOT_DIR, absPath);
739*6dbdd20aSAndroid Build Coastguard Worker  for (const rule of RULES) {
740*6dbdd20aSAndroid Build Coastguard Worker    const match = rule.r.exec(normPath);
741*6dbdd20aSAndroid Build Coastguard Worker    if (!match || match[0] !== normPath) continue;
742*6dbdd20aSAndroid Build Coastguard Worker    const captureGroup = match.length > 1 ? match[1] : undefined;
743*6dbdd20aSAndroid Build Coastguard Worker    rule.f(absPath, captureGroup);
744*6dbdd20aSAndroid Build Coastguard Worker  }
745*6dbdd20aSAndroid Build Coastguard Worker}
746*6dbdd20aSAndroid Build Coastguard Worker
747*6dbdd20aSAndroid Build Coastguard Worker// Walks the passed |dir| recursively and, for each file, invokes the matching
748*6dbdd20aSAndroid Build Coastguard Worker// RULES. If --watch is used, it also installs a fswatch() and re-triggers the
749*6dbdd20aSAndroid Build Coastguard Worker// matching RULES on each file change.
750*6dbdd20aSAndroid Build Coastguard Workerfunction scanDir(dir, regex) {
751*6dbdd20aSAndroid Build Coastguard Worker  const filterFn = regex ? (absPath) => regex.test(absPath) : () => true;
752*6dbdd20aSAndroid Build Coastguard Worker  const absDir = path.isAbsolute(dir) ? dir : pjoin(ROOT_DIR, dir);
753*6dbdd20aSAndroid Build Coastguard Worker  // Add a fs watch if in watch mode.
754*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.watch) {
755*6dbdd20aSAndroid Build Coastguard Worker    fs.watch(absDir, {recursive: true}, (_eventType, relFilePath) => {
756*6dbdd20aSAndroid Build Coastguard Worker      const filePath = pjoin(absDir, relFilePath);
757*6dbdd20aSAndroid Build Coastguard Worker      if (!filterFn(filePath)) return;
758*6dbdd20aSAndroid Build Coastguard Worker      if (cfg.verbose) {
759*6dbdd20aSAndroid Build Coastguard Worker        console.log('File change detected', _eventType, filePath);
760*6dbdd20aSAndroid Build Coastguard Worker      }
761*6dbdd20aSAndroid Build Coastguard Worker      if (fs.existsSync(filePath)) {
762*6dbdd20aSAndroid Build Coastguard Worker        scanFile(filePath, filterFn);
763*6dbdd20aSAndroid Build Coastguard Worker      }
764*6dbdd20aSAndroid Build Coastguard Worker    });
765*6dbdd20aSAndroid Build Coastguard Worker  }
766*6dbdd20aSAndroid Build Coastguard Worker  walk(absDir, (f) => {
767*6dbdd20aSAndroid Build Coastguard Worker    if (filterFn(f)) scanFile(f);
768*6dbdd20aSAndroid Build Coastguard Worker  });
769*6dbdd20aSAndroid Build Coastguard Worker}
770*6dbdd20aSAndroid Build Coastguard Worker
771*6dbdd20aSAndroid Build Coastguard Workerfunction exec(cmd, args, opts) {
772*6dbdd20aSAndroid Build Coastguard Worker  opts = opts || {};
773*6dbdd20aSAndroid Build Coastguard Worker  opts.stdout = opts.stdout || 'inherit';
774*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.verbose) console.log(`${cmd} ${args.join(' ')}\n`);
775*6dbdd20aSAndroid Build Coastguard Worker  const spwOpts = {cwd: cfg.outDir, stdio: ['ignore', opts.stdout, 'inherit']};
776*6dbdd20aSAndroid Build Coastguard Worker  const checkExitCode = (code, signal) => {
777*6dbdd20aSAndroid Build Coastguard Worker    if (signal === 'SIGINT' || signal === 'SIGTERM') return;
778*6dbdd20aSAndroid Build Coastguard Worker    if (code !== 0 && !opts.noErrCheck) {
779*6dbdd20aSAndroid Build Coastguard Worker      console.error(`${cmd} ${args.join(' ')} failed with code ${code}`);
780*6dbdd20aSAndroid Build Coastguard Worker      process.exit(1);
781*6dbdd20aSAndroid Build Coastguard Worker    }
782*6dbdd20aSAndroid Build Coastguard Worker  };
783*6dbdd20aSAndroid Build Coastguard Worker  if (opts.async) {
784*6dbdd20aSAndroid Build Coastguard Worker    const proc = childProcess.spawn(cmd, args, spwOpts);
785*6dbdd20aSAndroid Build Coastguard Worker    const procIndex = subprocesses.length;
786*6dbdd20aSAndroid Build Coastguard Worker    subprocesses.push(proc);
787*6dbdd20aSAndroid Build Coastguard Worker    return new Promise((resolve, _reject) => {
788*6dbdd20aSAndroid Build Coastguard Worker      proc.on('exit', (code, signal) => {
789*6dbdd20aSAndroid Build Coastguard Worker        delete subprocesses[procIndex];
790*6dbdd20aSAndroid Build Coastguard Worker        checkExitCode(code, signal);
791*6dbdd20aSAndroid Build Coastguard Worker        resolve();
792*6dbdd20aSAndroid Build Coastguard Worker      });
793*6dbdd20aSAndroid Build Coastguard Worker    });
794*6dbdd20aSAndroid Build Coastguard Worker  } else {
795*6dbdd20aSAndroid Build Coastguard Worker    const spawnRes = childProcess.spawnSync(cmd, args, spwOpts);
796*6dbdd20aSAndroid Build Coastguard Worker    checkExitCode(spawnRes.status, spawnRes.signal);
797*6dbdd20aSAndroid Build Coastguard Worker    return spawnRes;
798*6dbdd20aSAndroid Build Coastguard Worker  }
799*6dbdd20aSAndroid Build Coastguard Worker}
800*6dbdd20aSAndroid Build Coastguard Worker
801*6dbdd20aSAndroid Build Coastguard Workerfunction execModule(module, args, opts) {
802*6dbdd20aSAndroid Build Coastguard Worker  const modPath = pjoin(ROOT_DIR, 'ui/node_modules/.bin', module);
803*6dbdd20aSAndroid Build Coastguard Worker  return exec(modPath, args || [], opts);
804*6dbdd20aSAndroid Build Coastguard Worker}
805*6dbdd20aSAndroid Build Coastguard Worker
806*6dbdd20aSAndroid Build Coastguard Worker// ------------------------------------------
807*6dbdd20aSAndroid Build Coastguard Worker// File system & subprocess utility functions
808*6dbdd20aSAndroid Build Coastguard Worker// ------------------------------------------
809*6dbdd20aSAndroid Build Coastguard Worker
810*6dbdd20aSAndroid Build Coastguard Workerclass Task {
811*6dbdd20aSAndroid Build Coastguard Worker  constructor(func, args) {
812*6dbdd20aSAndroid Build Coastguard Worker    this.func = func;
813*6dbdd20aSAndroid Build Coastguard Worker    this.args = args || [];
814*6dbdd20aSAndroid Build Coastguard Worker    // |identity| is used to dedupe identical tasks in the queue.
815*6dbdd20aSAndroid Build Coastguard Worker    this.identity = JSON.stringify([this.func.name, this.args]);
816*6dbdd20aSAndroid Build Coastguard Worker  }
817*6dbdd20aSAndroid Build Coastguard Worker
818*6dbdd20aSAndroid Build Coastguard Worker  get description() {
819*6dbdd20aSAndroid Build Coastguard Worker    const ret = this.func.name.startsWith('exec') ? [] : [this.func.name];
820*6dbdd20aSAndroid Build Coastguard Worker    const flattenedArgs = [].concat(...this.args);
821*6dbdd20aSAndroid Build Coastguard Worker    for (const arg of flattenedArgs) {
822*6dbdd20aSAndroid Build Coastguard Worker      const argStr = `${arg}`;
823*6dbdd20aSAndroid Build Coastguard Worker      if (argStr.startsWith('/')) {
824*6dbdd20aSAndroid Build Coastguard Worker        ret.push(path.relative(cfg.outDir, arg));
825*6dbdd20aSAndroid Build Coastguard Worker      } else {
826*6dbdd20aSAndroid Build Coastguard Worker        ret.push(argStr);
827*6dbdd20aSAndroid Build Coastguard Worker      }
828*6dbdd20aSAndroid Build Coastguard Worker    }
829*6dbdd20aSAndroid Build Coastguard Worker    return ret.join(' ');
830*6dbdd20aSAndroid Build Coastguard Worker  }
831*6dbdd20aSAndroid Build Coastguard Worker}
832*6dbdd20aSAndroid Build Coastguard Worker
833*6dbdd20aSAndroid Build Coastguard Workerfunction walk(dir, callback, skipRegex) {
834*6dbdd20aSAndroid Build Coastguard Worker  for (const child of fs.readdirSync(dir)) {
835*6dbdd20aSAndroid Build Coastguard Worker    const childPath = pjoin(dir, child);
836*6dbdd20aSAndroid Build Coastguard Worker    const stat = fs.lstatSync(childPath);
837*6dbdd20aSAndroid Build Coastguard Worker    if (skipRegex !== undefined && skipRegex.test(child)) continue;
838*6dbdd20aSAndroid Build Coastguard Worker    if (stat.isDirectory()) {
839*6dbdd20aSAndroid Build Coastguard Worker      walk(childPath, callback, skipRegex);
840*6dbdd20aSAndroid Build Coastguard Worker    } else if (!stat.isSymbolicLink()) {
841*6dbdd20aSAndroid Build Coastguard Worker      callback(childPath);
842*6dbdd20aSAndroid Build Coastguard Worker    }
843*6dbdd20aSAndroid Build Coastguard Worker  }
844*6dbdd20aSAndroid Build Coastguard Worker}
845*6dbdd20aSAndroid Build Coastguard Worker
846*6dbdd20aSAndroid Build Coastguard Worker// Recursively build a list of files in a given directory and return a list of
847*6dbdd20aSAndroid Build Coastguard Worker// file paths, similar to `find -type f`.
848*6dbdd20aSAndroid Build Coastguard Workerfunction listFilesRecursive(dir) {
849*6dbdd20aSAndroid Build Coastguard Worker  const fileList = [];
850*6dbdd20aSAndroid Build Coastguard Worker
851*6dbdd20aSAndroid Build Coastguard Worker  walk(dir, (filePath) => {
852*6dbdd20aSAndroid Build Coastguard Worker    fileList.push(filePath);
853*6dbdd20aSAndroid Build Coastguard Worker  });
854*6dbdd20aSAndroid Build Coastguard Worker
855*6dbdd20aSAndroid Build Coastguard Worker  return fileList;
856*6dbdd20aSAndroid Build Coastguard Worker}
857*6dbdd20aSAndroid Build Coastguard Worker
858*6dbdd20aSAndroid Build Coastguard Workerfunction ensureDir(dirPath, clean) {
859*6dbdd20aSAndroid Build Coastguard Worker  const exists = fs.existsSync(dirPath);
860*6dbdd20aSAndroid Build Coastguard Worker  if (exists && clean) {
861*6dbdd20aSAndroid Build Coastguard Worker    console.log('rm', dirPath);
862*6dbdd20aSAndroid Build Coastguard Worker    fs.rmSync(dirPath, {recursive: true});
863*6dbdd20aSAndroid Build Coastguard Worker  }
864*6dbdd20aSAndroid Build Coastguard Worker  if (!exists || clean) fs.mkdirSync(dirPath, {recursive: true});
865*6dbdd20aSAndroid Build Coastguard Worker  return dirPath;
866*6dbdd20aSAndroid Build Coastguard Worker}
867*6dbdd20aSAndroid Build Coastguard Worker
868*6dbdd20aSAndroid Build Coastguard Workerfunction cp(src, dst) {
869*6dbdd20aSAndroid Build Coastguard Worker  ensureDir(path.dirname(dst));
870*6dbdd20aSAndroid Build Coastguard Worker  if (cfg.verbose) {
871*6dbdd20aSAndroid Build Coastguard Worker    console.log(
872*6dbdd20aSAndroid Build Coastguard Worker        'cp', path.relative(ROOT_DIR, src), '->', path.relative(ROOT_DIR, dst));
873*6dbdd20aSAndroid Build Coastguard Worker  }
874*6dbdd20aSAndroid Build Coastguard Worker  fs.copyFileSync(src, dst);
875*6dbdd20aSAndroid Build Coastguard Worker}
876*6dbdd20aSAndroid Build Coastguard Worker
877*6dbdd20aSAndroid Build Coastguard Workerfunction mklink(src, dst) {
878*6dbdd20aSAndroid Build Coastguard Worker  // If the symlink already points to the right place don't touch it. This is
879*6dbdd20aSAndroid Build Coastguard Worker  // to avoid changing the mtime of the ui/ dir when unnecessary.
880*6dbdd20aSAndroid Build Coastguard Worker  if (fs.existsSync(dst)) {
881*6dbdd20aSAndroid Build Coastguard Worker    if (fs.lstatSync(dst).isSymbolicLink() && fs.readlinkSync(dst) === src) {
882*6dbdd20aSAndroid Build Coastguard Worker      return;
883*6dbdd20aSAndroid Build Coastguard Worker    } else {
884*6dbdd20aSAndroid Build Coastguard Worker      fs.unlinkSync(dst);
885*6dbdd20aSAndroid Build Coastguard Worker    }
886*6dbdd20aSAndroid Build Coastguard Worker  }
887*6dbdd20aSAndroid Build Coastguard Worker  fs.symlinkSync(src, dst);
888*6dbdd20aSAndroid Build Coastguard Worker}
889*6dbdd20aSAndroid Build Coastguard Worker
890*6dbdd20aSAndroid Build Coastguard Workermain();
891