Skip to content

Commit 50449d0

Browse files
[rush] feat(cobuilds): allow orchestration without using the build cache (#4871)
* feat(cobuilds): allow orchestration without using the build cache * update comment * rename allowCobuildOrchestration to allowCobuildWithoutCache * move to experiments.json * fix lint errors * Update build-tests/rush-redis-cobuild-plugin-integration-test/sandbox/sharded-repo/projects/e/config/rush-project.json * adding to rush-init and adjusting description --------- Co-authored-by: Aramis Sennyey <[email protected]>
1 parent 476c61e commit 50449d0

File tree

12 files changed

+111
-16
lines changed

12 files changed

+111
-16
lines changed

build-tests/rush-redis-cobuild-plugin-integration-test/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,3 +150,24 @@ rm -rf common/temp/build-cache
150150
5. Open command palette (Ctrl+Shift+P or Command+Shift+P) and select `Tasks: Run Task` and select `cobuild`.
151151

152152
Expected behavior: Cobuild feature is enabled, cobuild related logs out in both terminals. These two cobuild commands fail because of the failing build of project "A". And, one of them restored the failing build cache created by the other one.
153+
154+
#### Case 5: Sharded cobuilds
155+
156+
Enable the `allowCobuildWithoutCache` experiment in `experiments.json`.
157+
158+
Navigate to the sandbox for sharded cobuilds,
159+
```sh
160+
cd sandbox/sharded-repo
161+
```
162+
163+
Next, start up your Redis instance,
164+
```sh
165+
docker compose down && docker compose up -d
166+
```
167+
168+
Then, open 2 terminals and run this in each (changing the RUSH_COBUILD_RUNNER_ID across the 2 terminals),
169+
```sh
170+
rm -rf common/temp/build-cache && RUSH_COBUILD_CONTEXT_ID=foo REDIS_PASS=redis123 RUSH_COBUILD_RUNNER_ID=runner1 node ../../lib/runRush.js cobuild -p 10 --timeline
171+
```
172+
173+
If all goes well, you should see a bunch of operation with `- shard xx/yy`. Operations `h (build)` and `e (build)` are both sharded heavily and should be cobuild compatible. To validate changes you're making, ensure that the timeline view for all of the shards of those 2 operations are cobuilt across both terminals. If they're not, something is wrong with your update.

build-tests/rush-redis-cobuild-plugin-integration-test/sandbox/sharded-repo/common/config/rush/experiments.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Rush features. More documentation is available on the Rush website: https://rushjs.io
44
*/
55
{
6-
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/experiments.schema.json",
6+
"$schema": "../../../../../../../libraries/rush-lib/src/schemas/experiments.schema.json",
77

88
/**
99
* By default, 'rush install' passes --no-prefer-frozen-lockfile to 'pnpm install'.
@@ -40,7 +40,7 @@
4040
* If true, the phased commands feature is enabled. To use this feature, create a "phased" command
4141
* in common/config/rush/command-line.json.
4242
*/
43-
"phasedCommands": true
43+
"phasedCommands": true,
4444

4545
/**
4646
* If true, perform a clean install after when running `rush install` or `rush update` if the
@@ -52,4 +52,6 @@
5252
* If true, print the outputs of shell commands defined in event hooks to the console.
5353
*/
5454
// "printEventHooksOutputToConsole": true
55+
56+
"allowCobuildWithoutCache": true
5557
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
{
2+
"$schema": "../../../../../../../libraries/rush-lib/src/schemas/rush-project.schema.json",
23
"operationSettings": [
34
{
45
"operationName": "_phase:build",
56
"outputFolderNames": ["dist"],
7+
"allowCobuildOrchestration": true,
8+
"disableBuildCacheForOperation": true,
69
"sharding": {
7-
"count": 75,
8-
"shardOperationSettings": {
9-
"weight": 10
10-
}
10+
"count": 75
1111
}
1212
},
1313
{
1414
"operationName": "_phase:build:shard",
15-
"weight": 1
15+
"weight": 10,
16+
"allowCobuildOrchestration": true
1617
}
1718
]
1819
}
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 a new experiment 'allowCobuildWithoutCache' for cobuilds to allow uncacheable operations to benefit from cobuild orchestration without using the build cache.",
6+
"type": "none"
7+
}
8+
],
9+
"packageName": "@microsoft/rush"
10+
}

