refactor(ngcc): support running callback before writing transformed files (#36626)

This commit enhances the `CompileFn`, which is used to process each
entry-point, to support running a passed-in callback (and wait for it to
complete) before proceeding with writing the transformed files to disk.

This functionality is currently not used. In a subsequent commit, it
will be used for passing info from worker processes to the master
process that will allow ngcc to recover when a worker process crashes in
the middle of processing a task.

PR Close #36626
This commit is contained in:
George Kalpakas 2020-04-29 21:28:11 +03:00 committed by Andrew Kushnir
parent 16039d837e
commit e367593a26
6 changed files with 28 additions and 14 deletions

View File

@ -5,6 +5,7 @@
* Use of this source code is governed by an MIT-style license that can be * Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {FileToWrite} from '../rendering/utils';
import {Task, TaskCompletedCallback, TaskQueue} from './tasks/api'; import {Task, TaskCompletedCallback, TaskQueue} from './tasks/api';
/** /**
@ -16,10 +17,12 @@ import {Task, TaskCompletedCallback, TaskQueue} from './tasks/api';
export type AnalyzeEntryPointsFn = () => TaskQueue; export type AnalyzeEntryPointsFn = () => TaskQueue;
/** The type of the function that can process/compile a task. */ /** The type of the function that can process/compile a task. */
export type CompileFn = (task: Task) => void; export type CompileFn<T> = (task: Task) => void|T;
/** The type of the function that creates the `CompileFn` function used to process tasks. */ /** The type of the function that creates the `CompileFn` function used to process tasks. */
export type CreateCompileFn = (onTaskCompleted: TaskCompletedCallback) => CompileFn; export type CreateCompileFn = <T extends void|Promise<void>>(
beforeWritingFiles: (transformedFiles: FileToWrite[]) => T,
onTaskCompleted: TaskCompletedCallback) => CompileFn<T>;
/** /**
* A class that orchestrates and executes the required work (i.e. analyzes the entry-points, * A class that orchestrates and executes the required work (i.e. analyzes the entry-points,

View File

@ -62,17 +62,18 @@ export async function startWorker(logger: Logger, createCompileFn: CreateCompile
} }
const compile = createCompileFn( const compile = createCompileFn(
() => {},
(_task, outcome, message) => sendMessageToMaster({type: 'task-completed', outcome, message})); (_task, outcome, message) => sendMessageToMaster({type: 'task-completed', outcome, message}));
// Listen for `ProcessTaskMessage`s and process tasks. // Listen for `ProcessTaskMessage`s and process tasks.
cluster.worker.on('message', (msg: MessageToWorker) => { cluster.worker.on('message', async (msg: MessageToWorker) => {
try { try {
switch (msg.type) { switch (msg.type) {
case 'process-task': case 'process-task':
logger.debug( logger.debug(
`[Worker #${cluster.worker.id}] Processing task: ${stringifyTask(msg.task)}`); `[Worker #${cluster.worker.id}] Processing task: ${stringifyTask(msg.task)}`);
return compile(msg.task); return await compile(msg.task);
default: default:
throw new Error( throw new Error(
`[Worker #${cluster.worker.id}] Invalid message received: ${JSON.stringify(msg)}`); `[Worker #${cluster.worker.id}] Invalid message received: ${JSON.stringify(msg)}`);

View File

@ -31,7 +31,7 @@ export function getCreateCompileFn(
createNewEntryPointFormats: boolean, errorOnFailedEntryPoint: boolean, createNewEntryPointFormats: boolean, errorOnFailedEntryPoint: boolean,
enableI18nLegacyMessageIdFormat: boolean, tsConfig: ParsedConfiguration|null, enableI18nLegacyMessageIdFormat: boolean, tsConfig: ParsedConfiguration|null,
pathMappings: PathMappings|undefined): CreateCompileFn { pathMappings: PathMappings|undefined): CreateCompileFn {
return onTaskCompleted => { return (beforeWritingFiles, onTaskCompleted) => {
const fileWriter = getFileWriter( const fileWriter = getFileWriter(
fileSystem, logger, pkgJsonUpdater, createNewEntryPointFormats, errorOnFailedEntryPoint); fileSystem, logger, pkgJsonUpdater, createNewEntryPointFormats, errorOnFailedEntryPoint);
const {Transformer} = require('../packages/transformer'); const {Transformer} = require('../packages/transformer');
@ -69,11 +69,20 @@ export function getCreateCompileFn(
logger.warn(replaceTsWithNgInErrors( logger.warn(replaceTsWithNgInErrors(
ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host))); ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host)));
} }
fileWriter.writeBundle(bundle, result.transformedFiles, formatPropertiesToMarkAsProcessed);
logger.debug(` Successfully compiled ${entryPoint.name} : ${formatProperty}`); const writeBundle = () => {
fileWriter.writeBundle(
bundle, result.transformedFiles, formatPropertiesToMarkAsProcessed);
onTaskCompleted(task, TaskProcessingOutcome.Processed, null); logger.debug(` Successfully compiled ${entryPoint.name} : ${formatProperty}`);
onTaskCompleted(task, TaskProcessingOutcome.Processed, null);
};
const beforeWritingResult = beforeWritingFiles(result.transformedFiles);
return (beforeWritingResult instanceof Promise) ?
beforeWritingResult.then(writeBundle) as ReturnType<typeof beforeWritingFiles>:
writeBundle();
} else { } else {
const errors = replaceTsWithNgInErrors( const errors = replaceTsWithNgInErrors(
ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host)); ts.formatDiagnosticsWithColorAndContext(result.diagnostics, bundle.src.host));

View File

@ -23,7 +23,7 @@ export abstract class SingleProcessorExecutorBase {
const taskQueue = analyzeEntryPoints(); const taskQueue = analyzeEntryPoints();
const onTaskCompleted = this.createTaskCompletedCallback(taskQueue); const onTaskCompleted = this.createTaskCompletedCallback(taskQueue);
const compile = createCompileFn(onTaskCompleted); const compile = createCompileFn(() => {}, onTaskCompleted);
// Process all tasks. // Process all tasks.
this.logger.debug('Processing tasks...'); this.logger.debug('Processing tasks...');

View File

@ -57,12 +57,12 @@ describe('startWorker()', () => {
it('should create the `compileFn()`', () => { it('should create the `compileFn()`', () => {
startWorker(mockLogger, createCompileFnSpy); startWorker(mockLogger, createCompileFnSpy);
expect(createCompileFnSpy).toHaveBeenCalledWith(jasmine.any(Function)); expect(createCompileFnSpy).toHaveBeenCalledWith(jasmine.any(Function), jasmine.any(Function));
}); });
it('should set up `compileFn()` to send `task-completed` messages to master', () => { it('should set up `compileFn()` to send `task-completed` messages to master', () => {
startWorker(mockLogger, createCompileFnSpy); startWorker(mockLogger, createCompileFnSpy);
const onTaskCompleted: TaskCompletedCallback = createCompileFnSpy.calls.argsFor(0)[0]; const onTaskCompleted: TaskCompletedCallback = createCompileFnSpy.calls.argsFor(0)[1];
onTaskCompleted(null as any, TaskProcessingOutcome.Processed, null); onTaskCompleted(null as any, TaskProcessingOutcome.Processed, null);
expect(processSendSpy).toHaveBeenCalledTimes(1); expect(processSendSpy).toHaveBeenCalledTimes(1);

View File

@ -132,14 +132,15 @@ describe('SingleProcessExecutor', () => {
{allTasksCompleted: true, getNextTask: jasmine.any(Function)})]); {allTasksCompleted: true, getNextTask: jasmine.any(Function)})]);
}); });
it('should pass the created TaskCompletedCallback to the createCompileFn', () => { it('should pass the necessary callbacks to createCompileFn', () => {
const beforeWritingFiles = jasmine.any(Function);
const onTaskCompleted = () => {};
const createCompileFn = const createCompileFn =
jasmine.createSpy('createCompileFn').and.returnValue(function compileFn() {}); jasmine.createSpy('createCompileFn').and.returnValue(function compileFn() {});
function onTaskCompleted() {}
createTaskCompletedCallback.and.returnValue(onTaskCompleted); createTaskCompletedCallback.and.returnValue(onTaskCompleted);
executor.execute(noTasks, createCompileFn); executor.execute(noTasks, createCompileFn);
expect(createCompileFn).toHaveBeenCalledTimes(1); expect(createCompileFn).toHaveBeenCalledTimes(1);
expect(createCompileFn).toHaveBeenCalledWith(onTaskCompleted); expect(createCompileFn).toHaveBeenCalledWith(beforeWritingFiles, onTaskCompleted);
}); });
}); });
}); });