Skip to content

Commit 6227fab

Browse files
committed
Make invalidated project when only need to be built or updated
1 parent f017433 commit 6227fab

File tree

1 file changed

+177
-118
lines changed

1 file changed

+177
-118
lines changed

src/compiler/tsbuild.ts

Lines changed: 177 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -283,14 +283,30 @@ namespace ts {
283283
/*@internal*/ buildNextInvalidatedProject(): void;
284284
}
285285

286-
interface InvalidatedProject {
286+
const enum InvalidatedProjectKind {
287+
BuildProject,
288+
UpdateBundle,
289+
UpdateOutputFileStamps
290+
}
291+
292+
interface UpdateOutputFileStampsProject {
293+
readonly kind: InvalidatedProjectKind.UpdateOutputFileStamps;
294+
readonly project: ResolvedConfigFileName;
295+
readonly projectPath: ResolvedConfigFilePath;
296+
readonly config: ParsedCommandLine;
297+
}
298+
299+
interface BuildOrUpdateBundleProject {
300+
readonly kind: InvalidatedProjectKind.BuildProject | InvalidatedProjectKind.UpdateBundle;
287301
readonly project: ResolvedConfigFileName;
288302
readonly projectPath: ResolvedConfigFilePath;
289-
readonly reloadLevel: ConfigFileProgramReloadLevel;
290303
readonly projectIndex: number;
304+
readonly config: ParsedCommandLine;
291305
readonly buildOrder: readonly ResolvedConfigFileName[];
292306
}
293307

308+
type InvalidatedProject = UpdateOutputFileStampsProject | BuildOrUpdateBundleProject;
309+
294310
/**
295311
* Create a function that reports watch status by writing to the system and handles the formating of the diagnostic
296312
*/
@@ -663,15 +679,90 @@ namespace ts {
663679
}
664680

665681
function getNextInvalidatedProject(state: SolutionBuilderState, buildOrder: readonly ResolvedConfigFileName[]): InvalidatedProject | undefined {
666-
return state.projectPendingBuild.size ?
667-
forEach(buildOrder, (project, projectIndex) => {
668-
const projectPath = toResolvedConfigFilePath(state, project);
669-
const reloadLevel = state.projectPendingBuild.get(projectPath);
670-
if (reloadLevel !== undefined) {
671-
return { project, projectPath, reloadLevel, projectIndex, buildOrder };
682+
if (!state.projectPendingBuild.size) return undefined;
683+
684+
const { options, projectPendingBuild } = state;
685+
for (let projectIndex = 0; projectIndex < buildOrder.length; projectIndex++) {
686+
const project = buildOrder[projectIndex];
687+
const projectPath = toResolvedConfigFilePath(state, project);
688+
const reloadLevel = state.projectPendingBuild.get(projectPath);
689+
if (reloadLevel === undefined) continue;
690+
691+
const config = parseConfigFile(state, project, projectPath);
692+
if (!config) {
693+
reportParseConfigFileDiagnostic(state, projectPath);
694+
projectPendingBuild.delete(projectPath);
695+
continue;
696+
}
697+
698+
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
699+
watchConfigFile(state, project, projectPath);
700+
watchWildCardDirectories(state, project, projectPath, config);
701+
watchInputFiles(state, project, projectPath, config);
702+
}
703+
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
704+
// Update file names
705+
const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost);
706+
updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw));
707+
config.fileNames = result.fileNames;
708+
watchInputFiles(state, project, projectPath, config);
709+
}
710+
711+
const status = getUpToDateStatus(state, config, projectPath);
712+
verboseReportProjectStatus(state, project, status);
713+
if (!options.force) {
714+
if (status.type === UpToDateStatusType.UpToDate) {
715+
reportAndStoreErrors(state, projectPath, config.errors);
716+
projectPendingBuild.delete(projectPath);
717+
// Up to date, skip
718+
if (options.dry) {
719+
// In a dry build, inform the user of this fact
720+
reportStatus(state, Diagnostics.Project_0_is_up_to_date, project);
721+
}
722+
continue;
672723
}
673-
}) :
674-
undefined;
724+
725+
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
726+
reportAndStoreErrors(state, projectPath, config.errors);
727+
return {
728+
kind: InvalidatedProjectKind.UpdateOutputFileStamps,
729+
project,
730+
projectPath,
731+
config
732+
};
733+
734+
continue;
735+
}
736+
}
737+
738+
if (status.type === UpToDateStatusType.UpstreamBlocked) {
739+
reportAndStoreErrors(state, projectPath, config.errors);
740+
projectPendingBuild.delete(projectPath);
741+
if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName);
742+
continue;
743+
}
744+
745+
if (status.type === UpToDateStatusType.ContainerOnly) {
746+
reportAndStoreErrors(state, projectPath, config.errors);
747+
projectPendingBuild.delete(projectPath);
748+
// Do nothing
749+
continue;
750+
}
751+
752+
753+
return {
754+
kind: needsBuild(state, status, config) ?
755+
InvalidatedProjectKind.BuildProject :
756+
InvalidatedProjectKind.UpdateBundle,
757+
project,
758+
projectPath,
759+
projectIndex,
760+
config,
761+
buildOrder
762+
};
763+
}
764+
765+
return undefined;
675766
}
676767

