Skip to content

Commit 78b5b1a

Browse files
benkeenBen Keendmichon-msft
authored
[rush] Add safety check to Bridge Cache plugin write action (#5275)
* Add safety check to Bridge Cache plugin write action * Make safety check an optional flag; code review feedback * Code review feedback: check param type * Update rush-plugins/rush-bridge-cache-plugin/src/BridgeCachePlugin.ts Co-authored-by: David Michon <[email protected]> --------- Co-authored-by: Ben Keen <[email protected]> Co-authored-by: David Michon <[email protected]>
1 parent 47c6e3d commit 78b5b1a

File tree

4 files changed

+81
-11
lines changed

4 files changed

+81
-11
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@microsoft/rush",
5+
"comment": "Adds an optional safety check flag to the Bridge Cache plugin write action.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

rush-plugins/rush-bridge-cache-plugin/README.md

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Alternatively, the `--bridge-cache-action=read` parameter is useful for tasks su
88

99
## Here be dragons!
1010

11-
The `write` action for plugin assumes that the work for a particular task has already been completed and the build artifacts have been generated on disk. **If you run this command on a package where the command hasn't already been run and the build artifacts are missing or incorrect, you will cache invalid content**. Be careful and beware!
11+
The `write` action for this plugin assumes that the work for a particular task has already been completed and the build artifacts have been generated on disk. **If you run this command on a package where the command hasn't already been run and the build artifacts are missing or incorrect, you will cache invalid content**. Be careful and beware! See the optional `requireOutputFoldersParameterName` setting below to include a safety check to require all expected output folders for a command to actually be on disk.
1212

1313
The `read` action for this plugin makes no guarantee that the requested operations will have their outputs restored and is purely a best-effort.
1414

@@ -34,6 +34,14 @@ The `read` action for this plugin makes no guarantee that the requested operatio
3434
"description": "When specified for any associated command, bypass running the command itself, and cache whatever outputs exist in the output folders as-is. Beware! Only run when you know the build artifacts are in a valid state for the command."
3535
}
3636
]
37+
},
38+
39+
// optional
40+
{
41+
"associatedCommands": ["build", "test", "lint", "a11y", "typecheck"],
42+
"description": "Optional flag that can be used in combination with --bridge-cache-action=write. When used, this will only populate a cache entry when all defined output folders for a command are present on disk.",
43+
"parameterKind": "flag",
44+
"longName": "--require-output-folders",
3745
}
3846
```
3947

@@ -47,15 +55,20 @@ The `read` action for this plugin makes no guarantee that the requested operatio
4755
```
4856

4957
4. Create a configuration file for this plugin at this location: `common/config/rush-plugins/rush-bridge-cache-plugin.json` that defines the flag name you'll use to trigger the plugin:
58+
5059
```json
5160
{
52-
"actionParameterName": "--bridge-cache-action"
61+
"actionParameterName": "--bridge-cache-action",
62+
63+
// optional
64+
"requireOutputFoldersParameterName": "--require-output-folders"
5365
}
5466
```
5567

68+
5669
## Usage
5770

58-
You can now use the parameter to have any Rush phased command either *only* restore from the cache (without any local building), or *only* write the cache, assuming all current output files are correct.
71+
You can now use this plugin to have any Rush phased command either *only* restore from the cache (without any local building), or *only* write the cache, assuming all current output files are correct.
5972

6073
**Replay the cache entries for this command as best-effort, but don't execute any build processes**
6174
`rush build --to your-packageX --bridge-cache-action=read`
@@ -65,9 +78,6 @@ That will populate the cache for `your-packageX` and all of its dependencies.
6578
`rush build --to your-packageX --bridge-cache-action=write`
6679
That will populate the cache for `your-packageX` and all of its dependencies.
6780

68-
69-
## Performance
70-
71-
When running within a pipeline, you may want to populate the cache as quickly as possible so local Rush users will benefit from the cached entry sooner. So instead of waiting until the full build graph has been processed, running it after each individual task when it's been completed, e.g.
72-
73-
`rush lint --only your-packageY --set-cache-only`
81+
**Write whatever outputs are on disk for this command to the cache, but only if all output folders are present**
82+
`rush build --to your-packageX --bridge-cache-action=write --require-output-folders`
83+
That will populate the cache for `your-packageX` and all of its dependencies, skipping any that don't have all output folders present.

