Skip to content
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

[rush] Exposing executionRecords in PhasedScriptsAction allows users to observe the execution process #4010

Merged
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@microsoft/rush",
"comment": "add hooks to PhasedCommandHooks to give user the operation records",
iclanton marked this conversation as resolved.
Show resolved Hide resolved
"type": "none"
}
],
"packageName": "@microsoft/rush"
}
2 changes: 2 additions & 0 deletions common/reviews/api/rush-lib.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -744,7 +744,9 @@ export abstract class PackageManagerOptionsConfigurationBase implements IPackage
// @alpha
export class PhasedCommandHooks {
readonly afterExecuteOperations: AsyncSeriesHook<[IExecutionResult, ICreateOperationsContext]>;
readonly beforeExecuteOperations: AsyncSeriesHook<[Map<Operation, IOperationExecutionResult>]>;
readonly createOperations: AsyncSeriesWaterfallHook<[Set<Operation>, ICreateOperationsContext]>;
readonly onOperationStatusChanged: SyncHook<[IOperationExecutionResult]>;
readonly waitingForChanges: SyncHook<void>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { BuildCacheConfiguration } from '../../api/BuildCacheConfiguration';
import { SelectionParameterSet } from '../parsing/SelectionParameterSet';
import type { IPhase, IPhasedCommandConfig } from '../../api/CommandLineConfiguration';
import { Operation } from '../../logic/operations/Operation';
import { OperationExecutionRecord } from '../../logic/operations/OperationExecutionRecord';
import { PhasedOperationPlugin } from '../../logic/operations/PhasedOperationPlugin';
import { ShellOperationRunnerPlugin } from '../../logic/operations/ShellOperationRunnerPlugin';
import { Event } from '../../api/EventHooks';
Expand Down Expand Up @@ -340,7 +341,13 @@ export class PhasedScriptAction extends BaseScriptAction<IPhasedCommandConfig> {
quietMode: isQuietMode,
debugMode: this.parser.isDebug,
parallelism,
changedProjectsOnly
changedProjectsOnly,
beforeExecuteOperations: async (records: Map<Operation, OperationExecutionRecord>) => {
await this.hooks.beforeExecuteOperations.promise(records);
},
onOperationStatusChanged: (record: OperationExecutionRecord) => {
this.hooks.onOperationStatusChanged.call(record);
}
};

const internalOptions: IRunPhasesOptions = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export interface IOperationExecutionManagerOptions {
parallelism: number;
changedProjectsOnly: boolean;
destination?: TerminalWritable;

onOperationStatusChanged?: (record: OperationExecutionRecord) => void;
beforeExecuteOperations?: (records: Map<Operation, OperationExecutionRecord>) => Promise<void>;
}

/**
Expand All @@ -44,20 +47,35 @@ export class OperationExecutionManager {

private readonly _terminal: CollatedTerminal;

private readonly _onOperationStatusChanged?: (record: OperationExecutionRecord) => void;
private readonly _beforeExecuteOperations?: (
records: Map<Operation, OperationExecutionRecord>
) => Promise<void>;

// Variables for current status
private _hasAnyFailures: boolean;
private _hasAnyNonAllowedWarnings: boolean;
private _completedOperations: number;

public constructor(operations: Set<Operation>, options: IOperationExecutionManagerOptions) {
const { quietMode, debugMode, parallelism, changedProjectsOnly } = options;
const {
quietMode,
debugMode,
parallelism,
changedProjectsOnly,
onOperationStatusChanged,
beforeExecuteOperations
} = options;
this._completedOperations = 0;
this._quietMode = quietMode;
this._hasAnyFailures = false;
this._hasAnyNonAllowedWarnings = false;
this._changedProjectsOnly = changedProjectsOnly;
this._parallelism = parallelism;

this._beforeExecuteOperations = beforeExecuteOperations;
this._onOperationStatusChanged = onOperationStatusChanged;

// TERMINAL PIPELINE:
//
// streamCollator --> colorsNewlinesTransform --> StdioWritable
Expand All @@ -77,6 +95,7 @@ export class OperationExecutionManager {
// Convert the developer graph to the mutable execution graph
const executionRecordContext: IOperationExecutionRecordContext = {
streamCollator: this._streamCollator,
onOperationStatusChanged,
debugMode,
quietMode
};
Expand Down Expand Up @@ -183,6 +202,8 @@ export class OperationExecutionManager {
prioritySort
);

await this._beforeExecuteOperations?.(this._executionRecords);

// This function is a callback because it may write to the collatedWriter before
// operation.executeAsync returns (and cleans up the writer)
const onOperationComplete: (record: OperationExecutionRecord) => void = (
Expand Down Expand Up @@ -250,6 +271,7 @@ export class OperationExecutionManager {
terminal.writeStdoutLine(`"${blockedRecord.name}" is blocked by "${name}".`);
}
blockedRecord.status = OperationStatus.Blocked;
this._onOperationStatusChanged?.(blockedRecord);

for (const dependent of blockedRecord.consumers) {
blockedQueue.add(dependent);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { OperationMetadataManager } from './OperationMetadataManager';

export interface IOperationExecutionRecordContext {
streamCollator: StreamCollator;
onOperationStatusChanged?: (record: OperationExecutionRecord) => void;

debugMode: boolean;
quietMode: boolean;
Expand Down Expand Up @@ -135,6 +136,7 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
public async executeAsync(onResult: (record: OperationExecutionRecord) => void): Promise<void> {
this.status = OperationStatus.Executing;
this.stopwatch.start();
this._context.onOperationStatusChanged?.(this);

try {
this.status = await this.runner.executeAsync(this);
Expand All @@ -149,6 +151,7 @@ export class OperationExecutionRecord implements IOperationRunnerContext {
this._collatedWriter?.close();
this.stdioSummarizer.close();
this.stopwatch.stop();
this._context.onOperationStatusChanged?.(this);
}
}
}
15 changes: 14 additions & 1 deletion libraries/rush-lib/src/pluginFramework/PhasedCommandHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { RushConfigurationProject } from '../api/RushConfigurationProject';

import type { Operation } from '../logic/operations/Operation';
import type { ProjectChangeAnalyzer } from '../logic/ProjectChangeAnalyzer';
import { IExecutionResult } from '../logic/operations/IOperationExecutionResult';
import { IExecutionResult, IOperationExecutionResult } from '../logic/operations/IOperationExecutionResult';

/**
* A plugin that interacts with a phased commands.
Expand Down Expand Up @@ -87,6 +87,19 @@ export class PhasedCommandHooks {
public readonly createOperations: AsyncSeriesWaterfallHook<[Set<Operation>, ICreateOperationsContext]> =
new AsyncSeriesWaterfallHook(['operations', 'context'], 'createOperations');

/**
* Hook invoked before operation start
* Hook is series for stable output.
*/
public readonly beforeExecuteOperations: AsyncSeriesHook<[Map<Operation, IOperationExecutionResult>]> =
new AsyncSeriesHook(['records']);

/**
* Hook invoked when operation statue changed
iclanton marked this conversation as resolved.
Show resolved Hide resolved
* Hook is series for stable output.
*/
public readonly onOperationStatusChanged: SyncHook<[IOperationExecutionResult]> = new SyncHook(['record']);

/**
* Hook invoked after executing a set of operations.
* Use the context to distinguish between the initial run and phased runs.
Expand Down