677768
function listEmittedFile({ writeFileName }: SolutionBuilderState, proj: ParsedCommandLine, file: string) {
@@ -714,62 +805,70 @@ namespace ts {
714805
return errorFlags;
715806
}
716807

717-
function buildSingleProject(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags {
808+
function updateModuleResolutionCache(
809+
state: SolutionBuilderState,
810+
proj: ResolvedConfigFileName,
811+
config: ParsedCommandLine
812+
) {
813+
if (!state.moduleResolutionCache) return;
814+
815+
// Update module resolution cache if needed
816+
const { moduleResolutionCache } = state;
817+
const projPath = toPath(state, proj);
818+
if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) {
819+
// The own map will be for projectCompilerOptions
820+
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0);
821+
moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap);
822+
moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap);
823+
}
824+
else {
825+
// Set correct own map
826+
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0);
827+
828+
const ref: ResolvedProjectReference = {
829+
sourceFile: config.options.configFile!,
830+
commandLine: config
831+
};
832+
moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref));
833+
moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref));
834+
}
835+
moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(config.options);
836+
moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(config.options);
837+
}
838+
839+
function buildSingleProject(
840+
state: SolutionBuilderState,
841+
proj: ResolvedConfigFileName,
842+
resolvedPath: ResolvedConfigFilePath,
843+
config: ParsedCommandLine,
844+
cancellationToken: CancellationToken | undefined
845+
): BuildResultFlags {
718846
if (state.options.dry) {
719847
reportStatus(state, Diagnostics.A_non_dry_build_would_build_project_0, proj);
720848
return BuildResultFlags.Success;
721849
}
722850

723851
if (state.options.verbose) reportStatus(state, Diagnostics.Building_project_0, proj);
724852

725-
const { host, projectStatus, diagnostics, compilerHost, moduleResolutionCache, } = state;
726-
const configFile = parseConfigFile(state, proj, resolvedPath);
727-
if (!configFile) {
728-
// Failed to read the config file
729-
reportParseConfigFileDiagnostic(state, resolvedPath);
730-
projectStatus.set(resolvedPath, { type: UpToDateStatusType.Unbuildable, reason: "Config file errors" });
731-
return BuildResultFlags.ConfigFileErrors;
732-
}
733-
734-
if (configFile.fileNames.length === 0) {
735-
reportAndStoreErrors(state, resolvedPath, configFile.errors);
853+
if (config.fileNames.length === 0) {
854+
reportAndStoreErrors(state, resolvedPath, config.errors);
736855
// Nothing to build - must be a solution file, basically
737856
return BuildResultFlags.None;
738857
}
739858

740-
state.projectCompilerOptions = configFile.options;
859+
const { host, projectStatus, diagnostics, compilerHost } = state;
860+
state.projectCompilerOptions = config.options;
741861
// Update module resolution cache if needed
742-
if (moduleResolutionCache) {
743-
const projPath = toPath(state, proj);
744-
if (moduleResolutionCache.directoryToModuleNameMap.redirectsMap.size === 0) {
745-
// The own map will be for projectCompilerOptions
746-
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size === 0);
747-
moduleResolutionCache.directoryToModuleNameMap.redirectsMap.set(projPath, moduleResolutionCache.directoryToModuleNameMap.ownMap);
748-
moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.set(projPath, moduleResolutionCache.moduleNameToDirectoryMap.ownMap);
749-
}
750-
else {
751-
// Set correct own map
752-
Debug.assert(moduleResolutionCache.moduleNameToDirectoryMap.redirectsMap.size > 0);
753-
754-
const ref: ResolvedProjectReference = {
755-
sourceFile: configFile.options.configFile!,
756-
commandLine: configFile
757-
};
758-
moduleResolutionCache.directoryToModuleNameMap.setOwnMap(moduleResolutionCache.directoryToModuleNameMap.getOrCreateMapOfCacheRedirects(ref));
759-
moduleResolutionCache.moduleNameToDirectoryMap.setOwnMap(moduleResolutionCache.moduleNameToDirectoryMap.getOrCreateMapOfCacheRedirects(ref));
760-
}
761-
moduleResolutionCache.directoryToModuleNameMap.setOwnOptions(configFile.options);
762-
moduleResolutionCache.moduleNameToDirectoryMap.setOwnOptions(configFile.options);
763-
}
862+
updateModuleResolutionCache(state, proj, config);
764863

765864
// Create program
766865
const program = host.createProgram(
767-
configFile.fileNames,
768-
configFile.options,
866+
config.fileNames,
867+
config.options,
769868
compilerHost,
770-
getOldProgram(state, resolvedPath, configFile),
771-
configFile.errors,
772-
configFile.projectReferences
869+
getOldProgram(state, resolvedPath, config),
870+
config.errors,
871+
config.projectReferences
773872
);
774873

775874
// Don't emit anything in the presence of syntactic errors or options diagnostics
@@ -867,24 +966,30 @@ namespace ts {
867966
}
868967

869968
if (state.writeFileName) {
870-
emittedOutputs.forEach(name => listEmittedFile(state, configFile, name));
969+
emittedOutputs.forEach(name => listEmittedFile(state, config, name));
871970
listFiles(program, state.writeFileName);
872971
}
873972

874973
// Update time stamps for rest of the outputs
875-
newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, configFile, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs);
974+
newestDeclarationFileContentChangedTime = updateOutputTimestampsWorker(state, config, newestDeclarationFileContentChangedTime, Diagnostics.Updating_unchanged_output_timestamps_of_project_0, emittedOutputs);
876975
diagnostics.delete(resolvedPath);
877976
projectStatus.set(resolvedPath, {
878977
type: UpToDateStatusType.UpToDate,
879978
newestDeclarationFileContentChangedTime: anyDtsChanged ? maximumDate : newestDeclarationFileContentChangedTime,
880-
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(configFile, !host.useCaseSensitiveFileNames())
979+
oldestOutputFileName: outputFiles.length ? outputFiles[0].name : getFirstProjectOutput(config, !host.useCaseSensitiveFileNames())
881980
});
882981
afterProgramCreate(state, resolvedPath, program);
883982
state.projectCompilerOptions = state.baseCompilerOptions;
884983
return resultFlags;
885984
}
886985