rush-plugins/rush-bridge-cache-plugin/src/BridgeCachePlugin.ts

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
import { Async } from '@rushstack/node-core-library';
4+
import { Async, FileSystem } from '@rushstack/node-core-library';
55
import { _OperationBuildCache as OperationBuildCache } from '@rushstack/rush-sdk';
66
import type {
77
ICreateOperationsContext,
@@ -25,14 +25,17 @@ type CacheAction = typeof CACHE_ACTION_READ | typeof CACHE_ACTION_WRITE;
2525

2626
export interface IBridgeCachePluginOptions {
2727
readonly actionParameterName: string;
28+
readonly requireOutputFoldersParameterName: string | undefined;
2829
}
2930

3031
export class BridgeCachePlugin implements IRushPlugin {
3132
public readonly pluginName: string = PLUGIN_NAME;
3233
private readonly _actionParameterName: string;
34+
private readonly _requireOutputFoldersParameterName: string | undefined;
3335

3436
public constructor(options: IBridgeCachePluginOptions) {
3537
this._actionParameterName = options.actionParameterName;
38+
this._requireOutputFoldersParameterName = options.requireOutputFoldersParameterName;
3639

3740
if (!this._actionParameterName) {
3841
throw new Error(
@@ -46,6 +49,7 @@ export class BridgeCachePlugin implements IRushPlugin {
4649
const logger: ILogger = session.getLogger(PLUGIN_NAME);
4750

4851
let cacheAction: CacheAction | undefined;
52+
let requireOutputFolders: boolean = false;
4953

5054
// cancel the actual operations. We don't want to run the command, just cache the output folders on disk
5155
command.hooks.createOperations.tap(
@@ -63,12 +67,13 @@ export class BridgeCachePlugin implements IRushPlugin {
6367
for (const operation of operations) {
6468
operation.enabled = false;
6569
}
70+
71+
requireOutputFolders = this._isRequireOutputFoldersFlagSet(context);
6672
}
6773

6874
return operations;
6975
}
7076
);
71-
7277
// populate the cache for each operation
7378
command.hooks.beforeExecuteOperations.tap(
7479
PLUGIN_NAME,
@@ -123,6 +128,27 @@ export class BridgeCachePlugin implements IRushPlugin {
123128
);
124129
}
125130
} else if (cacheAction === CACHE_ACTION_WRITE) {
131+
// if the require output folders flag has been passed, skip populating the cache if any of the expected output folders does not exist
132+
if (
133+
requireOutputFolders &&
134+
operation.settings?.outputFolderNames &&
135+
operation.settings?.outputFolderNames?.length > 0
136+
) {
137+
const projectFolder: string = operation.associatedProject?.projectFolder;
138+
const missingFolders: string[] = [];
139+
operation.settings.outputFolderNames.forEach((outputFolderName: string) => {
140+
if (!FileSystem.exists(`${projectFolder}/${outputFolderName}`)) {
141+
missingFolders.push(outputFolderName);
142+
}
143+
});
144+
if (missingFolders.length > 0) {
145+
terminal.writeWarningLine(
146+
`Operation "${operation.name}": The following output folders do not exist: "${missingFolders.join('", "')}". Skipping cache population.`
147+
);
148+
return;
149+
}
150+
}
151+
126152
const success: boolean = await projectBuildCache.trySetCacheEntryAsync(terminal);
127153
if (success) {
128154
++successCount;
@@ -186,4 +212,24 @@ export class BridgeCachePlugin implements IRushPlugin {
186212

187213
return undefined;
188214
}
215+
216+
private _isRequireOutputFoldersFlagSet(context: IExecuteOperationsContext): boolean {
217+
if (!this._requireOutputFoldersParameterName) {
218+
return false;
219+
}
220+
221+
const requireOutputFoldersParam: CommandLineParameter | undefined = context.customParameters.get(
222+
this._requireOutputFoldersParameterName
223+
);
224+
225+
if (!requireOutputFoldersParam) {
226+
return false;
227+
}
228+
229+
if (requireOutputFoldersParam.kind !== CommandLineParameterKind.Flag) {
230+
throw new Error(`The parameter "${this._requireOutputFoldersParameterName}" must be a flag.`);
231+
}
232+
233+
return requireOutputFoldersParam.value;
234+
}
189235
}

rush-plugins/rush-bridge-cache-plugin/src/schemas/bridge-cache-config.schema.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
"actionParameterName": {
1111
"type": "string",
1212
"description": "(Required) The name of the choice parameter used to trigger this plugin on your phased commands. It should accept two values, 'read' and 'write'."
13+
},
14+
"requireOutputFoldersParameterName": {
15+
"type": "string",
16+
"description": "(Optional) The name of the parameter used to specify whether the output folders must exist for the action in order to populate the cache."
1317
}
1418
}
1519
}

0 commit comments

Comments
 (0)