From 24b635395f163e727effbf66da168dfbb29fc4a0 Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Fri, 19 Nov 2021 13:17:24 +0000 Subject: [PATCH] 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 --- integration/ngcc/test.sh | 2 +- .../compiler-cli/ngcc/src/locking/lock_file.ts | 4 ++-- packages/compiler-cli/ngcc/test/helpers/utils.ts | 15 +++++++++++++++ .../ngcc/test/integration/ngcc_spec.ts | 2 ++ .../lockfile_with_child_process/index_spec.ts | 5 +++++ 5 files changed, 25 insertions(+), 3 deletions(-) diff --git a/integration/ngcc/test.sh b/integration/ngcc/test.sh index ba04cb7ff8a..bc8e35dde06 100755 --- a/integration/ngcc/test.sh +++ b/integration/ngcc/test.sh @@ -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 diff --git a/packages/compiler-cli/ngcc/src/locking/lock_file.ts b/packages/compiler-cli/ngcc/src/locking/lock_file.ts index d627e0106a6..d16d5d43061 100644 --- a/packages/compiler-cli/ngcc/src/locking/lock_file.ts +++ b/packages/compiler-cli/ngcc/src/locking/lock_file.ts @@ -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 { diff --git a/packages/compiler-cli/ngcc/test/helpers/utils.ts b/packages/compiler-cli/ngcc/test/helpers/utils.ts index dc8634c984d..a776c024842 100644 --- a/packages/compiler-cli/ngcc/test/helpers/utils.ts +++ b/packages/compiler-cli/ngcc/test/helpers/utils.ts @@ -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(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); + } + }); +} diff --git a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts index 27e8d2db98b..9b5abf01c18 100644 --- a/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts +++ b/packages/compiler-cli/ngcc/test/integration/ngcc_spec.ts @@ -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. diff --git a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts b/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts index edff4dc0b7f..95b84b9a42f 100644 --- a/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts +++ b/packages/compiler-cli/ngcc/test/locking/lockfile_with_child_process/index_spec.ts @@ -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();