refactor(ngcc): abstract task selection behind an interface (#32427)
This change does not alter the current behavior, but makes it easier to introduce `TaskQueue`s implementing different task selection algorithms, for example to support executing multiple tasks in parallel (while respecting interdependencies between them). Inspired by/Based on @alxhub's prototype: alxhub/angular@cb631bdb1 PR Close #32427
This commit is contained in:

committed by
Matias Niemelä

parent
0cf94e3ed5
commit
2844dd2972
@ -7,6 +7,7 @@
|
||||
*/
|
||||
|
||||
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||
import {PartiallyOrderedList} from '../utils';
|
||||
|
||||
|
||||
/**
|
||||
@ -15,7 +16,7 @@ import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
|
||||
* @return A list of tasks that need to be executed in order to process the necessary format
|
||||
* properties for all entry-points.
|
||||
*/
|
||||
export type AnalyzeEntryPointsFn = () => Task[];
|
||||
export type AnalyzeEntryPointsFn = () => TaskQueue;
|
||||
|
||||
/** The type of the function that can process/compile a task. */
|
||||
export type CompileFn = (task: Task) => void;
|
||||
@ -32,6 +33,21 @@ export interface Executor {
|
||||
void|Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a partially ordered list of tasks.
|
||||
*
|
||||
* The ordering/precedence of tasks is determined by the inter-dependencies between their associated
|
||||
* entry-points. Specifically, the tasks' order/precedence is such that tasks associated to
|
||||
* dependent entry-points always come after tasks associated with their dependencies.
|
||||
*
|
||||
* As result of this ordering, it is guaranteed that - by processing tasks in the order in which
|
||||
* they appear in the list - a task's dependencies will always have been processed before processing
|
||||
* the task itself.
|
||||
*
|
||||
* See `DependencyResolver#sortEntryPointsByDependency()`.
|
||||
*/
|
||||
export type PartiallyOrderedTasks = PartiallyOrderedList<Task>;
|
||||
|
||||
/** Represents a unit of work: processing a specific format property of an entry-point. */
|
||||
export interface Task {
|
||||
/** The `EntryPoint` which needs to be processed as part of the task. */
|
||||
@ -65,3 +81,43 @@ export const enum TaskProcessingOutcome {
|
||||
/** Successfully processed the target format property. */
|
||||
Processed,
|
||||
}
|
||||
|
||||
/**
|
||||
* A wrapper around a list of tasks and providing utility methods for getting the next task of
|
||||
* interest and determining when all tasks have been completed.
|
||||
*
|
||||
* (This allows different implementations to impose different constraints on when a task's
|
||||
* processing can start.)
|
||||
*/
|
||||
export interface TaskQueue {
|
||||
/** Whether all tasks have been completed. */
|
||||
allTasksCompleted: boolean;
|
||||
|
||||
/**
|
||||
* Get the next task whose processing can start (if any).
|
||||
*
|
||||
* This implicitly marks the task as in-progress.
|
||||
* (This information is used to determine whether all tasks have been completed.)
|
||||
*
|
||||
* @return The next task available for processing or `null`, if no task can be processed at the
|
||||
* moment (including if there are no more unprocessed tasks).
|
||||
*/
|
||||
getNextTask(): Task|null;
|
||||
|
||||
/**
|
||||
* Mark a task as completed.
|
||||
*
|
||||
* This removes the task from the internal list of in-progress tasks.
|
||||
* (This information is used to determine whether all tasks have been completed.)
|
||||
*
|
||||
* @param task The task to mark as completed.
|
||||
*/
|
||||
markTaskCompleted(task: Task): void;
|
||||
|
||||
/**
|
||||
* Return a string representation of the task queue (for debugging purposes).
|
||||
*
|
||||
* @return A string representation of the task queue.
|
||||
*/
|
||||
toString(): string;
|
||||
}
|
||||
|
@ -22,13 +22,15 @@ export class SingleProcessExecutor implements Executor {
|
||||
execute(analyzeEntryPoints: AnalyzeEntryPointsFn, createCompileFn: CreateCompileFn): void {
|
||||
this.logger.debug(`Running ngcc on ${this.constructor.name}.`);
|
||||
|
||||
const tasks = analyzeEntryPoints();
|
||||
const taskQueue = analyzeEntryPoints();
|
||||
const compile =
|
||||
createCompileFn((task, outcome) => onTaskCompleted(this.pkgJsonUpdater, task, outcome));
|
||||
|
||||
// Process all tasks.
|
||||
for (const task of tasks) {
|
||||
while (!taskQueue.allTasksCompleted) {
|
||||
const task = taskQueue.getNextTask() !;
|
||||
compile(task);
|
||||
taskQueue.markTaskCompleted(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
import {PartiallyOrderedTasks, Task, TaskQueue} from '../api';
|
||||
import {stringifyTask} from '../utils';
|
||||
|
||||
|
||||
/**
|
||||
* A base `TaskQueue` implementation to be used as base for concrete implementations.
|
||||
*/
|
||||
export abstract class BaseTaskQueue implements TaskQueue {
|
||||
get allTasksCompleted(): boolean {
|
||||
return (this.tasks.length === 0) && (this.inProgressTasks.size === 0);
|
||||
}
|
||||
protected inProgressTasks = new Set<Task>();
|
||||
|
||||
constructor(protected tasks: PartiallyOrderedTasks) {}
|
||||
|
||||
abstract getNextTask(): Task|null;
|
||||
|
||||
markTaskCompleted(task: Task): void {
|
||||
if (!this.inProgressTasks.has(task)) {
|
||||
throw new Error(
|
||||
`Trying to mark task that was not in progress as completed: ${stringifyTask(task)}`);
|
||||
}
|
||||
|
||||
this.inProgressTasks.delete(task);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
const inProgTasks = Array.from(this.inProgressTasks);
|
||||
|
||||
return `${this.constructor.name}\n` +
|
||||
` All tasks completed: ${this.allTasksCompleted}\n` +
|
||||
` Unprocessed tasks (${this.tasks.length}): ${this.stringifyTasks(this.tasks, ' ')}\n` +
|
||||
` In-progress tasks (${inProgTasks.length}): ${this.stringifyTasks(inProgTasks, ' ')}`;
|
||||
}
|
||||
|
||||
protected stringifyTasks(tasks: Task[], indentation: string): string {
|
||||
return tasks.map(task => `\n${indentation}- ${stringifyTask(task)}`).join('');
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
import {Task} from '../api';
|
||||
import {stringifyTask} from '../utils';
|
||||
|
||||
import {BaseTaskQueue} from './base_task_queue';
|
||||
|
||||
|
||||
/**
|
||||
* A `TaskQueue` implementation that assumes tasks are processed serially and each one is completed
|
||||
* before requesting the next one.
|
||||
*/
|
||||
export class SerialTaskQueue extends BaseTaskQueue {
|
||||
getNextTask(): Task|null {
|
||||
const nextTask = this.tasks.shift() || null;
|
||||
|
||||
if (nextTask) {
|
||||
if (this.inProgressTasks.size > 0) {
|
||||
// `SerialTaskQueue` can have max one in-progress task.
|
||||
const inProgressTask = this.inProgressTasks.values().next().value;
|
||||
throw new Error(
|
||||
'Trying to get next task, while there is already a task in progress: ' +
|
||||
stringifyTask(inProgressTask));
|
||||
}
|
||||
|
||||
this.inProgressTasks.add(nextTask);
|
||||
}
|
||||
|
||||
return nextTask;
|
||||
}
|
||||
}
|
@ -32,3 +32,7 @@ export const onTaskCompleted =
|
||||
pkgJsonUpdater, entryPoint.packageJson, packageJsonPath, propsToMarkAsProcessed);
|
||||
}
|
||||
};
|
||||
|
||||
/** Stringify a task for debugging purposes. */
|
||||
export const stringifyTask = (task: Task): string =>
|
||||
`{entryPoint: ${task.entryPoint.name}, formatProperty: ${task.formatProperty}, processDts: ${task.processDts}}`;
|
||||
|
Reference in New Issue
Block a user