Skip to content

[rush] feat(cobuilds): allow orchestration without using the build cache #4871

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,24 @@ rm -rf common/temp/build-cache
5. Open command palette (Ctrl+Shift+P or Command+Shift+P) and select `Tasks: Run Task` and select `cobuild`.

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.

#### Case 5: Sharded cobuilds

Enable the `allowCobuildWithoutCache` experiment in `experiments.json`.

Navigate to the sandbox for sharded cobuilds,
```sh
cd sandbox/sharded-repo
```

Next, start up your Redis instance,
```sh
docker compose down && docker compose up -d
```

Then, open 2 terminals and run this in each (changing the RUSH_COBUILD_RUNNER_ID across the 2 terminals),
```sh
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
```

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.
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Rush features. More documentation is available on the Rush website: https://rushjs.io
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/rush/v5/experiments.schema.json",
"$schema": "../../../../../../../libraries/rush-lib/src/schemas/experiments.schema.json",

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

/**
* If true, perform a clean install after when running `rush install` or `rush update` if the
Expand All @@ -52,4 +52,6 @@
* If true, print the outputs of shell commands defined in event hooks to the console.
*/
// "printEventHooksOutputToConsole": true

"allowCobuildWithoutCache": true
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
{
"$schema": "../../../../../../../libraries/rush-lib/src/schemas/rush-project.schema.json",
"operationSettings": [
{
"operationName": "_phase:build",
"outputFolderNames": ["dist"],
"allowCobuildOrchestration": true,
"disableBuildCacheForOperation": true,
"sharding": {
"count": 75,
"shardOperationSettings": {
"weight": 10
}
"count": 75
}
},
{
"operationName": "_phase:build:shard",
"weight": 1
"weight": 10,
"allowCobuildOrchestration": true
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "Adds a new experiment 'allowCobuildWithoutCache' for cobuilds to allow uncacheable operations to benefit from cobuild orchestration without using the build cache.",
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
3 changes: 3 additions & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export class CobuildConfiguration {
readonly cobuildFeatureEnabled: boolean;
readonly cobuildLeafProjectLogOnlyAllowed: boolean;
readonly cobuildRunnerId: string;
readonly cobuildWithoutCacheAllowed: boolean;
// (undocumented)
createLockProviderAsync(terminal: ITerminal): Promise<void>;
// (undocumented)
Expand Down Expand Up @@ -469,6 +470,7 @@ export interface IExecutionResult {

// @beta
export interface IExperimentsJson {
allowCobuildWithoutCache?: boolean;
buildCacheWithAllowWarningsInSuccessfulBuild?: boolean;
buildSkipWithAllowWarningsInSuccessfulBuild?: boolean;
cleanInstallAfterNpmrcChanges?: boolean;
Expand Down Expand Up @@ -625,6 +627,7 @@ export interface IOperationRunnerContext {

// @alpha (undocumented)
export interface IOperationSettings {
allowCobuildWithoutCache?: boolean;
dependsOnAdditionalFiles?: string[];
dependsOnEnvVars?: string[];
disableBuildCacheForOperation?: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,5 +96,11 @@
* This ensures that important notices will be seen by anyone doing active development, since people often
* ignore normal discussion group messages or don't know to subscribe.
*/
/*[LINE "HYPOTHETICAL"]*/ "rushAlerts": true
/*[LINE "HYPOTHETICAL"]*/ "rushAlerts": true,


/**
* When using cobuilds, this experiment allows uncacheable operations to benefit from cobuild orchestration without using the build cache.
*/
/*[LINE "HYPOTHETICAL"]*/ "allowCobuildWithoutCache": true
}
10 changes: 9 additions & 1 deletion libraries/rush-lib/src/api/CobuildConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,18 +69,26 @@ export class CobuildConfiguration {
*/
public readonly cobuildLeafProjectLogOnlyAllowed: boolean;

/**
* If true, operations can opt into leveraging cobuilds without restoring from the build cache.
* Operations will need to us the allowCobuildWithoutCache flag to opt into this behavior per phase.
*/
public readonly cobuildWithoutCacheAllowed: boolean;

private _cobuildLockProvider: ICobuildLockProvider | undefined;
private readonly _cobuildLockProviderFactory: CobuildLockProviderFactory;
private readonly _cobuildJson: ICobuildJson;

private constructor(options: ICobuildConfigurationOptions) {
const { cobuildJson, cobuildLockProviderFactory } = options;
const { cobuildJson, cobuildLockProviderFactory, rushConfiguration } = options;

this.cobuildContextId = EnvironmentConfiguration.cobuildContextId;
this.cobuildFeatureEnabled = this.cobuildContextId ? cobuildJson.cobuildFeatureEnabled : false;
this.cobuildRunnerId = EnvironmentConfiguration.cobuildRunnerId || uuidv4();
this.cobuildLeafProjectLogOnlyAllowed =
EnvironmentConfiguration.cobuildLeafProjectLogOnlyAllowed ?? false;
this.cobuildWithoutCacheAllowed =
rushConfiguration.experimentsConfiguration.configuration.allowCobuildWithoutCache ?? false;

this._cobuildLockProviderFactory = cobuildLockProviderFactory;
this._cobuildJson = cobuildJson;
Expand Down
7 changes: 7 additions & 0 deletions libraries/rush-lib/src/api/ExperimentsConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ export interface IExperimentsJson {
* ignore normal discussion group messages or don't know to subscribe.
*/
rushAlerts?: boolean;

/**
* Allow cobuilds without using the build cache to store previous execution info. When setting up
* distributed builds, Rush will allow uncacheable projects to still leverage the cobuild feature.
* This is useful when you want to speed up operations that can't (or shouldn't) be cached.
*/
allowCobuildWithoutCache?: boolean;
}

const _EXPERIMENTS_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(schemaJson);
Expand Down
5 changes: 5 additions & 0 deletions libraries/rush-lib/src/api/RushProjectConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,11 @@ export interface IOperationSettings {
* determined by the -p flag.
*/
weight?: number;

/**
* If true, this operation can use cobuilds for orchestration without restoring build cache entries.
*/
allowCobuildWithoutCache?: boolean;
}

interface IOldRushProjectJson {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
? new DisjointSet()
: undefined;

for (const operation of recordByOperation.keys()) {
if (
operation.settings?.allowCobuildWithoutCache &&
!cobuildConfiguration?.cobuildWithoutCacheAllowed
) {
throw new Error(
`Operation ${operation.name} is not allowed to run without the cobuild orchestration experiment enabled. You must enable the "allowCobuildWithoutCache" experiment in experiments.json.`
);
}
if (
operation.settings?.allowCobuildWithoutCache &&
!operation.settings.disableBuildCacheForOperation
) {
throw new Error(
`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.`
);
}
}

await Async.forEachAsync(
recordByOperation.keys(),
async (operation: Operation) => {
Expand Down Expand Up @@ -258,7 +277,8 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
phase,
configHash,
terminal: buildCacheTerminal,
operationMetadataManager
operationMetadataManager,
operation: record.operation
});

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

if (record.operation.settings?.allowCobuildWithoutCache) {
// This should only be enabled if the experiment for cobuild orchestration is enabled.
return status;
}

const restoreFromCacheSuccess: boolean = await restoreCacheAsync(
cobuildLock.projectBuildCache,
cacheId
);

if (restoreFromCacheSuccess) {
if (cobuildCompletedState) {
return cobuildCompletedState.status;
}
return status;
}
} else if (!buildCacheContext.isCacheReadAttempted && buildCacheContext.isCacheReadAllowed) {
Expand Down Expand Up @@ -569,7 +591,8 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
phase,
configHash,
terminal,
operationMetadataManager
operationMetadataManager,
operation
}: {
buildCacheContext: IOperationBuildCacheContext;
buildCacheConfiguration: BuildCacheConfiguration | undefined;
Expand All @@ -578,10 +601,11 @@ export class CacheableOperationPlugin implements IPhasedCommandPlugin {
configHash: string;
terminal: ITerminal;
operationMetadataManager: OperationMetadataManager | undefined;
operation: Operation;
}): Promise<ProjectBuildCache | undefined> {
if (!buildCacheContext.projectBuildCache) {
const { cacheDisabledReason } = buildCacheContext;
if (cacheDisabledReason) {
if (cacheDisabledReason && !operation.settings?.allowCobuildWithoutCache) {
terminal.writeVerboseLine(cacheDisabledReason);
return;
}
Expand Down Expand Up @@ -863,7 +887,7 @@ export function clusterOperations(
for (const [operation, { cacheDisabledReason }] of operationBuildCacheMap) {
const { associatedProject: project, associatedPhase: phase } = operation;
if (project && phase) {
if (cacheDisabledReason) {
if (cacheDisabledReason && !operation.settings?.allowCobuildWithoutCache) {
/**
* Group the project build cache disabled with its consumers. This won't affect too much in
* a monorepo with high build cache coverage.
Expand Down
4 changes: 4 additions & 0 deletions libraries/rush-lib/src/schemas/experiments.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@
"description": "If true, when running in watch mode, Rush will check for phase scripts named `_phase:<name>:ipc` and run them instead of `_phase:<name>` if they exist. The created child process will be provided with an IPC channel and expected to persist across invocations.",
"type": "boolean"
},
"allowCobuildWithoutCache": {
"description": "When using cobuilds, this experiment allows uncacheable operations to benefit from cobuild orchestration without using the build cache.",
"type": "boolean"
},
"rushAlerts": {
"description": "(UNDER DEVELOPMENT) The Rush alerts feature provides a way to send announcements to engineers working in the monorepo, by printing directly in the user's shell window when they invoke Rush commands. This ensures that important notices will be seen by anyone doing active development, since people often ignore normal discussion group messages or don't know to subscribe.",
"type": "boolean"
Expand Down
4 changes: 4 additions & 0 deletions libraries/rush-lib/src/schemas/rush-project.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
"description": "The number of concurrency units that this operation should take up. The maximum concurrency units is determined by the -p flag.",
"type": "integer",
"minimum": 0
},
"allowCobuildWithoutCache": {
"type": "boolean",
"description": "If true, this operation will not need to use the build cache to leverage cobuilds"
}
}
}
Expand Down
Loading