Skip to content

Commit 8ac795b

Browse files
committed
Correctly skip upstream-blocked projects
1 parent cb8aa9b commit 8ac795b

3 files changed

Lines changed: 100 additions & 27 deletions

File tree

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3644,6 +3644,14 @@
36443644
"category": "Message",
36453645
"code": 6360
36463646
},
3647+
"Skipping build of project '{0}' because its upstream project '{1}' has errors": {
3648+
"category": "Message",
3649+
"code": 6361
3650+
},
3651+
"Project '{0}' can't be built because it depends on a project with errors": {
3652+
"category": "Message",
3653+
"code": 6362
3654+
},
36473655

36483656
"Variable '{0}' implicitly has an '{1}' type.": {
36493657
"category": "Error",

src/compiler/tsbuild.ts

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ namespace ts {
6969
OutputMissing,
7070
OutOfDateWithSelf,
7171
OutOfDateWithUpstream,
72-
UpstreamOutOfDate
72+
UpstreamOutOfDate,
73+
UpstreamBlocked
7374
}
7475

7576
type UpToDateStatus =
@@ -78,7 +79,8 @@ namespace ts {
7879
| StatusOutputMissing
7980
| StatusOutOfDateWithSelf
8081
| StatusOutOfDateWithUpstream
81-
| StatusUpstreamOutOfDate;
82+
| StatusUpstreamOutOfDate
83+
| StatusUpstreamBlocked;
8284

8385
/**
8486
* The project can't be built at all in its current state. For example,
@@ -128,6 +130,14 @@ namespace ts {
128130
upstreamProjectName: string;
129131
}
130132

133+
/**
134+
* This project depends an upstream project with build errors
135+
*/
136+
interface StatusUpstreamBlocked {
137+
type: UpToDateStatusType.UpstreamBlocked;
138+
upstreamProjectName: string;
139+
}
140+
131141
/**
132142
* One or more of the project's outputs is older than the newest output of
133143
* an upstream project.
@@ -466,33 +476,33 @@ namespace ts {
466476
const outputs = getAllProjectOutputs(project);
467477

468478
// Now see if all outputs are newer than the newest input
469-
let oldestOutputFileName: string = undefined!;
479+
let oldestOutputFileName: string | undefined;
470480
let oldestOutputFileTime: Date = maximumDate;
471481
let newestOutputFileTime: Date = minimumDate;
472482
let newestDeclarationFileContentChangedTime: Date = minimumDate;
483+
let missingOutputFileName: string | undefined;
484+
let isOutOfDateWithInputs = false;
473485
for (const output of outputs) {
474-
// Output is missing
486+
// Output is missing; can stop checking
487+
// Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
475488
if (!host.fileExists(output)) {
476-
return {
477-
type: UpToDateStatusType.OutputMissing,
478-
missingOutputFileName: output
479-
};
489+
missingOutputFileName = output;
490+
break;
480491
}
481492

482493
const outputTime = host.getModifiedTime!(output);
483-
// If an output is older than the newest input, we can stop checking
484-
if (outputTime < newestInputFileTime) {
485-
return {
486-
type: UpToDateStatusType.OutOfDateWithSelf,
487-
outOfDateOutputFileName: output,
488-
newerInputFileName: newestInputFileName
489-
};
490-
}
491-
492494
if (outputTime < oldestOutputFileTime) {
493495
oldestOutputFileTime = outputTime;
494496
oldestOutputFileName = output;
495497
}
498+
499+
// If an output is older than the newest input, we can stop checking
500+
// Don't immediately return because we can still be upstream-blocked, which is a higher-priority status
501+
if (outputTime < newestInputFileTime) {
502+
isOutOfDateWithInputs = true;
503+
break;
504+
}
505+
496506
newestOutputFileTime = newer(newestOutputFileTime, outputTime);
497507

498508
// Keep track of when the most recent time a .d.ts file was changed.
@@ -511,13 +521,19 @@ namespace ts {
511521
}
512522

513523
let pseudoUpToDate = false;
514-
// By here, we know the project is at least up-to-date with its own inputs.
515-
// See if any of its upstream projects are newer than it
516524
if (project.projectReferences) {
517525
for (const ref of project.projectReferences) {
518526
const resolvedRef = resolveProjectReferencePath(host, ref) as ResolvedConfigFileName;
519527
const refStatus = getUpToDateStatus(configFileCache.parseConfigFile(resolvedRef));
520528

529+
// An upstream project is blocked
530+
if (refStatus.type === UpToDateStatusType.Unbuildable) {
531+
return {
532+
type: UpToDateStatusType.UpstreamBlocked,
533+
upstreamProjectName: ref.path
534+
};
535+
}
536+
521537
// If the upstream project is out of date, then so are we (someone shouldn't have asked, though?)
522538
if (refStatus.type !== UpToDateStatusType.UpToDate) {
523539
return {
@@ -543,12 +559,27 @@ namespace ts {
543559
Debug.assert(oldestOutputFileName !== undefined, "Should have an oldest output filename here");
544560
return {
545561
type: UpToDateStatusType.OutOfDateWithUpstream,
546-
outOfDateOutputFileName: oldestOutputFileName,
562+
outOfDateOutputFileName: oldestOutputFileName!,
547563
newerProjectName: ref.path
548564
};
549565
}
550566
}
551567

568+
if (missingOutputFileName !== undefined) {
569+
return {
570+
type: UpToDateStatusType.OutputMissing,
571+
missingOutputFileName
572+
};
573+
}
574+
575+
if (isOutOfDateWithInputs) {
576+
return {
577+
type: UpToDateStatusType.OutOfDateWithSelf,
578+
outOfDateOutputFileName: oldestOutputFileName!,
579+
newerInputFileName: newestInputFileName
580+
};
581+
}
582+
552583
// Up to date
553584
return {
554585
type: pseudoUpToDate ? UpToDateStatusType.UpToDateWithUpstreamTypes : UpToDateStatusType.UpToDate,
@@ -637,6 +668,7 @@ namespace ts {
637668
if (!configFile) {
638669
// Failed to read the config file
639670
resultFlags |= BuildResultFlags.ConfigFileErrors;
671+
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
640672
return resultFlags;
641673
}
642674

@@ -660,6 +692,7 @@ namespace ts {
660692
for (const diag of syntaxDiagnostics) {
661693
reportDiagnostic(diag);
662694
}
695+
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Syntactic errors" });
663696
return resultFlags;
664697
}
665698

@@ -671,6 +704,7 @@ namespace ts {
671704
for (const diag of declDiagnostics) {
672705
reportDiagnostic(diag);
673706
}
707+
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Declaration file errors" });
674708
return resultFlags;
675709
}
676710
}
@@ -681,6 +715,7 @@ namespace ts {
681715
for (const diag of semanticDiagnostics) {
682716
reportDiagnostic(diag);
683717
}
718+
context.projectStatus.setValue(proj, { type: UpToDateStatusType.Unbuildable, reason: "Semantic errors" });
684719
return resultFlags;
685720
}
686721

@@ -704,7 +739,6 @@ namespace ts {
704739
});
705740

706741
context.projectStatus.setValue(proj, { type: UpToDateStatusType.UpToDate, newestDeclarationFileContentChangedTime } as UpToDateStatus);
707-
708742
return resultFlags;
709743
}
710744

@@ -812,15 +846,15 @@ namespace ts {
812846
if (proj === undefined) {
813847
break;
814848
}
815-
816849
const status = getUpToDateStatus(proj);
817850
reportProjectStatus(next, status);
818851

852+
const projName = proj.options.configFilePath;
819853
if (status.type === UpToDateStatusType.UpToDate && !context.options.force) {
820854
// Up to date, skip
821855
if (options.dry) {
822856
// In a dry build, inform the user of this fact
823-
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Project_0_is_up_to_date));
857+
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Project_0_is_up_to_date, projName));
824858
}
825859
continue;
826860
}
@@ -831,10 +865,12 @@ namespace ts {
831865
continue;
832866
}
833867

834-
const result = buildSingleProject(next);
835-
if (result & BuildResultFlags.AnyErrors) {
836-
break;
868+
if (status.type === UpToDateStatusType.UpstreamBlocked) {
869+
context.verbose(Diagnostics.Skipping_build_of_project_0_because_its_upstream_project_1_has_errors, projName, status.upstreamProjectName);
870+
continue;
837871
}
872+
873+
buildSingleProject(next);
838874
}
839875

840876
function getNext(): ResolvedConfigFileName | undefined {
@@ -889,9 +925,13 @@ namespace ts {
889925
case UpToDateStatusType.UpToDateWithUpstreamTypes:
890926
context.verbose(Diagnostics.Project_0_is_up_to_date_with_its_upstream_types, configFileName);
891927
return;
892-
case UpToDateStatusType.UpstreamOutOfDate:
928+
case UpToDateStatusType.UpstreamOutOfDate:
893929
context.verbose(Diagnostics.Project_0_is_up_to_date_with_its_upstream_types, configFileName);
894930
return;
931+
case UpToDateStatusType.UpstreamBlocked:
932+
context.verbose(Diagnostics.Project_0_can_t_be_built_because_it_depends_on_a_project_with_errors, configFileName);
933+
return;
934+
895935
default:
896936
throw new Error(`Invalid build status - ${UpToDateStatusType[status.type]}`);
897937
}

src/harness/unittests/tsbuild.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,31 @@ namespace ts {
181181
});
182182
});
183183

184+
describe("tsbuild - downstream-blocked compilations", () => {
185+
it("won't build downstream projects if upstream projects have errors", () => {
186+
const fs = bfs.shadow();
187+
const host = new fakes.CompilerHost(fs);
188+
const builder = createSolutionBuilder(host, reportDiagnostic, { dry: false, force: false, verbose: true });
189+
190+
clearDiagnostics();
191+
192+
// Induce an error in the middle project
193+
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
194+
fs.chdir("/src/tests");
195+
builder.buildProjects(["."]);
196+
assertDiagnosticMessages(
197+
Diagnostics.Sorted_list_of_input_projects_Colon_0,
198+
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
199+
Diagnostics.Building_project_0,
200+
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
201+
Diagnostics.Building_project_0,
202+
Diagnostics.Property_0_does_not_exist_on_type_1,
203+
Diagnostics.Project_0_can_t_be_built_because_it_depends_on_a_project_with_errors,
204+
Diagnostics.Skipping_build_of_project_0_because_its_upstream_project_1_has_errors
205+
);
206+
});
207+
});
208+
184209
function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
185210
if (!fs.statSync(path).isFile()) {
186211
throw new Error(`File ${path} does not exist`);

0 commit comments

Comments
 (0)