176 lines
7.8 KiB
TypeScript
Raw Normal View History

/**
* @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 {DtsProcessing, 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[], typingsOnly: boolean,
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, typingsOnly);
let processDts = hasProcessedTypings ? DtsProcessing.No :
typingsOnly ? DtsProcessing.Only : DtsProcessing.Yes;
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 = DtsProcessing.No;
}
}
// 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, typingsOnly: 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]!;
// If we are only processing typings then there should be no format properties to mark
const equivalentProperties = typingsOnly ? [] : 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);
}