The `Logger` interface and its related classes are general purpose and could be used by other tooling. Moving it into ngtsc is a more suitable place from which to share it - similar to the FileSystem stuff. PR Close #37114
173 lines
7.5 KiB
TypeScript
173 lines
7.5 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC 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 {DepGraph} from 'dependency-graph';
|
|
|
|
import {FileSystem} from '../../../src/ngtsc/file_system';
|
|
import {Logger} from '../../../src/ngtsc/logging';
|
|
import {InvalidEntryPoint} from '../dependencies/dependency_resolver';
|
|
import {EntryPointFinder} from '../entry_point_finder/interface';
|
|
import {ParallelTaskQueue} from '../execution/tasks/queues/parallel_task_queue';
|
|
import {SerialTaskQueue} from '../execution/tasks/queues/serial_task_queue';
|
|
import {computeTaskDependencies} from '../execution/tasks/utils';
|
|
import {hasBeenProcessed} from '../packages/build_marker';
|
|
import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../packages/entry_point';
|
|
import {cleanOutdatedPackages} from '../writing/cleaning/package_cleaner';
|
|
|
|
import {AnalyzeEntryPointsFn} from './api';
|
|
import {PartiallyOrderedTasks, TaskQueue} from './tasks/api';
|
|
|
|
/**
|
|
* Create the function for performing the analysis of the entry-points.
|
|
*/
|
|
export function getAnalyzeEntryPointsFn(
|
|
logger: Logger, finder: EntryPointFinder, fileSystem: FileSystem,
|
|
supportedPropertiesToConsider: EntryPointJsonProperty[], compileAllFormats: boolean,
|
|
propertiesToConsider: string[], inParallel: boolean): AnalyzeEntryPointsFn {
|
|
return () => {
|
|
logger.debug('Analyzing entry-points...');
|
|
const startTime = Date.now();
|
|
|
|
let entryPointInfo = finder.findEntryPoints();
|
|
const cleaned = cleanOutdatedPackages(fileSystem, entryPointInfo.entryPoints);
|
|
if (cleaned) {
|
|
// If we had to clean up one or more packages then we must read in the entry-points again.
|
|
entryPointInfo = finder.findEntryPoints();
|
|
}
|
|
|
|
const {entryPoints, invalidEntryPoints, graph} = entryPointInfo;
|
|
logInvalidEntryPoints(logger, invalidEntryPoints);
|
|
|
|
const unprocessableEntryPointPaths: string[] = [];
|
|
// The tasks are partially ordered by virtue of the entry-points being partially ordered too.
|
|
const tasks: PartiallyOrderedTasks = [] as any;
|
|
|
|
for (const entryPoint of entryPoints) {
|
|
const packageJson = entryPoint.packageJson;
|
|
const hasProcessedTypings = hasBeenProcessed(packageJson, 'typings');
|
|
const {propertiesToProcess, equivalentPropertiesMap} =
|
|
getPropertiesToProcess(packageJson, supportedPropertiesToConsider, compileAllFormats);
|
|
let processDts = !hasProcessedTypings;
|
|
|
|
if (propertiesToProcess.length === 0) {
|
|
// This entry-point is unprocessable (i.e. there is no format property that is of interest
|
|
// and can be processed). This will result in an error, but continue looping over
|
|
// entry-points in order to collect all unprocessable ones and display a more informative
|
|
// error.
|
|
unprocessableEntryPointPaths.push(entryPoint.path);
|
|
continue;
|
|
}
|
|
|
|
for (const formatProperty of propertiesToProcess) {
|
|
if (hasBeenProcessed(entryPoint.packageJson, formatProperty)) {
|
|
// The format-path which the property maps to is already processed - nothing to do.
|
|
logger.debug(`Skipping ${entryPoint.name} : ${formatProperty} (already compiled).`);
|
|
continue;
|
|
}
|
|
|
|
const formatPropertiesToMarkAsProcessed = equivalentPropertiesMap.get(formatProperty)!;
|
|
tasks.push({entryPoint, formatProperty, formatPropertiesToMarkAsProcessed, processDts});
|
|
|
|
// Only process typings for the first property (if not already processed).
|
|
processDts = false;
|
|
}
|
|
}
|
|
|
|
// Check for entry-points for which we could not process any format at all.
|
|
if (unprocessableEntryPointPaths.length > 0) {
|
|
throw new Error(
|
|
'Unable to process any formats for the following entry-points (tried ' +
|
|
`${propertiesToConsider.join(', ')}): ` +
|
|
unprocessableEntryPointPaths.map(path => `\n - ${path}`).join(''));
|
|
}
|
|
|
|
const duration = Math.round((Date.now() - startTime) / 100) / 10;
|
|
logger.debug(
|
|
`Analyzed ${entryPoints.length} entry-points in ${duration}s. ` +
|
|
`(Total tasks: ${tasks.length})`);
|
|
|
|
return getTaskQueue(logger, inParallel, tasks, graph);
|
|
};
|
|
}
|
|
|
|
function logInvalidEntryPoints(logger: Logger, invalidEntryPoints: InvalidEntryPoint[]): void {
|
|
invalidEntryPoints.forEach(invalidEntryPoint => {
|
|
logger.debug(
|
|
`Invalid entry-point ${invalidEntryPoint.entryPoint.path}.`,
|
|
`It is missing required dependencies:\n` +
|
|
invalidEntryPoint.missingDependencies.map(dep => ` - ${dep}`).join('\n'));
|
|
});
|
|
}
|
|
|
|
/**
|
|
* This function computes and returns the following:
|
|
* - `propertiesToProcess`: An (ordered) list of properties that exist and need to be processed,
|
|
* based on the provided `propertiesToConsider`, the properties in `package.json` and their
|
|
* corresponding format-paths. NOTE: Only one property per format-path needs to be processed.
|
|
* - `equivalentPropertiesMap`: A mapping from each property in `propertiesToProcess` to the list of
|
|
* other format properties in `package.json` that need to be marked as processed as soon as the
|
|
* former has been processed.
|
|
*/
|
|
function getPropertiesToProcess(
|
|
packageJson: EntryPointPackageJson, propertiesToConsider: EntryPointJsonProperty[],
|
|
compileAllFormats: boolean): {
|
|
propertiesToProcess: EntryPointJsonProperty[];
|
|
equivalentPropertiesMap: Map<EntryPointJsonProperty, EntryPointJsonProperty[]>;
|
|
} {
|
|
const formatPathsToConsider = new Set<string>();
|
|
|
|
const propertiesToProcess: EntryPointJsonProperty[] = [];
|
|
for (const prop of propertiesToConsider) {
|
|
const formatPath = packageJson[prop];
|
|
|
|
// Ignore properties that are not defined in `package.json`.
|
|
if (typeof formatPath !== 'string') continue;
|
|
|
|
// Ignore properties that map to the same format-path as a preceding property.
|
|
if (formatPathsToConsider.has(formatPath)) continue;
|
|
|
|
// Process this property, because it is the first one to map to this format-path.
|
|
formatPathsToConsider.add(formatPath);
|
|
propertiesToProcess.push(prop);
|
|
|
|
// If we only need one format processed, there is no need to process any more properties.
|
|
if (!compileAllFormats) break;
|
|
}
|
|
|
|
const formatPathToProperties: {[formatPath: string]: EntryPointJsonProperty[]} = {};
|
|
for (const prop of SUPPORTED_FORMAT_PROPERTIES) {
|
|
const formatPath = packageJson[prop];
|
|
|
|
// Ignore properties that are not defined in `package.json`.
|
|
if (typeof formatPath !== 'string') continue;
|
|
|
|
// Ignore properties that do not map to a format-path that will be considered.
|
|
if (!formatPathsToConsider.has(formatPath)) continue;
|
|
|
|
// Add this property to the map.
|
|
const list = formatPathToProperties[formatPath] || (formatPathToProperties[formatPath] = []);
|
|
list.push(prop);
|
|
}
|
|
|
|
const equivalentPropertiesMap = new Map<EntryPointJsonProperty, EntryPointJsonProperty[]>();
|
|
for (const prop of propertiesToConsider) {
|
|
const formatPath = packageJson[prop]!;
|
|
const equivalentProperties = formatPathToProperties[formatPath];
|
|
equivalentPropertiesMap.set(prop, equivalentProperties);
|
|
}
|
|
|
|
return {propertiesToProcess, equivalentPropertiesMap};
|
|
}
|
|
|
|
function getTaskQueue(
|
|
logger: Logger, inParallel: boolean, tasks: PartiallyOrderedTasks,
|
|
graph: DepGraph<EntryPoint>): TaskQueue {
|
|
const dependencies = computeTaskDependencies(tasks, graph);
|
|
return inParallel ? new ParallelTaskQueue(logger, tasks, dependencies) :
|
|
new SerialTaskQueue(logger, tasks, dependencies);
|
|
}
|