| import * as path from 'path'; |
| /* tslint:disable:no-require-imports */ |
| const protobufjs = require('protobufjs'); |
| const ByteBuffer = require('bytebuffer'); |
| |
| protobufjs.convertFieldsToCamelCase = true; |
| |
| const DEBUG = false; |
| |
| export function debug(...args: Array<{}>) { |
| if (DEBUG) console.error.apply(console, args); |
| } |
| |
| /** |
| * Write a message to stderr, which appears in the bazel log and is visible to |
| * the end user. |
| */ |
| export function log(...args: Array<{}>) { |
| console.error.apply(console, args); |
| } |
| |
| export function runAsWorker(args: string[]) { |
| return args.indexOf('--persistent_worker') !== -1; |
| } |
| |
| const workerpb = (function loadWorkerPb() { |
| // This doesn't work due to a Bazel bug, see comments in build_defs.bzl |
| // let protoPath = |
| // 'external/bazel_tools/src/main/protobuf/worker_protocol.proto'; |
| const protoPath = 'build_bazel_rules_typescript/third_party/github.com/bazelbuild/bazel/src/main/protobuf/worker_protocol.proto'; |
| |
| // Use node module resolution so we can find the .proto file in any of the root dirs |
| const protoNamespace = protobufjs.loadProtoFile(require.resolve(protoPath)); |
| if (!protoNamespace) { |
| throw new Error('Cannot find ' + path.resolve(protoPath)); |
| } |
| return protoNamespace.build('blaze.worker'); |
| })(); |
| |
| interface Input { |
| getPath(): string; |
| getDigest(): {toString(encoding: string): string}; // npm:ByteBuffer |
| } |
| interface WorkRequest { |
| getArguments(): string[]; |
| getInputs(): Input[]; |
| } |
| |
| export function runWorkerLoop( |
| runOneBuild: (args: string[], inputs?: {[path: string]: string}) => |
| boolean) { |
| // Hook all output to stderr and write it to a buffer, then include |
| // that buffer's in the worker protcol proto's textual output. This |
| // means you can log via console.error() and it will appear to the |
| // user as expected. |
| let consoleOutput = ''; |
| process.stderr.write = |
| (chunk: string | Buffer, ...otherArgs: any[]): boolean => { |
| consoleOutput += chunk.toString(); |
| return true; |
| }; |
| |
| // Accumulator for asynchronously read input. |
| // tslint:disable-next-line:no-any protobufjs is untyped |
| let buf: any; |
| process.stdin.on('readable', () => { |
| const chunk = process.stdin.read(); |
| if (!chunk) return; |
| |
| const wrapped = ByteBuffer.wrap(chunk); |
| buf = buf ? ByteBuffer.concat([buf, wrapped]) : wrapped; |
| try { |
| let req: WorkRequest; |
| // Read all requests that have accumulated in the buffer. |
| while ((req = workerpb.WorkRequest.decodeDelimited(buf)) != null) { |
| debug('=== Handling new build request'); |
| // Reset accumulated log output. |
| consoleOutput = ''; |
| const args = req.getArguments(); |
| const inputs: {[path: string]: string} = {}; |
| for (const input of req.getInputs()) { |
| inputs[input.getPath()] = input.getDigest().toString('hex'); |
| } |
| debug('Compiling with:\n\t' + args.join('\n\t')); |
| const exitCode = runOneBuild(args, inputs) ? 0 : 1; |
| process.stdout.write(new workerpb.WorkResponse() |
| .setExitCode(exitCode) |
| .setOutput(consoleOutput) |
| .encodeDelimited() |
| .toBuffer()); |
| |
| // Force a garbage collection pass. This keeps our memory |
| // usage consistent across multiple compilations, and allows |
| // the file cache to use the current memory usage as a |
| // guideline for expiring data. |
| global.gc(); |
| } |
| // Avoid growing the buffer indefinitely. |
| buf.compact(); |
| } catch (e) { |
| log('Compilation failed', e.stack); |
| process.stdout.write(new workerpb.WorkResponse() |
| .setExitCode(1) |
| .setOutput(consoleOutput) |
| .encodeDelimited() |
| .toBuffer()); |
| // Clear buffer so the next build won't read an incomplete request. |
| buf = null; |
| } |
| }); |
| process.stdin.on('end', () => { |
| log('Exiting TypeScript compiler persistent worker.'); |
| process.exit(0); |
| }); |
| } |