diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index 30ddbef9f7..4efd1e7d15 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -5,7 +5,14 @@ set -e -x PATH=$PATH:$(npm bin) ivy-ngcc fesm2015,esm2015 +# Did it add the appropriate build markers? +ls node_modules/@angular/common | grep __modified_by_ngcc_for_fesm2015 +if [[ $? != 0 ]]; then exit 1; fi +ls node_modules/@angular/common | grep __modified_by_ngcc_for_esm2015 +if [[ $? != 0 ]]; then exit 1; fi + ngc -p tsconfig-app.json -# Look for correct output -grep "directives: \[\S*\.NgIf\]" dist/src/main.js > /dev/null +# Did it compile the main.ts correctly? +grep "directives: \[\S*\.NgIf\]" dist/src/main.js +if [[ $? != 0 ]]; then exit 1; fi diff --git a/packages/compiler-cli/src/ngcc/src/transform/package_transformer.ts b/packages/compiler-cli/src/ngcc/src/transform/package_transformer.ts index 815d129b48..a0dc42e3ba 100644 --- a/packages/compiler-cli/src/ngcc/src/transform/package_transformer.ts +++ b/packages/compiler-cli/src/ngcc/src/transform/package_transformer.ts @@ -25,9 +25,7 @@ import {Esm2015Renderer} from '../rendering/esm2015_renderer'; import {Esm5Renderer} from '../rendering/esm5_renderer'; import {FileInfo, Renderer} from '../rendering/renderer'; -import {getEntryPoints} from './utils'; - - +import {checkMarkerFile, findAllPackageJsonFiles, getEntryPoints, writeMarkerFile} from './utils'; /** * A Package is stored in a directory on disk and that directory can contain one or more package @@ -52,7 +50,11 @@ export class PackageTransformer { transform(packagePath: string, format: string, targetPath: string = 'node_modules'): void { const sourceNodeModules = this.findNodeModulesPath(packagePath); const targetNodeModules = resolve(sourceNodeModules, '..', targetPath); - const entryPoints = getEntryPoints(packagePath, format); + const packageJsonPaths = + findAllPackageJsonFiles(packagePath) + // Ignore paths that have been built already + .filter(packageJsonPath => !checkMarkerFile(packageJsonPath, format)); + const entryPoints = getEntryPoints(packageJsonPaths, format); entryPoints.forEach(entryPoint => { const outputFiles: FileInfo[] = []; @@ -102,6 +104,9 @@ export class PackageTransformer { // Write out all the transformed files. outputFiles.forEach(file => this.writeFile(file)); }); + + // Write the built-with-ngcc markers + packageJsonPaths.forEach(packageJsonPath => { writeMarkerFile(packageJsonPath, format); }); } getHost(format: string, program: ts.Program, dtsMapper: DtsMapper): NgccReflectionHost { diff --git a/packages/compiler-cli/src/ngcc/src/transform/utils.ts b/packages/compiler-cli/src/ngcc/src/transform/utils.ts index caa042f36e..8e4a0bdce5 100644 --- a/packages/compiler-cli/src/ngcc/src/transform/utils.ts +++ b/packages/compiler-cli/src/ngcc/src/transform/utils.ts @@ -6,12 +6,14 @@ * found in the LICENSE file at https://angular.io/license */ -import {readFileSync} from 'fs'; -import {dirname, relative, resolve} from 'path'; +import {existsSync, readFileSync, writeFileSync} from 'fs'; +import {dirname, resolve} from 'path'; import {find} from 'shelljs'; import {isDefined} from '../utils'; +export const NGCC_VERSION = '0.0.0-PLACEHOLDER'; + /** * Represents an entry point to a package or sub-package. * @@ -29,7 +31,7 @@ export class EntryPoint { * @param relativeEntryPath The relative path to the entry point file. * @param relativeDtsEntryPath The relative path to the `.d.ts` entry point file. */ - constructor(packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath: string) { + constructor(public packageRoot: string, relativeEntryPath: string, relativeDtsEntryPath: string) { this.entryFileName = resolve(packageRoot, relativeEntryPath); this.entryRoot = dirname(this.entryFileName); const dtsEntryFileName = resolve(packageRoot, relativeDtsEntryPath); @@ -63,15 +65,14 @@ export function findAllPackageJsonFiles(rootDirectory: string): string[] { } /** - * Identify the entry points of a package. + * Identify the entry points of a collection of package.json files. * - * @param packageDirectory The absolute path to the root directory that contains the package. + * @param packageJsonPaths A collection of absolute paths to the package.json files. * @param format The format of the entry points to look for within the package. * * @returns A collection of `EntryPoint`s that correspond to entry points for the package. */ -export function getEntryPoints(packageDirectory: string, format: string): EntryPoint[] { - const packageJsonPaths = findAllPackageJsonFiles(packageDirectory); +export function getEntryPoints(packageJsonPaths: string[], format: string): EntryPoint[] { const entryPoints = packageJsonPaths .map(packageJsonPath => { @@ -86,3 +87,26 @@ export function getEntryPoints(packageDirectory: string, format: string): EntryP .filter(isDefined); return entryPoints; } + +function getMarkerPath(packageJsonPath: string, format: string) { + return resolve(dirname(packageJsonPath), `__modified_by_ngcc_for_${format}__`); +} + +export function checkMarkerFile(packageJsonPath: string, format: string) { + const markerPath = getMarkerPath(packageJsonPath, format); + const markerExists = existsSync(markerPath); + if (markerExists) { + const previousVersion = readFileSync(markerPath, 'utf8'); + if (previousVersion !== NGCC_VERSION) { + throw new Error( + 'The ngcc compiler has changed since the last ngcc build.\n' + + 'Please completely remove `node_modules` and try again.'); + } + } + return markerExists; +} + +export function writeMarkerFile(packageJsonPath: string, format: string) { + const markerPath = getMarkerPath(packageJsonPath, format); + writeFileSync(markerPath, NGCC_VERSION, 'utf8'); +} \ No newline at end of file diff --git a/packages/compiler-cli/src/ngcc/test/transform/utils_spec.ts b/packages/compiler-cli/src/ngcc/test/transform/utils_spec.ts index 5b14e4d39f..f0d2d20a3e 100644 --- a/packages/compiler-cli/src/ngcc/test/transform/utils_spec.ts +++ b/packages/compiler-cli/src/ngcc/test/transform/utils_spec.ts @@ -6,8 +6,10 @@ * found in the LICENSE file at https://angular.io/license */ +import {existsSync, readFileSync, writeFileSync} from 'fs'; import * as mockFs from 'mock-fs'; -import {EntryPoint, findAllPackageJsonFiles, getEntryPoints} from '../../src/transform/utils'; + +import {EntryPoint, checkMarkerFile, findAllPackageJsonFiles, getEntryPoints, writeMarkerFile} from '../../src/transform/utils'; function createMockFileSystem() { mockFs({ @@ -95,6 +97,11 @@ describe('EntryPoint', () => { const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js', '/typings/foo/bar.d.ts'); expect(entryPoint.entryFileName).toBe('/foo/baz/quux.js'); }); + + it('should expose the package root for the entry point file', () => { + const entryPoint = new EntryPoint('/foo/bar', '../baz/qux/../quux.js', '/typings/foo/bar.d.ts'); + expect(entryPoint.packageRoot).toBe('/foo/bar'); + }); }); describe('findAllPackageJsonFiles()', () => { @@ -138,7 +145,14 @@ describe('getEntryPoints()', () => { afterEach(restoreRealFileSystem); it('should return the entry points for the specified format from each `package.json`', () => { - const entryPoints = getEntryPoints('/node_modules/@angular/common', 'fesm2015'); + const entryPoints = getEntryPoints( + [ + '/node_modules/@angular/common/package.json', + '/node_modules/@angular/common/http/package.json', + '/node_modules/@angular/common/http/testing/package.json', + '/node_modules/@angular/common/testing/package.json' + ], + 'fesm2015'); entryPoints.forEach(ep => expect(ep).toEqual(jasmine.any(EntryPoint))); const sortedPaths = entryPoints.map(x => x.entryFileName).sort(); @@ -151,17 +165,18 @@ describe('getEntryPoints()', () => { }); it('should return an empty array if there are no matching `package.json` files', () => { - const entryPoints = getEntryPoints('/node_modules/@angular/other', 'fesm2015'); + const entryPoints = getEntryPoints([], 'fesm2015'); expect(entryPoints).toEqual([]); }); it('should return an empty array if there are no matching formats', () => { - const entryPoints = getEntryPoints('/node_modules/@angular/common', 'fesm3000'); + const entryPoints = getEntryPoints(['/node_modules/@angular/common/package.json'], 'fesm3000'); expect(entryPoints).toEqual([]); }); it('should return an entry point even if the typings are not specified', () => { - const entryPoints = getEntryPoints('/node_modules/@angular/no-typings', 'fesm2015'); + const entryPoints = + getEntryPoints(['/node_modules/@angular/no-typings/package.json'], 'fesm2015'); expect(entryPoints.length).toEqual(1); expect(entryPoints[0].entryFileName) .toEqual('/node_modules/@angular/no-typings/fesm2015/index.js'); @@ -169,3 +184,57 @@ describe('getEntryPoints()', () => { expect(entryPoints[0].dtsEntryRoot).toEqual(entryPoints[0].entryRoot); }); }); + +describe('Marker files', () => { + beforeEach(createMockFileSystem); + afterEach(restoreRealFileSystem); + + describe('writeMarkerFile', () => { + it('should write a file containing the version placeholder', () => { + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__')) + .toBe(false); + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false); + + writeMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015'); + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__')) + .toBe(true); + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(false); + expect( + readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8')) + .toEqual('0.0.0-PLACEHOLDER'); + + writeMarkerFile('/node_modules/@angular/common/package.json', 'esm5'); + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__')) + .toBe(true); + expect(existsSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__')).toBe(true); + expect( + readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'utf8')) + .toEqual('0.0.0-PLACEHOLDER'); + expect(readFileSync('/node_modules/@angular/common/__modified_by_ngcc_for_esm5__', 'utf8')) + .toEqual('0.0.0-PLACEHOLDER'); + }); + }); + + describe('checkMarkerFile', () => { + it('should return false if the marker file does not exist', () => { + expect(checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015')).toBe(false); + }); + + it('should return true if the marker file exists and contains the correct version', () => { + writeFileSync( + '/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', '0.0.0-PLACEHOLDER', + 'utf8'); + expect(checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015')).toBe(true); + }); + + it('should throw if the marker file exists but contains the wrong version', () => { + writeFileSync( + '/node_modules/@angular/common/__modified_by_ngcc_for_fesm2015__', 'WRONG_VERSION', + 'utf8'); + expect(() => checkMarkerFile('/node_modules/@angular/common/package.json', 'fesm2015')) + .toThrowError( + 'The ngcc compiler has changed since the last ngcc build.\n' + + 'Please completely remove `node_modules` and try again.'); + }); + }); +});