X Tutup
Skip to content

Commit a8feeb2

Browse files
authored
Merge pull request microsoft#1403 from kaidjohnson/kaidjohnson/incremental-commands
[rush] (feature) Add support for incremental custom commands
2 parents 2ee56e9 + 765c28f commit a8feeb2

File tree

16 files changed

+122
-58
lines changed

16 files changed

+122
-58
lines changed

apps/rush-lib/assets/rush-init/[dot]gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,4 @@ temp
6060

6161
# Rush files
6262
common/temp/**
63-
package-deps.json
63+
.rush/temp/**

apps/rush-lib/src/api/CommandLineJson.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface IBulkCommandJson extends IBaseCommandJson {
2323
enableParallelism: boolean;
2424
ignoreDependencyOrder?: boolean;
2525
ignoreMissingScript?: boolean;
26+
incremental?: boolean;
2627
allowWarningsInSuccessfulBuild?: boolean;
2728
}
2829

apps/rush-lib/src/api/RushConfigurationProject.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { RushConfiguration } from '../api/RushConfiguration';
1414
import { VersionPolicy, LockStepVersionPolicy } from './VersionPolicy';
1515
import { PackageJsonEditor } from './PackageJsonEditor';
16+
import { RushConstants } from '../logic/RushConstants';
1617

1718
/**
1819
* This represents the JSON data object for a project entry in the rush.json configuration file.
@@ -36,6 +37,7 @@ export class RushConfigurationProject {
3637
private _packageName: string;
3738
private _projectFolder: string;
3839
private _projectRelativeFolder: string;
40+
private _projectRushTempFolder: string;
3941
private _reviewCategory: string;
4042
private _packageJson: IPackageJson;
4143
private _packageJsonEditor: PackageJsonEditor;
@@ -50,9 +52,11 @@ export class RushConfigurationProject {
5052
private readonly _rushConfiguration: RushConfiguration;
5153

5254
/** @internal */
53-
constructor(projectJson: IRushConfigurationProjectJson,
54-
rushConfiguration: RushConfiguration,
55-
tempProjectName: string) {
55+
constructor(
56+
projectJson: IRushConfigurationProjectJson,
57+
rushConfiguration: RushConfiguration,
58+
tempProjectName: string
59+
) {
5660
this._rushConfiguration = rushConfiguration;
5761
this._packageName = projectJson.packageName;
5862
this._projectRelativeFolder = projectJson.projectFolder;
@@ -76,6 +80,12 @@ export class RushConfigurationProject {
7680
throw new Error(`Project folder not found: ${projectJson.projectFolder}`);
7781
}
7882

83+
this._projectRushTempFolder = path.join(
84+
this._projectFolder,
85+
RushConstants.projectRushFolderName,
86+
RushConstants.rushTempFolderName
87+
);
88+
7989
// Are we using a package review file?
8090
if (rushConfiguration.approvedPackagesPolicy.enabled) {
8191
// If so, then every project needs to have a reviewCategory that was defined
@@ -148,6 +158,15 @@ export class RushConfigurationProject {
148158
return this._projectRelativeFolder;
149159
}
150160

161+
/**
162+
* The project-specific Rush temp folder. This folder is used to store Rush-specific temporary files.
163+
*
164+
* Example: `C:\MyRepo\libraries\my-project\.rush\temp`
165+
*/
166+
public get projectRushTempFolder(): string {
167+
return this._projectRushTempFolder;
168+
}
169+
151170
/**
152171
* The review category name, or undefined if no category was assigned.
153172
* This name must be one of the valid choices listed in RushConfiguration.reviewCategories.

apps/rush-lib/src/cli/RushCommandLineParser.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -202,19 +202,20 @@ export class RushCommandLineParser extends CommandLineParser {
202202
summary: '(EXPERIMENTAL) Build all projects that haven\'t been built, or have changed since they were last '
203203
+ 'built.',
204204
documentation: 'This command is similar to "rush rebuild", except that "rush build" performs'
205-
+ ' an incremental build. In other words, it only builds projects whose source files have'
206-
+ ' changed since the last successful build. The analysis requires a Git working tree, and'
207-
+ ' only considers source files that are tracked by Git and whose path is under the project folder.'
208-
+ ' (For more details about this algorithm, see the documentation for the "package-deps-hash"'
209-
+ ' NPM package.) The incremental build state is tracked in a file "package-deps.json" which should'
210-
+ ' NOT be added to Git. The build command is tracked by the "arguments" field in this JSON file;'
211-
+ ' a full rebuild is forced whenever the command has changed (e.g. "--production" or not).',
205+
+ ' an incremental build. In other words, it only builds projects whose source files have changed'
206+
+ ' since the last successful build. The analysis requires a Git working tree, and only considers'
207+
+ ' source files that are tracked by Git and whose path is under the project folder. (For more details'
208+
+ ' about this algorithm, see the documentation for the "package-deps-hash" NPM package.) The incremental'
209+
+ ' build state is tracked in a per-project folder called ".rush/temp" which should NOT be added to Git. The'
210+
+ ' build command is tracked by the "arguments" field in the "package-deps_build.json" file contained'
211+
+ ' therein; a full rebuild is forced whenever the command has changed (e.g. "--production" or not).',
212212
parser: this,
213213
commandLineConfiguration: commandLineConfiguration,
214214

215215
enableParallelism: true,
216216
ignoreMissingScript: false,
217217
ignoreDependencyOrder: false,
218+
incremental: true,
218219
allowWarningsInSuccessfulBuild: false
219220
}));
220221
}
@@ -238,6 +239,7 @@ export class RushCommandLineParser extends CommandLineParser {
238239
enableParallelism: true,
239240
ignoreMissingScript: false,
240241
ignoreDependencyOrder: false,
242+
incremental: false,
241243
allowWarningsInSuccessfulBuild: false
242244
}));
243245
}
@@ -271,6 +273,7 @@ export class RushCommandLineParser extends CommandLineParser {
271273
enableParallelism: command.enableParallelism,
272274
ignoreMissingScript: command.ignoreMissingScript || false,
273275
ignoreDependencyOrder: command.ignoreDependencyOrder || false,
276+
incremental: command.incremental || false,
274277
allowWarningsInSuccessfulBuild: !!command.allowWarningsInSuccessfulBuild
275278
}));
276279
break;

apps/rush-lib/src/cli/scriptActions/BulkScriptAction.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { BaseScriptAction, IBaseScriptActionOptions } from './BaseScriptAction';
2323
import { FileSystem } from '@microsoft/node-core-library';
2424
import { TaskRunner } from '../../logic/taskRunner/TaskRunner';
2525
import { TaskCollection } from '../../logic/taskRunner/TaskCollection';
26+
import { Utilities } from '../../utilities/Utilities';
2627

2728
/**
2829
* Constructor parameters for BulkScriptAction.
@@ -31,6 +32,7 @@ export interface IBulkScriptActionOptions extends IBaseScriptActionOptions {
3132
enableParallelism: boolean;
3233
ignoreMissingScript: boolean;
3334
ignoreDependencyOrder: boolean;
35+
incremental: boolean;
3436
allowWarningsInSuccessfulBuild: boolean;
3537

3638
/**
@@ -51,6 +53,7 @@ export interface IBulkScriptActionOptions extends IBaseScriptActionOptions {
5153
export class BulkScriptAction extends BaseScriptAction {
5254
private _enableParallelism: boolean;
5355
private _ignoreMissingScript: boolean;
56+
private _isIncrementalBuildAllowed: boolean;
5457
private _commandToRun: string;
5558

5659
private _changedProjectsOnly: CommandLineFlagParameter;
@@ -67,6 +70,7 @@ export class BulkScriptAction extends BaseScriptAction {
6770
super(options);
6871
this._enableParallelism = options.enableParallelism;
6972
this._ignoreMissingScript = options.ignoreMissingScript;
73+
this._isIncrementalBuildAllowed = options.incremental;
7074
this._commandToRun = options.commandToRun || options.actionName;
7175
this._ignoreDependencyOrder = options.ignoreDependencyOrder;
7276
this._allowWarningsInSuccessfulBuild = options.allowWarningsInSuccessfulBuild;
@@ -95,7 +99,7 @@ export class BulkScriptAction extends BaseScriptAction {
9599
customParameter.appendToArgList(customParameterValues);
96100
}
97101

98-
const changedProjectsOnly: boolean = this.actionName === 'build' && this._changedProjectsOnly.value;
102+
const changedProjectsOnly: boolean = this._isIncrementalBuildAllowed && this._changedProjectsOnly.value;
99103

100104
const taskSelector: TaskSelector = new TaskSelector({
101105
rushConfiguration: this.rushConfiguration,
@@ -104,20 +108,24 @@ export class BulkScriptAction extends BaseScriptAction {
104108
commandToRun: this._commandToRun,
105109
customParameterValues,
106110
isQuietMode: isQuietMode,
107-
isIncrementalBuildAllowed: this.actionName === 'build',
111+
isIncrementalBuildAllowed: this._isIncrementalBuildAllowed,
108112
ignoreMissingScript: this._ignoreMissingScript,
109-
ignoreDependencyOrder: this._ignoreDependencyOrder
113+
ignoreDependencyOrder: this._ignoreDependencyOrder,
114+
packageDepsFilename: Utilities.getPackageDepsFilenameForCommand(this._commandToRun)
110115
});
111116

112117
// Register all tasks with the task collection
113118
const taskCollection: TaskCollection = taskSelector.registerTasks();
114119

115-
const taskRunner: TaskRunner = new TaskRunner(taskCollection.getOrderedTasks(), {
116-
quietMode: isQuietMode,
117-
parallelism: parallelism,
118-
changedProjectsOnly: changedProjectsOnly,
119-
allowWarningsInSuccessfulBuild: this._allowWarningsInSuccessfulBuild
120-
});
120+
const taskRunner: TaskRunner = new TaskRunner(
121+
taskCollection.getOrderedTasks(),
122+
{
123+
quietMode: isQuietMode,
124+
parallelism: parallelism,
125+
changedProjectsOnly: changedProjectsOnly,
126+
allowWarningsInSuccessfulBuild: this._allowWarningsInSuccessfulBuild
127+
}
128+
);
121129

122130
return taskRunner.execute().then(() => {
123131
stopwatch.stop();
@@ -179,7 +187,7 @@ export class BulkScriptAction extends BaseScriptAction {
179187
parameterShortName: '-v',
180188
description: 'Display the logs during the build, rather than just displaying the build status summary'
181189
});
182-
if (this.actionName === 'build') {
190+
if (this._isIncrementalBuildAllowed) {
183191
this._changedProjectsOnly = this.defineFlagParameter({
184192
parameterLongName: '--changed-projects-only',
185193
parameterShortName: '-o',

apps/rush-lib/src/cli/test/RushCommandLineParser.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,10 @@ function getCommandLineParserInstance(repoName: string, taskName: string): IPars
4343
// Point to the test repo folder
4444
const startPath: string = resolve(__dirname, repoName);
4545

46-
// The `build` task is hard-coded to be incremental. So delete the `package-deps.json` files in
46+
// The `build` task is hard-coded to be incremental. So delete the package-deps file folder in
4747
// the test repo to guarantee the test actually runs.
48-
FileSystem.deleteFile(resolve(__dirname, `${repoName}/a/package-deps.json`));
49-
FileSystem.deleteFile(resolve(__dirname, `${repoName}/b/package-deps.json`));
48+
FileSystem.deleteFolder(resolve(__dirname, `${repoName}/a/.rush/temp`));
49+
FileSystem.deleteFolder(resolve(__dirname, `${repoName}/b/.rush/temp`));
5050

5151
// Create a Rush CLI instance. This instance is heavy-weight and relies on setting process.exit
5252
// to exit and clear the Rush file lock. So running multiple `it` or `describe` test blocks over the same test
@@ -279,4 +279,4 @@ describe('RushCommandLineParser', () => {
279279
});
280280
});
281281
});
282-
});
282+
});

apps/rush-lib/src/cli/test/__snapshots__/CommandLineHelp.test.ts.snap

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,11 @@ files have changed since the last successful build. The analysis requires a
106106
Git working tree, and only considers source files that are tracked by Git and
107107
whose path is under the project folder. (For more details about this
108108
algorithm, see the documentation for the \\"package-deps-hash\\" NPM package.)
109-
The incremental build state is tracked in a file \\"package-deps.json\\" which
110-
should NOT be added to Git. The build command is tracked by the \\"arguments\\"
111-
field in this JSON file; a full rebuild is forced whenever the command has
112-
changed (e.g. \\"--production\\" or not).
109+
The incremental build state is tracked in a per-project folder called \\".
110+
rush/temp\\" which should NOT be added to Git. The build command is tracked by
111+
the \\"arguments\\" field in the \\"package-deps_build.json\\" file contained
112+
therein; a full rebuild is forced whenever the command has changed (e.g.
113+
\\"--production\\" or not).
113114
114115
Optional arguments:
115116
-h, --help Show this help message and exit.

apps/rush-lib/src/logic/PackageChangeAnalyzer.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
} from '@microsoft/package-deps-hash';
1111
import { Path } from '@microsoft/node-core-library';
1212

13-
import { RushConstants } from '../logic/RushConstants';
1413
import { RushConfiguration } from '../api/RushConfiguration';
1514
import { Git } from './Git';
1615

@@ -57,10 +56,7 @@ export class PackageChangeAnalyzer {
5756
try {
5857
if (this._isGitSupported) {
5958
// Load the package deps hash for the whole repository
60-
repoDeps = PackageChangeAnalyzer.getPackageDeps(
61-
this._rushConfiguration.rushJsonFolder,
62-
[RushConstants.packageDepsFilename]
63-
);
59+
repoDeps = PackageChangeAnalyzer.getPackageDeps(this._rushConfiguration.rushJsonFolder, []);
6460
} else {
6561
return projectHashDeps;
6662
}
@@ -92,10 +88,10 @@ export class PackageChangeAnalyzer {
9288
*
9389
* Temporarily revert below code in favor of replacing this solution with something more
9490
* flexible. Idea is essentially that we should have gulp-core-build (or other build tool)
95-
* create the package-deps.json. The build tool would default to using the 'simple'
91+
* create the package-deps_<command>.json. The build tool would default to using the 'simple'
9692
* algorithm (e.g. only files that are in a project folder are associated with the project), however it would
9793
* also provide a hook which would allow certain tasks to modify the package-deps-hash before being written.
98-
* At the end of the build, a we would create a package-deps.json file like so:
94+
* At the end of the build, a we would create a package-deps_<command>.json file like so:
9995
*
10096
* {
10197
* commandLine: ["--production"],
@@ -117,7 +113,7 @@ export class PackageChangeAnalyzer {
117113
* Notes:
118114
* * We need to store the command line arguments, which is currently done by rush instead of GCB
119115
* * We need to store the hash/text of the a file which describes the state of the node_modules folder
120-
* * The package-deps.json should be a complete list of dependencies, and it should be extremely cheap
116+
* * The package-deps_<command>.json should be a complete list of dependencies, and it should be extremely cheap
121117
* to validate/check the file (even if creating it is more computationally costly).
122118
*/
123119

@@ -133,11 +129,10 @@ export class PackageChangeAnalyzer {
133129
const variant: string | undefined = this._rushConfiguration.currentInstalledVariant;
134130

135131
// Add the shrinkwrap file to every project's dependencies
136-
137-
const shrinkwrapFile: string =
138-
path.relative(this._rushConfiguration.rushJsonFolder,
139-
this._rushConfiguration.getCommittedShrinkwrapFilename(variant))
140-
.replace(/\\/g, '/');
132+
const shrinkwrapFile: string = path.relative(
133+
this._rushConfiguration.rushJsonFolder,
134+
this._rushConfiguration.getCommittedShrinkwrapFilename(variant)
135+
).replace(/\\/g, '/');
141136

142137
for (const project of this._rushConfiguration.projects) {
143138
const shrinkwrapHash: string | undefined = noProjectHashes[shrinkwrapFile];

apps/rush-lib/src/logic/RushConstants.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ export namespace RushConstants {
4545
export const rushTempNpmScope: string = '@rush-temp';
4646

4747
/**
48-
* The folder name ("temp") under the common folder where temporary files will be stored.
48+
* The folder name ("temp") under the common folder, or under the .rush folder in each project's directory where
49+
* temporary files will be stored.
4950
* Example: `C:\MyRepo\common\temp`
5051
*/
5152
export const rushTempFolderName: string = 'temp';
@@ -114,10 +115,10 @@ export namespace RushConstants {
114115
export const commonVersionsFilename: string = 'common-versions.json';
115116

116117
/**
117-
* The name of the package-deps.json file, which is used by the "rush build"
118-
* command to determine if a particular project needs to be rebuilt.
118+
* The name of the per-project folder where project-specific Rush files are stored. For example,
119+
* the package-deps files, which are used by commands to determine if a particular project needs to be rebuilt.
119120
*/
120-
export const packageDepsFilename: string = 'package-deps.json';
121+
export const projectRushFolderName: string = '.rush';
121122

122123
/**
123124
* Custom command line configuration file, which is used by rush for implementing

apps/rush-lib/src/logic/TaskSelector.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export interface ITaskSelectorConstructor {
1919
isIncrementalBuildAllowed: boolean;
2020
ignoreMissingScript: boolean;
2121
ignoreDependencyOrder: boolean;
22+
packageDepsFilename: string;
2223
}
2324

2425
/**
@@ -188,7 +189,8 @@ export class TaskSelector {
188189
rushConfiguration: this._options.rushConfiguration,
189190
commandToRun: this._getScriptToRun(project),
190191
isIncrementalBuildAllowed: this._options.isIncrementalBuildAllowed,
191-
packageChangeAnalyzer: this._packageChangeAnalyzer
192+
packageChangeAnalyzer: this._packageChangeAnalyzer,
193+
packageDepsFilename: this._options.packageDepsFilename
192194
});
193195

194196
if (!this._taskCollection.hasTask(projectTask.name)) {

0 commit comments

Comments
 (0)
X Tutup