blob: ade509e898a26d56ca2efaf304fef3d68222bdb4 [file] [log] [blame]
Paul Lewisa1448c22020-07-23 12:22:041// Copyright 2020 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Run like the following:
6// $ node scripts/build/cross_reference_ninja_and_tsc.js [Target] [Path to bundle],
7// e.g. node scripts/build/cross_reference_ninja_and_tsc.js Default front_end/common:bundle,
8
9const fs = require('fs');
10const path = require('path');
11const childProcess = require('child_process');
12const [, , buildDir, gnTarget] = process.argv;
13const cwd = process.cwd();
14
15/**
16 * Execs a command.
17 *
18 * @param {string} cmd
19 * @return {string}
20 */
21async function exec(cmd) {
22 const env = process.env;
23
24 return new Promise((resolve, reject) => {
25 const [util, ...args] = cmd.split(' ');
26
27 let response = '';
28 const process = childProcess.spawn(util, args, {cwd, env});
29 process.stdout.on('data', data => {
30 response += data.toString();
31 });
32
33 process.on('close', () => {
34 resolve(response);
35 });
36
37 process.on('error', err => {
38 reject(err);
39 });
40 });
41}
42
43/**
44 *
45 * @param {string} buildDir
46 * @param {string} gnTarget
47 */
48async function buildTargetInfo(buildDir, gnTarget) {
49 /**
50 * @param {string} outputList
51 */
52 const flattenOutput = outputList => {
53 try {
54 const outputFiles = JSON.parse(outputList);
55
56 /**
57 * @type string[]
58 */
59 return Object.values(outputFiles).reduce((prev, {outputs}) => {
60 if (!outputs) {
61 return prev;
62 }
63
64 prev.push(...outputs);
65 return prev;
66 }, []);
67 } catch (e) {
68 return [];
69 }
70 };
71
72 // Grab the outputs of the build target itself.
73 const output = await exec(`gn desc out/${buildDir} ${gnTarget} outputs --format=json`);
Jack Franklin7a25be02020-07-24 08:54:1274 if (output.startsWith('ERROR')) {
75 console.error('GN error:');
76 console.error(output);
77 process.exit(1);
78 }
79
Paul Lewisa1448c22020-07-23 12:22:0480 const gnTargetOutputFileList = flattenOutput(output);
81
82 // The response from this is a new line-separated list of targets.
83 const deps = await exec(`gn desc out/${buildDir} //${gnTarget} deps --all`);
84 const depsOutputFileList = deps.split('\n').map(async line => {
85 if (line.trim() === '') {
86 return [];
87 }
88
89 const depOutputList = await exec(`gn desc out/${buildDir} ${line} outputs --format=json`);
90 return flattenOutput(depOutputList);
91 });
92
93 const fileList = await Promise.all(depsOutputFileList);
94 return [gnTargetOutputFileList, fileList]
95 .flat(Infinity)
96 // Only include those in gen.
97 .filter(file => file.startsWith(`//out/${buildDir}/gen/`))
98 // Strip the build dir out.
99 .map(file => file.replace(`//out/${buildDir}/gen/`, ''));
100}
101
102(async function init() {
103 // Go from foo:bundle to foo, on the basis that foo.ts / foo.js will be the entrypoint file.
104 const assumedName = path.basename(gnTarget).replace(/:.*$/, '');
105 const assumedDir = gnTarget.replace(/:.*$/, '');
106 const entryPointTs = path.join(cwd, assumedDir, `${assumedName}.ts`);
107 const entryPointJs = path.join(cwd, assumedDir, `${assumedName}.js`);
108 let entryPoint = entryPointTs;
109
110 if (!fs.existsSync(entryPoint)) {
111 entryPoint = entryPointJs;
112 if (!fs.existsSync(entryPoint)) {
113 console.error(`Neither ${entryPointTs} nor ${entryPointJs} exists.`);
114 process.exit(1);
115 }
116 }
117
118 console.log(`Output Build Dir: ${buildDir}`);
119 console.log(`GN Target: //${gnTarget}`);
120 console.log(`TS/JS Entry Point: ${entryPoint}`);
121
122 try {
123 // Ask TypeScript to enumerate the files it knows about.
124 const types = path.join(cwd, 'front_end/global_typings');
Jack Franklin15a98cd2020-07-23 15:40:32125 const tscPath = path.join(cwd, 'node_modules', '.bin', 'tsc');
Simon Zünd6d32fb12021-01-14 07:58:06126 const tscOut = await exec(`${tscPath} ${entryPoint} --types ${types}/request_idle_callback.d.ts --types ${
Simon Zünde4da47f2021-05-11 06:15:58127 types}/global_defs.d.ts --types ${
128 types}/intl_display_names.d.ts --noEmit --listFiles --allowJs --target esnext`);
Paul Lewisa1448c22020-07-23 12:22:04129
130 // Filter the list and remap to those that are explicitly in the front_end, excluding the entrypoint itself.
131 const frontEndFiles =
132 tscOut.split('\n')
Paul Lewis2f7079d2020-07-23 13:30:23133 .filter(line => line.includes('front_end') && line !== entryPoint && !line.includes('global_typings'))
Paul Lewisa1448c22020-07-23 12:22:04134 // Ensure we look for the original file, not the .d.ts.
135 .map(line => line.replace(/\.d\.ts$/, ''))
Paul Lewis2f7079d2020-07-23 13:30:23136 // Ensure that any file that ends in .ts is replaced as the equivalent outputted .js file.
137 .map(line => line.replace(/\.ts$/, '.js'))
Paul Lewisa1448c22020-07-23 12:22:04138 // Trim the files so that the path starts with front_end
139 .map(line => line.replace(/.*?front_end/, 'front_end'))
140 // Finally remove any files where stripping the .d.ts has resulted in a file with no suffix.
141 .filter(line => path.extname(line) !== '');
142
143 // Ask gn/ninja to do the same.
144 const gnFiles = await buildTargetInfo(buildDir, gnTarget);
145
146 // Then cross reference.
147 const missingFiles = [];
148 for (const file of frontEndFiles) {
149 if (!gnFiles.includes(file)) {
150 missingFiles.push(file);
151 }
152 }
153
154 if (missingFiles.length) {
155 console.error('TypeScript indicates it expects the following files:');
156 console.error(missingFiles.map(file => ` - ${file}`).join('\n'));
157 console.error(`There ${
158 missingFiles.length === 1 ? 'is 1 file' :
159 `are ${missingFiles.length} files`} not listed in the BUILD.gn as dependencies`);
160 console.error('Have you added all dependencies to the BUILD.gn?');
161 process.exit(1);
162 } else {
163 console.log('No mismatches found');
164 process.exit(0);
165 }
166 } catch (e) {
167 console.error(e);
168 process.exit(1);
169 }
170})();