Files
angular/packages/compiler-cli/ngcc/src/execution/cluster/package_json_updater.ts
George Kalpakas a10d2a8dc4 fix(ngcc): update package.json deterministically (#34870)
Ngcc adds properties to the `package.json` files of the entry-points it
processes to mark them as processed for a format and point to the
created Ivy entry-points (in case of `--create-ivy-entry-points`). When
running ngcc in parallel mode (which is the default for the standalone
ngcc command), multiple formats can be processed simultaneously for the
same entry-point and the order of completion is not deterministic.

Previously, ngcc would append new properties at the end of the target
object in `package.json` as soon as the format processing was completed.
As a result, the order of properties in the resulting `package.json`
(when processing multiple formats for an entry-point in parallel) was
not deterministic. For tools that use file hashes for caching purposes
(such as Bazel), this lead to a high probability of cache misses.

This commit fixes the problem by ensuring that the position of
properties added to `package.json` files is deterministic and
independent of the order in which each format is processed.

Jira issue: [FW-1801](https://angular-team.atlassian.net/browse/FW-1801)

Fixes #34635

PR Close #34870
2020-01-23 10:16:36 -08:00

59 lines
1.9 KiB
TypeScript

/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/// <reference types="node" />
import * as cluster from 'cluster';
import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system';
import {JsonObject} from '../../packages/entry_point';
import {PackageJsonChange, PackageJsonUpdate, PackageJsonUpdater, applyChange} from '../../writing/package_json_updater';
import {sendMessageToMaster} from './utils';
/**
* A `PackageJsonUpdater` that can safely handle update operations on multiple processes.
*/
export class ClusterPackageJsonUpdater implements PackageJsonUpdater {
constructor(private delegate: PackageJsonUpdater) {}
createUpdate(): PackageJsonUpdate {
return new PackageJsonUpdate((...args) => this.writeChanges(...args));
}
writeChanges(
changes: PackageJsonChange[], packageJsonPath: AbsoluteFsPath,
preExistingParsedJson?: JsonObject): void {
if (cluster.isMaster) {
// This is the master process:
// Actually apply the changes to the file on disk.
return this.delegate.writeChanges(changes, packageJsonPath, preExistingParsedJson);
}
// This is a worker process:
// Apply the changes in-memory (if necessary) and send a message to the master process.
if (preExistingParsedJson) {
for (const [propPath, value] of changes) {
if (propPath.length === 0) {
throw new Error(`Missing property path for writing value to '${packageJsonPath}'.`);
}
// No need to take property positioning into account for in-memory representations.
applyChange(preExistingParsedJson, propPath, value, 'unimportant');
}
}
sendMessageToMaster({
type: 'update-package-json',
packageJsonPath,
changes,
});
}
}