887-
function updateBundle(state: SolutionBuilderState, proj: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath, cancellationToken: CancellationToken | undefined): BuildResultFlags {
986+
function updateBundle(
987+
state: SolutionBuilderState,
988+
proj: ResolvedConfigFileName,
989+
resolvedPath: ResolvedConfigFilePath,
990+
config: ParsedCommandLine,
991+
cancellationToken: CancellationToken | undefined
992+
): BuildResultFlags {
888993
if (state.options.dry) {
889994
reportStatus(state, Diagnostics.A_non_dry_build_would_update_output_of_project_0, proj);
890995
return BuildResultFlags.Success;
@@ -894,7 +999,6 @@ namespace ts {
894999

8951000
// Update js, and source map
8961001
const { projectStatus, diagnostics, compilerHost } = state;
897-
const config = Debug.assertDefined(parseConfigFile(state, proj, resolvedPath));
8981002
state.projectCompilerOptions = config.options;
8991003
const outputFiles = emitUsingBuildInfo(
9001004
config,
@@ -905,7 +1009,7 @@ namespace ts {
9051009
});
9061010
if (isString(outputFiles)) {
9071011
reportStatus(state, Diagnostics.Cannot_update_output_of_project_0_because_there_was_error_reading_file_1, proj, relName(state, outputFiles));
908-
return buildSingleProject(state, proj, resolvedPath, cancellationToken);
1012+
return buildSingleProject(state, proj, resolvedPath, config, cancellationToken);
9091013
}
9101014

9111015
// Actual Emit
@@ -1210,68 +1314,24 @@ namespace ts {
12101314
!isIncrementalCompilation(config.options);
12111315
}
12121316

1213-
function buildInvalidatedProject(state: SolutionBuilderState, { project, projectPath, reloadLevel, projectIndex, buildOrder }: InvalidatedProject, cancellationToken?: CancellationToken) {
1214-
const { options, projectPendingBuild } = state;
1215-
const config = parseConfigFile(state, project, projectPath);
1216-
if (!config) {
1217-
reportParseConfigFileDiagnostic(state, projectPath);
1218-
projectPendingBuild.delete(projectPath);
1219-
return;
1220-
}
1221-
1222-
if (reloadLevel === ConfigFileProgramReloadLevel.Full) {
1223-
watchConfigFile(state, project, projectPath);
1224-
watchWildCardDirectories(state, project, projectPath, config);
1225-
watchInputFiles(state, project, projectPath, config);
1226-
}
1227-
else if (reloadLevel === ConfigFileProgramReloadLevel.Partial) {
1228-
// Update file names
1229-
const result = getFileNamesFromConfigSpecs(config.configFileSpecs!, getDirectoryPath(project), config.options, state.parseConfigFileHost);
1230-
updateErrorForNoInputFiles(result, project, config.configFileSpecs!, config.errors, canJsonReportNoInutFiles(config.raw));
1231-
config.fileNames = result.fileNames;
1232-
watchInputFiles(state, project, projectPath, config);
1233-
}
1234-
1235-
const status = getUpToDateStatus(state, config, projectPath);
1236-
verboseReportProjectStatus(state, project, status);
1237-
if (!options.force) {
1238-
if (status.type === UpToDateStatusType.UpToDate) {
1239-
reportAndStoreErrors(state, projectPath, config.errors);
1240-
projectPendingBuild.delete(projectPath);
1241-
// Up to date, skip
1242-
if (options.dry) {
1243-
// In a dry build, inform the user of this fact
1244-
reportStatus(state, Diagnostics.Project_0_is_up_to_date, project);
1245-
}
1246-
return;
1247-
}
1248-
1249-
if (status.type === UpToDateStatusType.UpToDateWithUpstreamTypes) {
1250-
reportAndStoreErrors(state, projectPath, config.errors);
1251-
projectPendingBuild.delete(projectPath);
1252-
// Fake that files have been built by updating output file stamps
1253-
updateOutputTimestamps(state, config, projectPath);
1254-
return;
1255-
}
1256-
}
1257-
1258-
if (status.type === UpToDateStatusType.UpstreamBlocked) {
1259-
reportAndStoreErrors(state, projectPath, config.errors);
1260-
projectPendingBuild.delete(projectPath);
1261-
if (options.verbose) reportStatus(state, Diagnostics.Skipping_build_of_project_0_because_its_dependency_1_has_errors, project, status.upstreamProjectName);
1262-
return;
1263-
}
1264-
1265-
if (status.type === UpToDateStatusType.ContainerOnly) {
1266-
reportAndStoreErrors(state, projectPath, config.errors);
1317+
function buildInvalidatedProject(
1318+
state: SolutionBuilderState,
1319+
invalidatedProject: InvalidatedProject,
1320+
cancellationToken?: CancellationToken
1321+
) {
1322+
const { projectPendingBuild } = state;
1323+
if (invalidatedProject.kind === InvalidatedProjectKind.UpdateOutputFileStamps) {
1324+
// Fake that files have been built by updating output file stamps
1325+
const { projectPath, config } = invalidatedProject;
1326+
updateOutputTimestamps(state, config, projectPath);
12671327
projectPendingBuild.delete(projectPath);
1268-
// Do nothing
12691328
return;
12701329
}
12711330

1272-
const buildResult = needsBuild(state, status, config) ?
1273-
buildSingleProject(state, project, projectPath, cancellationToken) : // Actual build
1274-
updateBundle(state, project, projectPath, cancellationToken); // Fake that files have been built by manipulating prepend and existing output
1331+
const { kind, project, projectPath, projectIndex, config, buildOrder } = invalidatedProject;
1332+
const buildResult = kind === InvalidatedProjectKind.BuildProject ?
1333+
buildSingleProject(state, project, projectPath, config, cancellationToken) : // Actual build
1334+
updateBundle(state, project, projectPath, config, cancellationToken); // Fake that files have been built by manipulating prepend and existing output
12751335
projectPendingBuild.delete(projectPath);
12761336
// Only composite projects can be referenced by other projects
12771337
if (!(buildResult & BuildResultFlags.AnyErrors) && config.options.composite) {
@@ -1467,12 +1527,11 @@ namespace ts {
14671527
if (state.watch && !state.timerToBuildInvalidatedProject) {
14681528
scheduleBuildInvalidatedProject(state);
14691529
}
1470-
}
1471-
else {
1472-
disableCache(state);
1473-
reportErrorSummary(state);
1530+
return;
14741531
}
14751532
}
1533+
disableCache(state);
1534+
reportErrorSummary(state);
14761535
}
14771536

14781537
function watchConfigFile(state: SolutionBuilderState, resolved: ResolvedConfigFileName, resolvedPath: ResolvedConfigFilePath) {

0 commit comments

Comments
 (0)