Skip to content

Commit d06fb82

Browse files
authored
Add option --stopBuildOnErrors to tsbuild to get previous behavior of not building downstream projects if upstream has errors (#59433)
1 parent f850298 commit d06fb82

13 files changed

+3702
-9
lines changed

src/compiler/commandLineParser.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,6 +1696,13 @@ export const optionsForBuild: CommandLineOption[] = [
16961696
type: "boolean",
16971697
defaultValueDescription: false,
16981698
},
1699+
{
1700+
name: "stopBuildOnErrors",
1701+
category: Diagnostics.Command_line_Options,
1702+
description: Diagnostics.Skip_building_downstream_projects_on_error_in_upstream_project,
1703+
type: "boolean",
1704+
defaultValueDescription: false,
1705+
},
16991706
];
17001707

17011708
/** @internal */

src/compiler/diagnosticMessages.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5716,6 +5716,14 @@
57165716
"category": "Message",
57175717
"code": 6361
57185718
},
5719+
"Skipping build of project '{0}' because its dependency '{1}' has errors": {
5720+
"category": "Message",
5721+
"code": 6362
5722+
},
5723+
"Project '{0}' can't be built because its dependency '{1}' has errors": {
5724+
"category": "Message",
5725+
"code": 6363
5726+
},
57195727
"Build one or more projects and their dependencies, if out of date": {
57205728
"category": "Message",
57215729
"code": 6364
@@ -5760,6 +5768,14 @@
57605768
"category": "Message",
57615769
"code": 6381
57625770
},
5771+
"Skipping build of project '{0}' because its dependency '{1}' was not built": {
5772+
"category": "Message",
5773+
"code": 6382
5774+
},
5775+
"Project '{0}' can't be built because its dependency '{1}' was not built": {
5776+
"category": "Message",
5777+
"code": 6383
5778+
},
57635779
"Have recompiles in '--incremental' and '--watch' assume that changes within a file will only affect files directly depending on it.": {
57645780
"category": "Message",
57655781
"code": 6384
@@ -6095,6 +6111,10 @@
60956111
"category": "Message",
60966112
"code": 6639
60976113
},
6114+
"Skip building downstream projects on error in upstream project.": {
6115+
"category": "Message",
6116+
"code": 6640
6117+
},
60986118
"Specify a list of glob patterns that match files to be included in compilation.": {
60996119
"category": "Message",
61006120
"code": 6641

src/compiler/tsbuild.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export enum UpToDateStatusType {
2525
OutOfDateOptions,
2626
OutOfDateRoots,
2727
UpstreamOutOfDate,
28+
UpstreamBlocked,
2829
ComputingUpstream,
2930
TsVersionOutputOfDate,
3031
UpToDateWithInputFileText,
@@ -47,6 +48,7 @@ export type UpToDateStatus =
4748
| Status.OutOfDateBuildInfo
4849
| Status.OutOfDateRoots
4950
| Status.UpstreamOutOfDate
51+
| Status.UpstreamBlocked
5052
| Status.ComputingUpstream
5153
| Status.TsVersionOutOfDate
5254
| Status.ContainerOnly
@@ -135,6 +137,15 @@ export namespace Status {
135137
upstreamProjectName: string;
136138
}
137139

140+
/**
141+
* This project depends an upstream project with build errors
142+
*/
143+
export interface UpstreamBlocked {
144+
type: UpToDateStatusType.UpstreamBlocked;
145+
upstreamProjectName: string;
146+
upstreamProjectBlocked: boolean;
147+
}
148+
138149
/**
139150
* Computing status of upstream projects referenced
140151
*/

src/compiler/tsbuildPublic.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export interface BuildOptions {
135135
dry?: boolean;
136136
force?: boolean;
137137
verbose?: boolean;
138+
stopBuildOnErrors?: boolean;
138139

139140
/** @internal */ clean?: boolean;
140141
/** @internal */ watch?: boolean;
@@ -1254,6 +1255,23 @@ function getNextInvalidatedProjectCreateInfo<T extends BuilderProgram>(
12541255
}
12551256
}
12561257

1258+
if (status.type === UpToDateStatusType.UpstreamBlocked) {
1259+
verboseReportProjectStatus(state, project, status);
1260+
reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config));
1261+
projectPendingBuild.delete(projectPath);
1262+
if (options.verbose) {
1263+
reportStatus(
1264+
state,
1265+
status.upstreamProjectBlocked ?
1266+
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_was_not_built :
1267+
Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors,
1268+
project,
1269+
status.upstreamProjectName,
1270+
);
1271+
}
1272+
continue;
1273+
}
1274+
12571275
if (status.type === UpToDateStatusType.ContainerOnly) {
12581276
verboseReportProjectStatus(state, project, status);
12591277
reportAndStoreErrors(state, projectPath, getConfigFileParsingDiagnostics(config));
@@ -1455,6 +1473,20 @@ function getUpToDateStatusWorker<T extends BuilderProgram>(state: SolutionBuilde
14551473
continue;
14561474
}
14571475

1476+
// An upstream project is blocked
1477+
if (
1478+
state.options.stopBuildOnErrors && (
1479+
refStatus.type === UpToDateStatusType.Unbuildable ||
1480+
refStatus.type === UpToDateStatusType.UpstreamBlocked
1481+
)
1482+
) {
1483+
return {
1484+
type: UpToDateStatusType.UpstreamBlocked,
1485+
upstreamProjectName: ref.path,
1486+
upstreamProjectBlocked: refStatus.type === UpToDateStatusType.UpstreamBlocked,
1487+
};
1488+
}
1489+
14581490
if (!force) (referenceStatuses ||= []).push({ ref, refStatus, resolvedRefPath, resolvedConfig });
14591491
}
14601492
}
@@ -1848,6 +1880,8 @@ function queueReferencingProjects<T extends BuilderProgram>(
18481880
buildOrder: readonly ResolvedConfigFileName[],
18491881
buildResult: BuildResultFlags,
18501882
) {
1883+
// Queue only if there are no errors
1884+
if (state.options.stopBuildOnErrors && (buildResult & BuildResultFlags.AnyErrors)) return;
18511885
// Only composite projects can be referenced by other projects
18521886
if (!config.options.composite) return;
18531887
// Always use build order to queue projects
@@ -1883,6 +1917,12 @@ function queueReferencingProjects<T extends BuilderProgram>(
18831917
});
18841918
}
18851919
break;
1920+
1921+
case UpToDateStatusType.UpstreamBlocked:
1922+
if (toResolvedConfigFilePath(state, resolveProjectName(state, status.upstreamProjectName)) === projectPath) {
1923+
clearProjectStatus(state, nextProjectPath);
1924+
}
1925+
break;
18861926
}
18871927
}
18881928
addProjToQueue(state, nextProjectPath, ProgramUpdateLevel.Update);
@@ -2386,6 +2426,15 @@ function reportUpToDateStatus<T extends BuilderProgram>(state: SolutionBuilderSt
23862426
relName(state, configFileName),
23872427
relName(state, status.upstreamProjectName),
23882428
);
2429+
case UpToDateStatusType.UpstreamBlocked:
2430+
return reportStatus(
2431+
state,
2432+
status.upstreamProjectBlocked ?
2433+
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_was_not_built :
2434+
Diagnostics.Project_0_can_t_be_built_because_its_dependency_1_has_errors,
2435+
relName(state, configFileName),
2436+
relName(state, status.upstreamProjectName),
2437+
);
23892438
case UpToDateStatusType.Unbuildable:
23902439
return reportStatus(
23912440
state,

src/testRunner/unittests/helpers/sampleProjectReferences.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export function getFsContentsForSampleProjectReferencesLogicConfig(withNodeNext?
2626
],
2727
});
2828
}
29-
export function getFsContentsForSampleProjectReferences(withNodeNext?: boolean): FsContents {
29+
export function getFsContentsForSampleProjectReferences(withNodeNext?: boolean, skipReferenceCoreFromTest?: boolean): FsContents {
3030
return {
3131
[libFile.path]: libFile.content,
3232
"/user/username/projects/sample1/core/tsconfig.json": jsonToReadableText({
@@ -55,10 +55,14 @@ export function getFsContentsForSampleProjectReferences(withNodeNext?: boolean):
5555
export const m = mod;
5656
`,
5757
"/user/username/projects/sample1/tests/tsconfig.json": jsonToReadableText({
58-
references: [
59-
{ path: "../core" },
60-
{ path: "../logic" },
61-
],
58+
references: !skipReferenceCoreFromTest ?
59+
[
60+
{ path: "../core" },
61+
{ path: "../logic" },
62+
] :
63+
[
64+
{ path: "../logic" },
65+
],
6266
files: ["index.ts"],
6367
compilerOptions: {
6468
...getProjectConfigWithNodeNext(withNodeNext),
@@ -81,19 +85,19 @@ export function getFsContentsForSampleProjectReferences(withNodeNext?: boolean):
8185
};
8286
}
8387

84-
export function getFsForSampleProjectReferences() {
88+
export function getFsForSampleProjectReferences(withNodeNext?: boolean, skipReferenceCoreFromTest?: boolean) {
8589
return loadProjectFromFiles(
86-
getFsContentsForSampleProjectReferences(),
90+
getFsContentsForSampleProjectReferences(withNodeNext, skipReferenceCoreFromTest),
8791
{
8892
cwd: "/user/username/projects/sample1",
8993
executingFilePath: libFile.path,
9094
},
9195
);
9296
}
9397

94-
export function getSysForSampleProjectReferences(withNodeNext?: boolean) {
98+
export function getSysForSampleProjectReferences(withNodeNext?: boolean, skipReferenceCoreFromTest?: boolean) {
9599
return createWatchedSystem(
96-
getFsContentsForSampleProjectReferences(withNodeNext),
100+
getFsContentsForSampleProjectReferences(withNodeNext, skipReferenceCoreFromTest),
97101
{
98102
currentDirectory: "/user/username/projects/sample1",
99103
},

src/testRunner/unittests/tsbuild/sample.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,23 @@ describe("unittests:: tsbuild:: on 'sample1' project", () => {
359359
modifyFs: fs => replaceText(fs, "logic/index.ts", "c.multiply(10, 15)", `c.muitply()`),
360360
edits: noChangeOnlyRuns,
361361
});
362+
363+
[false, true].forEach(skipReferenceCoreFromTest =>
364+
verifyTsc({
365+
scenario: "sample1",
366+
subScenario: `skips builds downstream projects if upstream projects have errors with stopBuildOnErrors${skipReferenceCoreFromTest ? " when test does not reference core" : ""}`,
367+
fs: () => getFsForSampleProjectReferences(/*withNodeNext*/ undefined, skipReferenceCoreFromTest),
368+
commandLineArgs: ["--b", "tests", "--verbose", "--stopBuildOnErrors"],
369+
modifyFs: fs => appendText(fs, "core/index.ts", `multiply();`),
370+
edits: [
371+
noChangeRun,
372+
{
373+
caption: "fix error",
374+
edit: fs => replaceText(fs, "core/index.ts", "multiply();", ""),
375+
},
376+
],
377+
})
378+
);
362379
});
363380

364381
describe("project invalidation", () => {

src/testRunner/unittests/tsbuildWatch/programUpdates.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,28 @@ createSomeObject().message;`,
316316
}
317317
verifyIncrementalErrors("when preserveWatchOutput is not used", ts.emptyArray);
318318
verifyIncrementalErrors("when preserveWatchOutput is passed on command line", ["--preserveWatchOutput"]);
319+
verifyIncrementalErrors("when stopBuildOnErrors is passed on command line", ["--stopBuildOnErrors"]);
320+
321+
[false, true].forEach(skipReferenceCoreFromTest =>
322+
verifyTscWatch({
323+
scenario: "programUpdates",
324+
subScenario: `skips builds downstream projects if upstream projects have errors with stopBuildOnErrors${skipReferenceCoreFromTest ? " when test does not reference core" : ""}`,
325+
sys: () => {
326+
const sys = getSysForSampleProjectReferences(/*withNodeNext*/ undefined, skipReferenceCoreFromTest);
327+
sys.appendFile("core/index.ts", `multiply();`);
328+
return sys;
329+
},
330+
commandLineArgs: ["--b", "-w", "tests", "--verbose", "--stopBuildOnErrors"],
331+
edits: [{
332+
caption: "fix error",
333+
edit: sys => sys.replaceFileText("core/index.ts", "multiply();", ""),
334+
timeouts: sys => {
335+
sys.runQueuedTimeoutCallbacks();
336+
sys.runQueuedTimeoutCallbacks();
337+
},
338+
}],
339+
})
340+
);
319341

320342
describe("when declaration emit errors are present", () => {
321343
const solution = "solution";

tests/baselines/reference/api/typescript.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9802,6 +9802,7 @@ declare namespace ts {
98029802
dry?: boolean;
98039803
force?: boolean;
98049804
verbose?: boolean;
9805+
stopBuildOnErrors?: boolean;
98059806
incremental?: boolean;
98069807
assumeChangesOnlyAffectDirectDependencies?: boolean;
98079808
declaration?: boolean;

0 commit comments

Comments
 (0)