common/reviews/api/rush-lib.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export class CobuildConfiguration {
105105
readonly cobuildFeatureEnabled: boolean;
106106
readonly cobuildLeafProjectLogOnlyAllowed: boolean;
107107
readonly cobuildRunnerId: string;
108+
readonly cobuildWithoutCacheAllowed: boolean;
108109
// (undocumented)
109110
createLockProviderAsync(terminal: ITerminal): Promise<void>;
110111
// (undocumented)
@@ -469,6 +470,7 @@ export interface IExecutionResult {
469470

470471
// @beta
471472
export interface IExperimentsJson {
473+
allowCobuildWithoutCache?: boolean;
472474
buildCacheWithAllowWarningsInSuccessfulBuild?: boolean;
473475
buildSkipWithAllowWarningsInSuccessfulBuild?: boolean;
474476
cleanInstallAfterNpmrcChanges?: boolean;
@@ -625,6 +627,7 @@ export interface IOperationRunnerContext {
625627

626628
// @alpha (undocumented)
627629
export interface IOperationSettings {
630+
allowCobuildWithoutCache?: boolean;
628631
dependsOnAdditionalFiles?: string[];
629632
dependsOnEnvVars?: string[];
630633
disableBuildCacheForOperation?: boolean;

libraries/rush-lib/assets/rush-init/common/config/rush/experiments.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,11 @@
9696
* This ensures that important notices will be seen by anyone doing active development, since people often
9797
* ignore normal discussion group messages or don't know to subscribe.
9898
*/
99-
/*[LINE "HYPOTHETICAL"]*/ "rushAlerts": true
99+
/*[LINE "HYPOTHETICAL"]*/ "rushAlerts": true,
100+
101+
102+
/**
103+
* When using cobuilds, this experiment allows uncacheable operations to benefit from cobuild orchestration without using the build cache.
104+
*/
105+
/*[LINE "HYPOTHETICAL"]*/ "allowCobuildWithoutCache": true
100106
}

libraries/rush-lib/src/api/CobuildConfiguration.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,26 @@ export class CobuildConfiguration {
6969
*/
7070
public readonly cobuildLeafProjectLogOnlyAllowed: boolean;
7171

72+
/**
73+
* If true, operations can opt into leveraging cobuilds without restoring from the build cache.
74+
* Operations will need to us the allowCobuildWithoutCache flag to opt into this behavior per phase.
75+
*/
76+
public readonly cobuildWithoutCacheAllowed: boolean;
77+
7278
private _cobuildLockProvider: ICobuildLockProvider | undefined;
7379
private readonly _cobuildLockProviderFactory: CobuildLockProviderFactory;
7480
private readonly _cobuildJson: ICobuildJson;
7581

7682
private constructor(options: ICobuildConfigurationOptions) {
77-
const { cobuildJson, cobuildLockProviderFactory } = options;
83+
const { cobuildJson, cobuildLockProviderFactory, rushConfiguration } = options;
7884

7985
this.cobuildContextId = EnvironmentConfiguration.cobuildContextId;
8086
this.cobuildFeatureEnabled = this.cobuildContextId ? cobuildJson.cobuildFeatureEnabled : false;
8187
this.cobuildRunnerId = EnvironmentConfiguration.cobuildRunnerId || uuidv4();
8288
this.cobuildLeafProjectLogOnlyAllowed =
8389
EnvironmentConfiguration.cobuildLeafProjectLogOnlyAllowed ?? false;
90+
this.cobuildWithoutCacheAllowed =
91+
rushConfiguration.experimentsConfiguration.configuration.allowCobuildWithoutCache ?? false;
8492

8593
this._cobuildLockProviderFactory = cobuildLockProviderFactory;
8694
this._cobuildJson = cobuildJson;

libraries/rush-lib/src/api/ExperimentsConfiguration.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,13 @@ export interface IExperimentsJson {
106106
* ignore normal discussion group messages or don't know to subscribe.
107107
*/
108108
rushAlerts?: boolean;
109+
110+
/**
111+
* Allow cobuilds without using the build cache to store previous execution info. When setting up
112+
* distributed builds, Rush will allow uncacheable projects to still leverage the cobuild feature.
113+
* This is useful when you want to speed up operations that can't (or shouldn't) be cached.
114+
*/
115+
allowCobuildWithoutCache?: boolean;
109116
}
110117

111118
const _EXPERIMENTS_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);

libraries/rush-lib/src/api/RushProjectConfiguration.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ export interface IOperationSettings {
134134
* determined by the -p flag.
135135
*/
136136
weight?: number;
137+
138+
/**
139+
* If true, this operation can use cobuilds for orchestration without restoring build cache entries.
140+
*/
141+
allowCobuildWithoutCache?: boolean;
137142
}
138143

139144
interface IOldRushProjectJson {

libraries/rush-lib/src/logic/operations/CacheableOperationPlugin.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
9999
? new DisjointSet()
100100
: undefined;
101101

102+
for (const operation of recordByOperation.keys()) {
103+
if (
104+
operation.settings?.allowCobuildWithoutCache &&
105+
!cobuildConfiguration?.cobuildWithoutCacheAllowed
106+
) {
107+
throw new Error(
108+
`Operation ${operation.name} is not allowed to run without the cobuild orchestration experiment enabled. You must enable the "allowCobuildWithoutCache" experiment in experiments.json.`
109+
);
110+
}
111+
if (
112+
operation.settings?.allowCobuildWithoutCache &&
113+
!operation.settings.disableBuildCacheForOperation
114+
) {
115+
throw new Error(
116+
`Operation ${operation.name} must have disableBuildCacheForOperation set to true when using the cobuild orchestration experiment. This is to prevent implicit cache dependencies for this operation.`
117+
);
118+
}
119+
}
120+
102121
await Async.forEachAsync(
103122
recordByOperation.keys(),
104123
async (operation: Operation) => {
@@ -258,7 +277,8 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
258277
phase,
259278
configHash,
260279
terminal: buildCacheTerminal,
261-
operationMetadataManager
280+
operationMetadataManager,
281+
operation: record.operation
262282
});
263283

264284
// Try to acquire the cobuild lock
@@ -359,15 +379,17 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
359379
if (cobuildCompletedState) {
360380
const { status, cacheId } = cobuildCompletedState;
361381

382+
if (record.operation.settings?.allowCobuildWithoutCache) {
383+
// This should only be enabled if the experiment for cobuild orchestration is enabled.
384+
return status;
385+
}
386+
362387
const restoreFromCacheSuccess: boolean = await restoreCacheAsync(
363388
cobuildLock.projectBuildCache,
364389
cacheId
365390
);
366391

367392
if (restoreFromCacheSuccess) {
368-
if (cobuildCompletedState) {
369-
return cobuildCompletedState.status;
370-
}
371393
return status;
372394
}
373395
} else if (!buildCacheContext.isCacheReadAttempted && buildCacheContext.isCacheReadAllowed) {
@@ -569,7 +591,8 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
569591
phase,
570592
configHash,
571593
terminal,
572-
operationMetadataManager
594+
operationMetadataManager,
595+
operation
573596
}: {
574597
buildCacheContext: IOperationBuildCacheContext;
575598
buildCacheConfiguration: BuildCacheConfiguration | undefined;
@@ -578,10 +601,11 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
578601
configHash: string;
579602
terminal: ITerminal;
580603
operationMetadataManager: OperationMetadataManager | undefined;
604+
operation: Operation;
581605
}): Promise<ProjectBuildCache | undefined> {
582606
if (!buildCacheContext.projectBuildCache) {
583607
const { cacheDisabledReason } = buildCacheContext;
584-
if (cacheDisabledReason) {
608+
if (cacheDisabledReason && !operation.settings?.allowCobuildWithoutCache) {
585609
terminal.writeVerboseLine(cacheDisabledReason);
586610
return;
587611
}
@@ -863,7 +887,7 @@ export function clusterOperations(
863887
for (const [operation, { cacheDisabledReason }] of operationBuildCacheMap) {
864888
const { associatedProject: project, associatedPhase: phase } = operation;
865889
if (project && phase) {
866-
if (cacheDisabledReason) {
890+
if (cacheDisabledReason && !operation.settings?.allowCobuildWithoutCache) {
867891
/**
868892
* Group the project build cache disabled with its consumers. This won't affect too much in
869893
* a monorepo with high build cache coverage.

0 commit comments

Comments
 (0)