Skip to content

Commit c9f554c

Browse files
petebacondarwinAndrewKushnir
authored andcommitted
fix(ngcc): do not crash on overlapping entry-points (#36083)
When two entry-points overlap, ngcc may attempt to process some files twice. Previously, when this occured ngcc would just exit with an error preventing any other entry-points from being processed. This commit changes ngcc so that if `errorOnFailedEntryPoint` is false, it will simply log an error and continue to process entry-points. This is useful when ngcc is processing the entire node_modules folder and there are some invalid entry-points that the project doesn't actually use. PR Close #36083
1 parent ff665b9 commit c9f554c

File tree

5 files changed

+79
-34
lines changed

5 files changed

+79
-34
lines changed

packages/compiler-cli/ngcc/src/main.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,8 @@ export function mainNgcc({basePath, targetEntryPointPath,
277277

278278
// The function for creating the `compile()` function.
279279
const createCompileFn: CreateCompileFn = onTaskCompleted => {
280-
const fileWriter = getFileWriter(fileSystem, pkgJsonUpdater, createNewEntryPointFormats);
280+
const fileWriter = getFileWriter(
281+
fileSystem, logger, pkgJsonUpdater, createNewEntryPointFormats, errorOnFailedEntryPoint);
281282
const transformer = new Transformer(fileSystem, logger);
282283

283284
return (task: Task) => {
@@ -362,10 +363,11 @@ function getPackageJsonUpdater(inParallel: boolean, fs: FileSystem): PackageJson
362363
}
363364

364365
function getFileWriter(
365-
fs: FileSystem, pkgJsonUpdater: PackageJsonUpdater,
366-
createNewEntryPointFormats: boolean): FileWriter {
367-
return createNewEntryPointFormats ? new NewEntryPointFileWriter(fs, pkgJsonUpdater) :
368-
new InPlaceFileWriter(fs);
366+
fs: FileSystem, logger: Logger, pkgJsonUpdater: PackageJsonUpdater,
367+
createNewEntryPointFormats: boolean, errorOnFailedEntryPoint: boolean): FileWriter {
368+
return createNewEntryPointFormats ?
369+
new NewEntryPointFileWriter(fs, logger, errorOnFailedEntryPoint, pkgJsonUpdater) :
370+
new InPlaceFileWriter(fs, logger, errorOnFailedEntryPoint);
369371
}
370372

371373
function getTaskQueue(

packages/compiler-cli/ngcc/src/writing/in_place_file_writer.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
/**
32
* @license
43
* Copyright Google Inc. All Rights Reserved.
@@ -7,6 +6,7 @@
76
* found in the LICENSE file at https://angular.io/license
87
*/
98
import {FileSystem, absoluteFrom, dirname} from '../../../src/ngtsc/file_system';
9+
import {Logger} from '../logging/logger';
1010
import {EntryPointJsonProperty} from '../packages/entry_point';
1111
import {EntryPointBundle} from '../packages/entry_point_bundle';
1212
import {FileToWrite} from '../rendering/utils';
@@ -18,7 +18,9 @@ export const NGCC_BACKUP_EXTENSION = '.__ivy_ngcc_bak';
1818
* a back-up of the original file with an extra `.__ivy_ngcc_bak` extension.
1919
*/
2020
export class InPlaceFileWriter implements FileWriter {
21-
constructor(protected fs: FileSystem) {}
21+
constructor(
22+
protected fs: FileSystem, protected logger: Logger,
23+
protected errorOnFailedEntryPoint: boolean) {}
2224

2325
writeBundle(
2426
_bundle: EntryPointBundle, transformedFiles: FileToWrite[],
@@ -30,12 +32,20 @@ export class InPlaceFileWriter implements FileWriter {
3032
this.fs.ensureDir(dirname(file.path));
3133
const backPath = absoluteFrom(`${file.path}${NGCC_BACKUP_EXTENSION}`);
3234
if (this.fs.exists(backPath)) {
33-
throw new Error(
34-
`Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`);
35-
}
36-
if (this.fs.exists(file.path)) {
37-
this.fs.moveFile(file.path, backPath);
35+
if (this.errorOnFailedEntryPoint) {
36+
throw new Error(
37+
`Tried to overwrite ${backPath} with an ngcc back up file, which is disallowed.`);
38+
} else {
39+
this.logger.error(
40+
`Tried to write ${backPath} with an ngcc back up file but it already exists so not writing, nor backing up, ${file.path}.\n` +
41+
`This error may be because two or more entry-points overlap and ngcc has been asked to process some files more than once.\n` +
42+
`You should check other entry-points in this package and set up a config to ignore any that you are not using.`);
43+
}
44+
} else {
45+
if (this.fs.exists(file.path)) {
46+
this.fs.moveFile(file.path, backPath);
47+
}
48+
this.fs.writeFile(file.path, file.contents);
3849
}
39-
this.fs.writeFile(file.path, file.contents);
4050
}
4151
}

packages/compiler-cli/ngcc/src/writing/new_entry_point_file_writer.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
*/
99
import {AbsoluteFsPath, FileSystem, absoluteFromSourceFile, dirname, join, relative} from '../../../src/ngtsc/file_system';
1010
import {isDtsPath} from '../../../src/ngtsc/util/src/typescript';
11+
import {Logger} from '../logging/logger';
1112
import {EntryPoint, EntryPointJsonProperty} from '../packages/entry_point';
1213
import {EntryPointBundle} from '../packages/entry_point_bundle';
1314
import {FileToWrite} from '../rendering/utils';
@@ -27,7 +28,11 @@ export const NGCC_PROPERTY_EXTENSION = '_ivy_ngcc';
2728
* `InPlaceFileWriter`).
2829
*/
2930
export class NewEntryPointFileWriter extends InPlaceFileWriter {
30-
constructor(fs: FileSystem, private pkgJsonUpdater: PackageJsonUpdater) { super(fs); }
31+
constructor(
32+
fs: FileSystem, logger: Logger, errorOnFailedEntryPoint: boolean,
33+
private pkgJsonUpdater: PackageJsonUpdater) {
34+
super(fs, logger, errorOnFailedEntryPoint);
35+
}
3136

3237
writeBundle(
3338
bundle: EntryPointBundle, transformedFiles: FileToWrite[],

packages/compiler-cli/ngcc/test/writing/in_place_file_writer_spec.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {runInEachFileSystem} from '../../../src/ngtsc/file_system/testing';
1010
import {loadTestFiles} from '../../../test/helpers';
1111
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
1212
import {InPlaceFileWriter} from '../../src/writing/in_place_file_writer';
13+
import {MockLogger} from '../helpers/mock_logger';
1314

1415
runInEachFileSystem(() => {
1516
describe('InPlaceFileWriter', () => {
@@ -24,13 +25,15 @@ runInEachFileSystem(() => {
2425
{name: _('/package/path/folder-1/file-2.js'), contents: 'ORIGINAL FILE 2'},
2526
{name: _('/package/path/folder-2/file-3.js'), contents: 'ORIGINAL FILE 3'},
2627
{name: _('/package/path/folder-2/file-4.js'), contents: 'ORIGINAL FILE 4'},
28+
{name: _('/package/path/already-backed-up.js'), contents: 'ORIGINAL ALREADY BACKED UP'},
2729
{name: _('/package/path/already-backed-up.js.__ivy_ngcc_bak'), contents: 'BACKED UP'},
2830
]);
2931
});
3032

3133
it('should write all the FileInfo to the disk', () => {
3234
const fs = getFileSystem();
33-
const fileWriter = new InPlaceFileWriter(fs);
35+
const logger = new MockLogger();
36+
const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true);
3437
fileWriter.writeBundle({} as EntryPointBundle, [
3538
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
3639
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
@@ -47,7 +50,8 @@ runInEachFileSystem(() => {
4750

4851
it('should create backups of all files that previously existed', () => {
4952
const fs = getFileSystem();
50-
const fileWriter = new InPlaceFileWriter(fs);
53+
const logger = new MockLogger();
54+
const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true);
5155
fileWriter.writeBundle({} as EntryPointBundle, [
5256
{path: _('/package/path/top-level.js'), contents: 'MODIFIED TOP LEVEL'},
5357
{path: _('/package/path/folder-1/file-1.js'), contents: 'MODIFIED FILE 1'},
@@ -65,15 +69,36 @@ runInEachFileSystem(() => {
6569
expect(fs.exists(_('/package/path/folder-3/file-5.js.__ivy_ngcc_bak'))).toBe(false);
6670
});
6771

68-
it('should error if the backup file already exists', () => {
69-
const fs = getFileSystem();
70-
const fileWriter = new InPlaceFileWriter(fs);
71-
const absoluteBackupPath = _('/package/path/already-backed-up.js');
72-
expect(
73-
() => fileWriter.writeBundle(
74-
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
75-
.toThrowError(
76-
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
77-
});
72+
it('should throw an error if the backup file already exists and errorOnFailedEntryPoint is true',
73+
() => {
74+
const fs = getFileSystem();
75+
const logger = new MockLogger();
76+
const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ true);
77+
const absoluteBackupPath = _('/package/path/already-backed-up.js');
78+
expect(
79+
() => fileWriter.writeBundle(
80+
{} as EntryPointBundle,
81+
[{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]))
82+
.toThrowError(
83+
`Tried to overwrite ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file, which is disallowed.`);
84+
});
85+
86+
it('should log an error, and skip writing the file, if the backup file already exists and errorOnFailedEntryPoint is false',
87+
() => {
88+
const fs = getFileSystem();
89+
const logger = new MockLogger();
90+
const fileWriter = new InPlaceFileWriter(fs, logger, /* errorOnFailedEntryPoint */ false);
91+
const absoluteBackupPath = _('/package/path/already-backed-up.js');
92+
fileWriter.writeBundle(
93+
{} as EntryPointBundle, [{path: absoluteBackupPath, contents: 'MODIFIED BACKED UP'}]);
94+
// Should not have written the new file nor overwritten the backup file.
95+
expect(fs.readFile(absoluteBackupPath)).toEqual('ORIGINAL ALREADY BACKED UP');
96+
expect(fs.readFile(_(absoluteBackupPath + '.__ivy_ngcc_bak'))).toEqual('BACKED UP');
97+
expect(logger.logs.error).toEqual([[
98+
`Tried to write ${absoluteBackupPath}.__ivy_ngcc_bak with an ngcc back up file but it already exists so not writing, nor backing up, ${absoluteBackupPath}.\n` +
99+
`This error may be because two or more entry-points overlap and ngcc has been asked to process some files more than once.\n` +
100+
`You should check other entry-points in this package and set up a config to ignore any that you are not using.`
101+
]]);
102+
});
78103
});
79104
});

packages/compiler-cli/ngcc/test/writing/new_entry_point_file_writer_spec.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,15 @@ runInEachFileSystem(() => {
2323
let _: typeof absoluteFrom;
2424
let fs: FileSystem;
2525
let fileWriter: FileWriter;
26+
let logger: MockLogger;
2627
let entryPoint: EntryPoint;
2728
let esm5bundle: EntryPointBundle;
2829
let esm2015bundle: EntryPointBundle;
2930

3031
beforeEach(() => {
3132
_ = absoluteFrom;
33+
fs = getFileSystem();
34+
logger = new MockLogger();
3235
loadTestFiles([
3336

3437
{
@@ -100,11 +103,11 @@ runInEachFileSystem(() => {
100103

101104
describe('writeBundle() [primary entry-point]', () => {
102105
beforeEach(() => {
103-
fs = getFileSystem();
104-
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
106+
fileWriter = new NewEntryPointFileWriter(
107+
fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs));
105108
const config = new NgccConfiguration(fs, _('/'));
106109
const result = getEntryPointInfo(
107-
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test')) !;
110+
fs, config, logger, _('/node_modules/test'), _('/node_modules/test')) !;
108111
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
109112
return fail(`Expected an entry point but got ${result}`);
110113
}
@@ -240,11 +243,11 @@ runInEachFileSystem(() => {
240243

241244
describe('writeBundle() [secondary entry-point]', () => {
242245
beforeEach(() => {
243-
fs = getFileSystem();
244-
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
246+
fileWriter = new NewEntryPointFileWriter(
247+
fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs));
245248
const config = new NgccConfiguration(fs, _('/'));
246249
const result = getEntryPointInfo(
247-
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/a')) !;
250+
fs, config, logger, _('/node_modules/test'), _('/node_modules/test/a')) !;
248251
if (result === NO_ENTRY_POINT || result === INVALID_ENTRY_POINT) {
249252
return fail(`Expected an entry point but got ${result}`);
250253
}
@@ -369,8 +372,8 @@ runInEachFileSystem(() => {
369372

370373
describe('writeBundle() [entry-point (with files placed outside entry-point folder)]', () => {
371374
beforeEach(() => {
372-
fs = getFileSystem();
373-
fileWriter = new NewEntryPointFileWriter(fs, new DirectPackageJsonUpdater(fs));
375+
fileWriter = new NewEntryPointFileWriter(
376+
fs, logger, /* errorOnFailedEntryPoint */ true, new DirectPackageJsonUpdater(fs));
374377
const config = new NgccConfiguration(fs, _('/'));
375378
const result = getEntryPointInfo(
376379
fs, config, new MockLogger(), _('/node_modules/test'), _('/node_modules/test/b')) !;

0 commit comments

Comments
 (0)