fix(ngcc): ensure that ngcc does not write a lock-file into node_modules package directories (#44228)

When executing, ngcc writes a lock-file that is used to coordinate multiple concurrent instances of ngcc.
Previously, this file was written at `node_modules/@angular/compiler-cli/ngcc`, or similar depending upon the bundling of the package.
But this causes problems for setups where `node_modules` package directories are expected to be read-only.
Now, the lock-file is written as `.ngcc_lock_file` into the top of the `node_modules`, which is an acceptable place to store transient files.

This change should help to unblock use of tools like pnpm and lerna, which can use symlinks to readonly package directories.

PR Close #44228
This commit is contained in:
Pete Bacon Darwin
2021-11-19 13:17:24 +00:00
committed by Jessica Janiuk
parent f7dd0f83f0
commit 24b635395f
5 changed files with 25 additions and 3 deletions
+1 -1
View File
@@ -209,7 +209,7 @@ ngcc --formats fesm2015
assertFailed "Expected 'ngcc --formats fesm2015' to fail (since '--formats' is deprecated)."
# Does it timeout if there is another ngcc process running
LOCKFILE=node_modules/@angular/compiler-cli/bundles/ngcc/__ngcc_lock_file__
LOCKFILE=node_modules/.ngcc_lock_file
touch $LOCKFILE
trap "[[ -f $LOCKFILE ]] && rm $LOCKFILE" EXIT
ngcc
@@ -19,8 +19,8 @@ export function getLockFilePath(fs: PathManipulation) {
// allows us to have a consistent position for the lock file to reside. We are unable to rely
// on `__dirname` (or equivalent) as this code is being bundled and different entry-points
// will have dedicated bundles where the lock file location would differ then.
const ngccEntryPointFile = requireFn.resolve('@angular/compiler-cli/ngcc');
return fs.resolve(ngccEntryPointFile, '../__ngcc_lock_file__');
const ngccEntryPointFile = requireFn.resolve('@angular/compiler-cli/package.json');
return fs.resolve(ngccEntryPointFile, '../../../.ngcc_lock_file');
}
export interface LockFile {
@@ -119,3 +119,18 @@ var __assign${suffix} = null;
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
}
/**
* Mock out the lockfile path resolution, which uses `require.resolve()`.
*/
export function mockRequireResolveForLockfile() {
const moduleConstructor: any = module.constructor;
const originalResolveFileName = moduleConstructor._resolveFilename;
spyOn<any>(moduleConstructor, '_resolveFilename').and.callFake(function(request: string) {
if (request === '@angular/compiler-cli/package.json') {
return '/node_modules/' + request;
} else {
return originalResolveFileName.apply(null, arguments as any);
}
});
}
@@ -22,6 +22,7 @@ import {EntryPointJsonProperty, EntryPointPackageJson, SUPPORTED_FORMAT_PROPERTI
import {EntryPointManifestFile} from '../../src/packages/entry_point_manifest';
import {Transformer} from '../../src/packages/transformer';
import {DirectPackageJsonUpdater, PackageJsonUpdater} from '../../src/writing/package_json_updater';
import {mockRequireResolveForLockfile} from '../helpers/utils';
import {compileIntoApf, compileIntoFlatEs2015Package, compileIntoFlatEs5Package, loadNgccIntegrationTestFiles} from './util';
@@ -46,6 +47,7 @@ runInEachFileSystem(() => {
_ = absoluteFrom;
fs = getFileSystem();
pkgJsonUpdater = new DirectPackageJsonUpdater(fs);
mockRequireResolveForLockfile();
initMockFileSystem(fs, testFiles);
// Force single-process execution in unit tests by mocking available CPUs to 1.
@@ -13,6 +13,7 @@ import {runInEachFileSystem} from '../../../../src/ngtsc/file_system/testing';
import {MockLogger} from '../../../../src/ngtsc/logging/testing';
import {getLockFilePath} from '../../../src/locking/lock_file';
import {LockFileWithChildProcess} from '../../../src/locking/lock_file_with_child_process';
import {mockRequireResolveForLockfile} from '../../helpers/utils';
runInEachFileSystem(() => {
describe('LockFileWithChildProcess', () => {
@@ -55,6 +56,10 @@ runInEachFileSystem(() => {
}
}
beforeEach(() => {
mockRequireResolveForLockfile();
});
describe('constructor', () => {
it('should create the unlocker process', () => {
const fs = getFileSystem();