[npm] Produce more readable build error messages.
The `npm run build` and `npm start` were producing quite messy error
messages, which are hard to parse, and the `esbuild` and `tsc` error
message format was also (naturally) different. This change adjusts
the error messages to always report in the `tsc` format (which is
well understood and supported by IDEs and tools). It also removes
the unnecessary clutter from the error messages and simplifies it to
basically this (for `tsc`)
```
✖ TypeScript compilation failed for `Debug'
front_end/panels/timeline/components/insights/Checklist.ts(46,1): error TS1128: Declaration or statement expected.
front_end/panels/timeline/components/insights/Checklist.ts(46,8): error TS1434: Unexpected keyword or identifier.
front_end/panels/timeline/components/insights/Checklist.ts(46,8): error TS2304: Cannot find name 'TableDataRow'.
front_end/panels/timeline/components/insights/Checklist.ts(48,3): error TS2552: Cannot find name 'overlays'. Did you mean 'Overlays'?
front_end/panels/timeline/components/insights/Checklist.ts(48,12): error TS1109: Expression expected.
front_end/panels/timeline/components/insights/Checklist.ts(48,14): error TS1361: 'Overlays' cannot be used as a value because it was imported using 'import type'.
front_end/panels/timeline/components/insights/Checklist.ts(48,32): error TS2339: Property 'TimelineOverlay' does not exist on type 'typeof import("/usr/local/google/home/bmeurer/Projects/devtools/devtools-frontend/out/Debug/gen/front_end/panels/timeline/overlays/OverlaysImpl")'.
front_end/panels/timeline/components/insights/Checklist.ts(48,48): error TS1011: An element access expression should take an argument.
front_end/panels/timeline/components/insights/Table.ts(26,71): error TS1005: ',' expected.
front_end/panels/timeline/components/insights/Table.ts(26,71): error TS2304: Cannot find name 'a'.
front_end/panels/timeline/components/insights/Table.ts(26,74): error TS2345: Argument of type 'RegisteredFileStrings' is not assignable to parameter of type 'string'.
front_end/panels/timeline/components/insights/Table.ts(66,39): error TS2554: Expected 0-1 arguments, but got 2.
```
and this (for `esbuild`):
```
✖ TypeScript compilation failed for `Default'
front_end/panels/timeline/components/insights/Checklist.ts(46,7): error TS0666: Unexpected "TableDataRow"
front_end/panels/timeline/components/insights/Table.ts(26,70): error TS0666: Expected ")" but found "a"
```
Bug: 411328609
Change-Id: Ic0be7780a91810386e38997c7237210aa69f63dd
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6469693
Reviewed-by: Nikolay Vitkov <[email protected]>
Auto-Submit: Benedikt Meurer <[email protected]>
Commit-Queue: Benedikt Meurer <[email protected]>
diff --git a/scripts/devtools_build.mjs b/scripts/devtools_build.mjs
index 36bdbcc..9062fa3 100644
--- a/scripts/devtools_build.mjs
+++ b/scripts/devtools_build.mjs
@@ -127,8 +127,56 @@
}
}
+/**
+ * Constructs a human readable error message for the given build `error`.
+ *
+ * @param {Error} error the `Error` from the failed `autoninja` invocation.
+ * @param {string} outDir the absolute path to the `target` out directory.
+ * @param {string} target the targe relative to `//out`.
+ * @return {string} the human readable error message.
+ */
+function buildErrorMessageForNinja(error, outDir, target) {
+ const {message, stderr, stdout} = error;
+ if (stderr) {
+ // Anything that went to stderr has precedence.
+ return `Failed to build \`${target}' in \`${outDir}'
+
+${stderr}
+`;
+ }
+ if (stdout) {
+ // Check for `tsc` or `esbuild` errors in the stdout.
+ const tscErrors = [...stdout.matchAll(/^[^\s].*\(\d+,\d+\): error TS\d+:\s+.*$/gm)].map(([tscError]) => tscError);
+ if (!tscErrors.length) {
+ // We didn't find any `tsc` errors, but maybe there are `esbuild` errors.
+ // Transform these into the `tsc` format (with a made up error code), so
+ // we can report all TypeScript errors consistently in `tsc` format (which
+ // is well-known and understood by tools).
+ const esbuildErrors = stdout.matchAll(/^✘ \[ERROR\] ([^\n]+)\n\n\s+\.\.\/\.\.\/(.+):(\d+):(\d+):/gm);
+ for (const [, message, filename, line, column] of esbuildErrors) {
+ tscErrors.push(`${filename}(${line},${column}): error TS0000: ${message}`);
+ }
+ }
+ if (tscErrors.length) {
+ return `TypeScript compilation failed for \`${target}'
+
+${tscErrors.join('\n')}
+`;
+ }
+
+ // At the very least we strip `ninja: Something, something` lines from the
+ // standard output, since that's not particularly helpful.
+ const output = stdout.replaceAll(/^ninja: [^\n]+\n+/mg, '').trim();
+ return `Failed to build \`${target}' in \`${outDir}'
+
+${output}
+`;
+ }
+ return `Failed to build \`${target}' in \`${outDir}' (${message.substring(0, message.indexOf('\n'))})`;
+}
+
export const BuildStep = {
- GN: 'gn-gen',
+ GN: 'gn',
AUTONINJA: 'autoninja',
};
@@ -144,22 +192,14 @@
*/
constructor(step, options) {
const {cause, outDir, target} = options;
- super(`Failed to build target ${target} in ${outDir}`, {cause});
- this.name = 'BuildError';
+ const message = step === BuildStep.GN ? `\`gn' failed to initialize out directory ${outDir}` :
+ buildErrorMessageForNinja(cause, outDir, target);
+ super(message, {cause});
this.step = step;
+ this.name = 'BuildError';
this.target = target;
this.outDir = outDir;
}
-
- toString() {
- const {cause} = this;
- const {stdout} = cause;
- if (stdout) {
- return stdout;
- }
- const {message} = this;
- return `${message}\n${cause.message}`;
- }
}
/**