Skip to content

Commit 5b361c8

Browse files
committed
Make API to build project and wire cancellation token
1 parent 9ba4ab1 commit 5b361c8

14 files changed

Lines changed: 233 additions & 117 deletions

src/compiler/tsbuild.ts

Lines changed: 107 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ namespace ts {
255255
}
256256

257257
export interface SolutionBuilder {
258-
buildAllProjects(): ExitStatus;
258+
build(project?: string, cancellationToken?: CancellationToken): ExitStatus;
259259
cleanAllProjects(): ExitStatus;
260260

261261
// Currently used for testing but can be made public if needed:
@@ -264,14 +264,21 @@ namespace ts {
264264
// Testing only
265265
/*@internal*/ getUpToDateStatusOfProject(project: string): UpToDateStatus;
266266
/*@internal*/ invalidateProject(configFileName: string, reloadLevel?: ConfigFileProgramReloadLevel): void;
267-
/*@internal*/ buildInvalidatedProject(): void;
267+
/*@internal*/ buildNextInvalidatedProject(): void;
268268
}
269269

270270
export interface SolutionBuilderWithWatch {
271-
buildAllProjects(): ExitStatus;
271+
build(project?: string, cancellationToken?: CancellationToken): ExitStatus;
272272
/*@internal*/ startWatching(): void;
273273
}
274274

275+
interface InvalidatedProject {
276+
project: ResolvedConfigFileName;
277+
projectPath: ResolvedConfigFilePath;
278+
reloadLevel: ConfigFileProgramReloadLevel;
279+
projectIndex: number;
280+
}
281+
275282
/**
276283
* Create a function that reports watch status by writing to the system and handles the formating of the diagnostic
277284
*/
@@ -386,18 +393,22 @@ namespace ts {
386393
const allWatchedInputFiles = createMap() as ConfigFileMap<Map<FileWatcher>>;
387394
const allWatchedConfigFiles = createMap() as ConfigFileMap<FileWatcher>;
388395

396+
let allProjectBuildPending = true;
397+
let needsSummary = true;
398+
// let watchAllProjectsPending = watch;
399+
389400
return watch ?
390401
{
391-
buildAllProjects,
402+
build,
392403
startWatching
393404
} :
394405
{
395-
buildAllProjects,
406+
build,
396407
cleanAllProjects,
397408
getBuildOrder,
398409
getUpToDateStatusOfProject,
399410
invalidateProject,
400-
buildInvalidatedProject,
411+
buildNextInvalidatedProject,
401412
};
402413

403414
function toPath(fileName: string) {
@@ -800,6 +811,7 @@ namespace ts {
800811
configFileCache.delete(resolved);
801812
buildOrder = undefined;
802813
}
814+
needsSummary = true;
803815
clearProjectStatus(resolved);
804816
addProjToQueue(resolved, reloadLevel);
805817
enableCache();
@@ -823,16 +835,16 @@ namespace ts {
823835
}
824836
}
825837

826-
function getNextInvalidatedProject() {
827-
Debug.assert(hasPendingInvalidatedProjects());
828-
return forEach(getBuildOrder(), (project, projectIndex) => {
829-
const projectPath = toResolvedConfigFilePath(project);
830-
const reloadLevel = projectPendingBuild.get(projectPath);
831-
if (reloadLevel !== undefined) {
832-
projectPendingBuild.delete(projectPath);
833-
return { project, projectPath, reloadLevel, projectIndex };
834-
}
835-
});
838+
function getNextInvalidatedProject(buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined {
839+
return hasPendingInvalidatedProjects() ?
840+
forEach(buildOrder, (project, projectIndex) => {
841+
const projectPath = toResolvedConfigFilePath(project);
842+
const reloadLevel = projectPendingBuild.get(projectPath);
843+
if (reloadLevel !== undefined) {
844+
return { project, projectPath, reloadLevel, projectIndex };
845+
}
846+
}) :
847+
undefined;
836848
}
837849

838850
function hasPendingInvalidatedProjects() {
@@ -846,18 +858,19 @@ namespace ts {
846858
if (timerToBuildInvalidatedProject) {
847859
hostWithWatch.clearTimeout(timerToBuildInvalidatedProject);
848860
}
849-
timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildInvalidatedProject, 250);
861+
timerToBuildInvalidatedProject = hostWithWatch.setTimeout(buildNextInvalidatedProject, 250);
850862
}
851863

852-
function buildInvalidatedProject() {
864+
function buildNextInvalidatedProject() {
853865
timerToBuildInvalidatedProject = undefined;
854866
if (reportFileChangeDetected) {
855867
reportFileChangeDetected = false;
856868
projectErrorsReported.clear();
857869
reportWatchStatus(Diagnostics.File_change_detected_Starting_incremental_compilation);
858870
}
859-
if (hasPendingInvalidatedProjects()) {
860-
buildNextInvalidatedProject();
871+
const invalidatedProject = getNextInvalidatedProject(getBuildOrder());
872+
if (invalidatedProject) {
873+
buildInvalidatedProject(invalidatedProject);
861874
if (hasPendingInvalidatedProjects()) {
862875
if (watch && !timerToBuildInvalidatedProject) {
863876
scheduleBuildInvalidatedProject();
@@ -872,6 +885,7 @@ namespace ts {
872885

873886
function reportErrorSummary() {
874887
if (watch || host.reportErrorSummary) {
888+
needsSummary = false;
875889
// Report errors from the other projects
876890
getBuildOrder().forEach(project => {
877891
const projectPath = toResolvedConfigFilePath(project);
@@ -890,11 +904,11 @@ namespace ts {
890904
}
891905
}
892906

893-
function buildNextInvalidatedProject() {
894-
const { project, projectPath, reloadLevel, projectIndex } = getNextInvalidatedProject()!;
907+
function buildInvalidatedProject({ project, projectPath, reloadLevel, projectIndex }: InvalidatedProject, cancellationToken?: CancellationToken) {
895908
const config = parseConfigFile(project, projectPath);
896909
if (!config) {
897910
reportParseConfigFileDiagnostic(projectPath);
911+
projectPendingBuild.delete(projectPath);
898912
return;
899913
}
900914

@@ -920,31 +934,36 @@ namespace ts {
920934
// In a dry build, inform the user of this fact
921935
reportStatus(Diagnostics.Project_0_is_up_to_date, project);
922936
}
937+
projectPendingBuild.delete(projectPath);
923938
return;
924939
}
925940

926941
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes && !options.force) {
927942
reportAndStoreErrors(projectPath, config.errors);
928943
// Fake that files have been built by updating output file stamps
929944
updateOutputTimestamps(config, projectPath);
945+
projectPendingBuild.delete(projectPath);
930946
return;
931947
}
932948

933949
if (status.type === UpToDateStatusType.UpstreamBlocked) {
934950
reportAndStoreErrors(projectPath, config.errors);
935951
if (options.verbose) reportStatus(Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName);
952+
projectPendingBuild.delete(projectPath);
936953
return;
937954
}
938955

939956
if (status.type === UpToDateStatusType.ContainerOnly) {
940957
reportAndStoreErrors(projectPath, config.errors);
941958
// Do nothing
959+
projectPendingBuild.delete(projectPath);
942960
return;
943961
}
944962

945963
const buildResult = needsBuild(status, config) ?
946-
buildSingleProject(project, projectPath) : // Actual build
947-
updateBundle(project, projectPath); // Fake that files have been built by manipulating prepend and existing output
964+
buildSingleProject(project, projectPath, cancellationToken) : // Actual build
965+
updateBundle(project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output
966+
projectPendingBuild.delete(projectPath);
948967
// Only composite projects can be referenced by other projects
949968
if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) {
950969
queueReferencingProjects(project, projectPath, projectIndex, !(buildResult & BuildResultFlags.DeclarationOutputUnchanged));
@@ -1050,7 +1069,7 @@ namespace ts {
10501069
}
10511070
}
10521071

1053-
function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags {
1072+
function buildSingleProject(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags {
10541073
if (options.dry) {
10551074
reportStatus(Diagnostics.A_non_dry_build_would_build_project_0, proj);
10561075
return BuildResultFlags.Success;
@@ -1112,15 +1131,15 @@ namespace ts {
11121131
// Don't emit anything in the presence of syntactic errors or options diagnostics
11131132
const syntaxDiagnostics = [
11141133
...program.getConfigFileParsingDiagnostics(),
1115-
...program.getOptionsDiagnostics(),
1116-
...program.getGlobalDiagnostics(),
1117-
...program.getSyntacticDiagnostics()];
1134+
...program.getOptionsDiagnostics(cancellationToken),
1135+
...program.getGlobalDiagnostics(cancellationToken),
1136+
...program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken)];
11181137
if (syntaxDiagnostics.length) {
11191138
return buildErrors(syntaxDiagnostics, BuildResultFlags.SyntaxErrors, "Syntactic");
11201139
}
11211140

11221141
// Same as above but now for semantic diagnostics
1123-
const semanticDiagnostics = program.getSemanticDiagnostics();
1142+
const semanticDiagnostics = program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken);
11241143
if (semanticDiagnostics.length) {
11251144
return buildErrors(semanticDiagnostics, BuildResultFlags.TypeErrors, "Semantic");
11261145
}
@@ -1132,7 +1151,14 @@ namespace ts {
11321151
let declDiagnostics: Diagnostic[] | undefined;
11331152
const reportDeclarationDiagnostics = (d: Diagnostic) => (declDiagnostics || (declDiagnostics = [])).push(d);
11341153
const outputFiles: OutputFile[] = [];
1135-
emitFilesAndReportErrors(program, reportDeclarationDiagnostics, /*writeFileName*/ undefined, /*reportSummary*/ undefined, (name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }));
1154+
emitFilesAndReportErrors(
1155+
program,
1156+
reportDeclarationDiagnostics,
1157+
/*writeFileName*/ undefined,
1158+
/*reportSummary*/ undefined,
1159+
(name, text, writeByteOrderMark) => outputFiles.push({ name, text, writeByteOrderMark }),
1160+
cancellationToken
1161+
);
11361162
// Don't emit .d.ts if there are decl file errors
11371163
if (declDiagnostics) {
11381164
program.restoreState();
@@ -1221,7 +1247,7 @@ namespace ts {
12211247
return readBuilderProgram(parsed.options, readFileWithCache) as any as T;
12221248
}
12231249

1224-
function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath): BuildResultFlags {
1250+
function updateBundle(proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags {
12251251
if (options.dry) {
12261252
reportStatus(Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj);
12271253
return BuildResultFlags.Success;
@@ -1241,7 +1267,7 @@ namespace ts {
12411267
});
12421268
if (isString(outputFiles)) {
12431269
reportStatus(Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(outputFiles));
1244-
return buildSingleProject(proj, resolvedPath);
1270+
return buildSingleProject(proj, resolvedPath, cancellationToken);
12451271
}
12461272

12471273
// Actual Emit
@@ -1403,21 +1429,58 @@ namespace ts {
14031429
cacheState = undefined;
14041430
}
14051431

1406-
function buildAllProjects(): ExitStatus {
1407-
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
1408-
enableCache();
1432+
function build(project?: string, cancellationToken?: CancellationToken): ExitStatus {
1433+
// Set initial build if not already built
1434+
if (allProjectBuildPending) {
1435+
allProjectBuildPending = false;
1436+
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
1437+
enableCache();
1438+
const buildOrder = getBuildOrder();
1439+
reportBuildQueue(buildOrder);
1440+
buildOrder.forEach(configFileName =>
1441+
projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None));
14091442

1410-
const buildOrder = getBuildOrder();
1411-
reportBuildQueue(buildOrder);
1412-
buildOrder.forEach(configFileName =>
1413-
projectPendingBuild.set(toResolvedConfigFilePath(configFileName), ConfigFileProgramReloadLevel.None));
1443+
if (cancellationToken) {
1444+
cancellationToken.throwIfCancellationRequested();
1445+
}
1446+
}
14141447

1415-
while (hasPendingInvalidatedProjects()) {
1416-
buildNextInvalidatedProject();
1448+
let successfulProjects = 0;
1449+
let errorProjects = 0;
1450+
const resolvedProject = project && resolveProjectName(project);
1451+
if (resolvedProject) {
1452+
const projectPath = toResolvedConfigFilePath(resolvedProject);
1453+
const projectIndex = findIndex(
1454+
getBuildOrder(),
1455+
configFileName => toResolvedConfigFilePath(configFileName) === projectPath
1456+
);
1457+
if (projectIndex === -1) return ExitStatus.InvalidProject_OutputsSkipped;
1458+
}
1459+
const buildOrder = resolvedProject ? createBuildOrder([resolvedProject]) : getBuildOrder();
1460+
while (true) {
1461+
const invalidatedProject = getNextInvalidatedProject(buildOrder);
1462+
if (!invalidatedProject) {
1463+
if (needsSummary) {
1464+
disableCache();
1465+
reportErrorSummary();
1466+
}
1467+
break;
1468+
}
1469+
1470+
buildInvalidatedProject(invalidatedProject, cancellationToken);
1471+
if (diagnostics.has(invalidatedProject.projectPath)) {
1472+
errorProjects++;
1473+
}
1474+
else {
1475+
successfulProjects++;
1476+
}
14171477
}
1418-
reportErrorSummary();
1419-
disableCache();
1420-
return diagnostics.size ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
1478+
1479+
return errorProjects ?
1480+
successfulProjects ?
1481+
ExitStatus.DiagnosticsPresent_OutputsGenerated :
1482+
ExitStatus.DiagnosticsPresent_OutputsSkipped :
1483+
ExitStatus.Success;
14211484
}
14221485

14231486
function needsBuild(status: UpToDateStatus, config: ParsedCommandLine) {

src/compiler/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3053,6 +3053,9 @@ namespace ts {
30533053

30543054
// Diagnostics were produced and outputs were generated in spite of them.
30553055
DiagnosticsPresent_OutputsGenerated = 2,
3056+
3057+
// When build skipped because passed in project is invalid
3058+
InvalidProject_OutputsSkipped = 3,
30563059
}
30573060

30583061
export interface EmitResult {

src/compiler/watch.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,12 @@ namespace ts {
113113
getCurrentDirectory(): string;
114114
getCompilerOptions(): CompilerOptions;
115115
getSourceFiles(): ReadonlyArray<SourceFile>;
116-
getSyntacticDiagnostics(): ReadonlyArray<Diagnostic>;
117-
getOptionsDiagnostics(): ReadonlyArray<Diagnostic>;
118-
getGlobalDiagnostics(): ReadonlyArray<Diagnostic>;
119-
getSemanticDiagnostics(): ReadonlyArray<Diagnostic>;
116+
getSyntacticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
117+
getOptionsDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
118+
getGlobalDiagnostics(cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
119+
getSemanticDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): ReadonlyArray<Diagnostic>;
120120
getConfigFileParsingDiagnostics(): ReadonlyArray<Diagnostic>;
121-
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback): EmitResult;
121+
emit(targetSourceFile?: SourceFile, writeFile?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult;
122122
}
123123

124124
export function listFiles(program: ProgramToEmitFilesAndReportErrors, writeFileName: (s: string) => void) {
@@ -132,25 +132,32 @@ namespace ts {
132132
/**
133133
* Helper that emit files, report diagnostics and lists emitted and/or source files depending on compiler options
134134
*/
135-
export function emitFilesAndReportErrors(program: ProgramToEmitFilesAndReportErrors, reportDiagnostic: DiagnosticReporter, writeFileName?: (s: string) => void, reportSummary?: ReportEmitErrorSummary, writeFile?: WriteFileCallback) {
135+
export function emitFilesAndReportErrors(
136+
program: ProgramToEmitFilesAndReportErrors,
137+
reportDiagnostic: DiagnosticReporter,
138+
writeFileName?: (s: string) => void,
139+
reportSummary?: ReportEmitErrorSummary,
140+
writeFile?: WriteFileCallback,
141+
cancellationToken?: CancellationToken
142+
) {
136143
// First get and report any syntactic errors.
137144
const diagnostics = program.getConfigFileParsingDiagnostics().slice();
138145
const configFileParsingDiagnosticsLength = diagnostics.length;
139-
addRange(diagnostics, program.getSyntacticDiagnostics());
146+
addRange(diagnostics, program.getSyntacticDiagnostics(/*sourceFile*/ undefined, cancellationToken));
140147

141148
// If we didn't have any syntactic errors, then also try getting the global and
142149
// semantic errors.
143150
if (diagnostics.length === configFileParsingDiagnosticsLength) {
144-
addRange(diagnostics, program.getOptionsDiagnostics());
145-
addRange(diagnostics, program.getGlobalDiagnostics());
151+
addRange(diagnostics, program.getOptionsDiagnostics(cancellationToken));
152+
addRange(diagnostics, program.getGlobalDiagnostics(cancellationToken));
146153

147154
if (diagnostics.length === configFileParsingDiagnosticsLength) {
148-
addRange(diagnostics, program.getSemanticDiagnostics());
155+
addRange(diagnostics, program.getSemanticDiagnostics(/*sourceFile*/ undefined, cancellationToken));
149156
}
150157
}
151158

152159
// Emit and report any errors we ran into.
153-
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile);
160+
const { emittedFiles, emitSkipped, diagnostics: emitDiagnostics } = program.emit(/*targetSourceFile*/ undefined, writeFile, cancellationToken);
154161
addRange(diagnostics, emitDiagnostics);
155162

156163
sortAndDeduplicateDiagnostics(diagnostics).forEach(reportDiagnostic);

0 commit comments

Comments
 (0)