diff --git a/packages/compiler-cli/ngcc/main-ngcc.ts b/packages/compiler-cli/ngcc/main-ngcc.ts index bc385c8253..41a10d0112 100644 --- a/packages/compiler-cli/ngcc/main-ngcc.ts +++ b/packages/compiler-cli/ngcc/main-ngcc.ts @@ -76,6 +76,13 @@ if (require.main === module) { describe: 'The lowest severity logging message that should be output.', choices: ['debug', 'info', 'warn', 'error'], }) + .option('invalidate-entry-point-manifest', { + describe: + 'If this is set then ngcc will not read an entry-point manifest file from disk.\n' + + 'Instead it will walk the directory tree as normal looking for entry-points, and then write a new manifest file.', + type: 'boolean', + default: false, + }) .strict() .help() .parse(args); @@ -95,6 +102,7 @@ if (require.main === module) { const createNewEntryPointFormats = options['create-ivy-entry-points']; const logLevel = options['l'] as keyof typeof LogLevel | undefined; const enableI18nLegacyMessageIdFormat = options['legacy-message-ids']; + const invalidateEntryPointManifest = options['invalidate-entry-point-manifest']; (async() => { try { @@ -108,7 +116,7 @@ if (require.main === module) { createNewEntryPointFormats, logger, enableI18nLegacyMessageIdFormat, - async: options['async'], + async: options['async'], invalidateEntryPointManifest, }); if (logger) { diff --git a/packages/compiler-cli/ngcc/src/main.ts b/packages/compiler-cli/ngcc/src/main.ts index b7a1ca3707..d7fd2fb9b7 100644 --- a/packages/compiler-cli/ngcc/src/main.ts +++ b/packages/compiler-cli/ngcc/src/main.ts @@ -39,7 +39,7 @@ import {hasBeenProcessed} from './packages/build_marker'; import {NgccConfiguration} from './packages/configuration'; import {EntryPoint, EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES, getEntryPointFormat} from './packages/entry_point'; import {makeEntryPointBundle} from './packages/entry_point_bundle'; -import {EntryPointManifest} from './packages/entry_point_manifest'; +import {EntryPointManifest, InvalidatingEntryPointManifest} from './packages/entry_point_manifest'; import {Transformer} from './packages/transformer'; import {PathMappings} from './utils'; import {cleanOutdatedPackages} from './writing/cleaning/package_cleaner'; @@ -117,6 +117,15 @@ export interface SyncNgccOptions { * legacy message ids will all be stripped during translation. */ enableI18nLegacyMessageIdFormat?: boolean; + + /** + * Whether to invalidate any entry-point manifest file that is on disk. Instead, walk the + * directory tree looking for entry-points, and then write a new entry-point manifest, if + * possible. + * + * Default: `false` (i.e. the manifest will be used if available) + */ + invalidateEntryPointManifest?: boolean; } /** @@ -139,11 +148,12 @@ export type NgccOptions = AsyncNgccOptions | SyncNgccOptions; */ export function mainNgcc(options: AsyncNgccOptions): Promise; export function mainNgcc(options: SyncNgccOptions): void; -export function mainNgcc( - {basePath, targetEntryPointPath, propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, - compileAllFormats = true, createNewEntryPointFormats = false, - logger = new ConsoleLogger(LogLevel.info), pathMappings, async = false, - enableI18nLegacyMessageIdFormat = true}: NgccOptions): void|Promise { +export function mainNgcc({basePath, targetEntryPointPath, + propertiesToConsider = SUPPORTED_FORMAT_PROPERTIES, + compileAllFormats = true, createNewEntryPointFormats = false, + logger = new ConsoleLogger(LogLevel.info), pathMappings, async = false, + enableI18nLegacyMessageIdFormat = true, + invalidateEntryPointManifest = false}: NgccOptions): void|Promise { // Execute in parallel, if async execution is acceptable and there are more than 1 CPU cores. const inParallel = async && (os.cpus().length > 1); @@ -154,7 +164,9 @@ export function mainNgcc( const absBasePath = absoluteFrom(basePath); const config = new NgccConfiguration(fileSystem, dirname(absBasePath)); const dependencyResolver = getDependencyResolver(fileSystem, logger, config, pathMappings); - const entryPointManifest = new EntryPointManifest(fileSystem, config, logger); + const entryPointManifest = invalidateEntryPointManifest ? + new InvalidatingEntryPointManifest(fileSystem, config, logger) : + new EntryPointManifest(fileSystem, config, logger); // Bail out early if the work is already done. const supportedPropertiesToConsider = ensureSupportedProperties(propertiesToConsider); diff --git a/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts b/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts index 6c44e66a10..8df1af7900 100644 --- a/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts +++ b/packages/compiler-cli/ngcc/src/packages/entry_point_manifest.ts @@ -129,6 +129,18 @@ export class EntryPointManifest { } } +/** + * A specialized implementation of the `EntryPointManifest` that can be used to invalidate the + * current manifest file. + * + * It always returns `null` from the `readEntryPointsUsingManifest()` method, which forces a new + * manifest to be created, which will overwrite the current file when `writeEntryPointManifest()` is + * called. + */ +export class InvalidatingEntryPointManifest extends EntryPointManifest { + readEntryPointsUsingManifest(basePath: AbsoluteFsPath): EntryPoint[]|null { return null; } +} + /** * The JSON format of the manifest file that is written to disk. */ diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 698c660a9b..0d68b90943 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -7,22 +7,20 @@ */ /// - import * as os from 'os'; - import {AbsoluteFsPath, FileSystem, absoluteFrom, getFileSystem, join} from '../../../src/ngtsc/file_system'; import {Folder, MockFileSystem, TestFile, runInEachFileSystem} from '../../../src/ngtsc/file_system/testing'; import {loadStandardTestFiles, loadTestFiles} from '../../../test/helpers'; import {getLockFilePath} from '../../src/locking/lock_file'; import {mainNgcc} from '../../src/main'; -import {markAsProcessed} from '../../src/packages/build_marker'; +import {hasBeenProcessed, markAsProcessed} from '../../src/packages/build_marker'; import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTIES} from '../../src/packages/entry_point'; +import {EntryPointManifestFile} from '../../src/packages/entry_point_manifest'; import {Transformer} from '../../src/packages/transformer'; import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater'; import {MockLogger} from '../helpers/mock_logger'; import {compileIntoApf, compileIntoFlatEs5Package} from './util'; - const testFiles = loadStandardTestFiles({fakeCore: false, rxjs: true}); runInEachFileSystem(() => { @@ -982,6 +980,46 @@ runInEachFileSystem(() => { }); }); + describe('with ignoreEntryPointManifest', () => { + it('should not read the entry-point manifest file', () => { + // Ensure there is a lock-file. Otherwise the manifest will not be written + fs.writeFile(_('/yarn.lock'), 'DUMMY YARN LOCK FILE'); + // Populate the manifest file + mainNgcc( + {basePath: '/node_modules', propertiesToConsider: ['esm5'], logger: new MockLogger()}); + // Check that common/testings ES5 was processed + let commonTesting = + JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json'))); + expect(hasBeenProcessed(commonTesting, 'esm5')).toBe(true); + expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(false); + // Modify the manifest to test that is has no effect + let manifest: EntryPointManifestFile = + JSON.parse(fs.readFile(_('/node_modules/__ngcc_entry_points__.json'))); + manifest.entryPointPaths = manifest.entryPointPaths.filter( + paths => paths[1] !== _('/node_modules/@angular/common/testing')); + fs.writeFile(_('/node_modules/__ngcc_entry_points__.json'), JSON.stringify(manifest)); + // Now run ngcc again ignoring this manifest but trying to process ES2015, which are not yet + // processed. + mainNgcc({ + basePath: '/node_modules', + propertiesToConsider: ['esm2015'], + logger: new MockLogger(), + invalidateEntryPointManifest: true, + }); + // Check that common/testing ES2015 is now processed, despite the manifest not listing it + commonTesting = + JSON.parse(fs.readFile(_('/node_modules/@angular/common/testing/package.json'))); + expect(hasBeenProcessed(commonTesting, 'esm5')).toBe(true); + expect(hasBeenProcessed(commonTesting, 'esm2015')).toBe(true); + // Check that the newly computed manifest has written to disk, containing the path that we + // had removed earlier. + manifest = JSON.parse(fs.readFile(_('/node_modules/__ngcc_entry_points__.json'))); + expect(manifest.entryPointPaths).toContain([ + _('/node_modules/@angular/common'), _('/node_modules/@angular/common/testing') + ]); + }); + }); + describe('diagnostics', () => { it('should fail with formatted diagnostics when an error diagnostic is produced', () => { loadTestFiles([