Skip to content

Commit 7cb0f40

Browse files
committed
uptodate status worker to read buildinfo and use it to determine upto date ness
1 parent e6a3ee8 commit 7cb0f40

File tree

168 files changed

+1301
-1657
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

168 files changed

+1301
-1657
lines changed

src/compiler/builder.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,7 @@ namespace ts {
800800
exportedModulesMap?: ProgramBuildInfoReferencedMap;
801801
semanticDiagnosticsPerFile?: ProgramBuildInfoDiagnostic[];
802802
affectedFilesPendingEmit?: ProgramBuilderInfoFilePendingEmit[];
803+
hasPendingChange?: boolean;
803804
}
804805

805806
/**
@@ -898,6 +899,7 @@ namespace ts {
898899
exportedModulesMap,
899900
semanticDiagnosticsPerFile,
900901
affectedFilesPendingEmit,
902+
hasPendingChange: state.changedFilesSet?.size ? true : undefined,
901903
};
902904

903905
function relativeToBuildInfoEnsuringAbsolutePath(path: string) {
@@ -941,7 +943,8 @@ namespace ts {
941943
// flags it controls (e.g. `strictNullChecks`) will be retrieved correctly from the buildinfo
942944
optionKey === "strict" ||
943945
// We need to store these to determine whether `lib` files need to be rechecked.
944-
optionKey === "skiplibcheck" || optionKey === "skipdefaultlibcheck") {
946+
optionKey === "skiplibcheck" || optionKey === "skipdefaultlibcheck" ||
947+
optionKey === "noemit") {
945948
(result ||= {})[name] = convertToReusableCompilerOptionValue(
946949
optionInfo,
947950
options[name] as CompilerOptionsValue,

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -5272,6 +5272,10 @@
52725272
"category": "Message",
52735273
"code": 6398
52745274
},
5275+
"Project '{0}' is out of date because buildinfo file '{1}' indicates that some of the changes are not emitted": {
5276+
"category": "Message",
5277+
"code": 6399
5278+
},
52755279

52765280
"The expected type comes from property '{0}' which is declared here on type '{1}'": {
52775281
"category": "Message",

src/compiler/tsbuild.ts

+10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ namespace ts {
1818
OutputMissing,
1919
OutOfDateWithSelf,
2020
OutOfDateWithUpstream,
21+
OutOfDateBuildInfo,
2122
UpstreamOutOfDate,
2223
UpstreamBlocked,
2324
ComputingUpstream,
@@ -37,6 +38,7 @@ namespace ts {
3738
| Status.OutputMissing
3839
| Status.OutOfDateWithSelf
3940
| Status.OutOfDateWithUpstream
41+
| Status.OutOfDateBuildInfo
4042
| Status.UpstreamOutOfDate
4143
| Status.UpstreamBlocked
4244
| Status.ComputingUpstream
@@ -102,6 +104,14 @@ namespace ts {
102104
newerInputFileName: string;
103105
}
104106

107+
/**
108+
* Buildinfo indicates that build is out of date
109+
*/
110+
export interface OutOfDateBuildInfo {
111+
type: UpToDateStatusType.OutOfDateBuildInfo,
112+
buildInfoFile: string;
113+
}
114+
105115
/**
106116
* This project depends on an out-of-date project, so shouldn't be built yet
107117
*/

src/compiler/tsbuildPublic.ts

+96-36
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ namespace ts {
210210
originalWriteFile: CompilerHost["writeFile"] | undefined;
211211
originalReadFileWithCache: CompilerHost["readFile"];
212212
originalGetSourceFile: CompilerHost["getSourceFile"];
213+
buildInfoCache: ESMap<Path, BuildInfo | false>;
213214
}
214215

215216
interface FileWatcherWithModifiedTime {
@@ -282,7 +283,7 @@ namespace ts {
282283

283284
// State of the solution
284285
const baseCompilerOptions = getCompilerOptionsOfBuildOptions(options);
285-
const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions);
286+
const compilerHost = createCompilerHostFromProgramHost(host, () => state.projectCompilerOptions) as CompilerHost & ReadBuildProgramHost;
286287
setGetSourceFileAsHashVersioned(compilerHost, host);
287288
compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName));
288289
compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames);
@@ -300,6 +301,7 @@ namespace ts {
300301
compilerHost.resolveTypeReferenceDirectives = (typeReferenceDirectiveNames, containingFile, redirectedReference, _options, containingFileMode) =>
301302
loadWithTypeDirectiveCache<ResolvedTypeReferenceDirective>(Debug.checkEachDefined(typeReferenceDirectiveNames), containingFile, redirectedReference, containingFileMode, loader);
302303
}
304+
compilerHost.getBuildInfo = fileName => getBuildInfo(state, fileName);
303305

304306
const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options);
305307

@@ -573,6 +575,7 @@ namespace ts {
573575
originalWriteFile,
574576
originalReadFileWithCache,
575577
originalGetSourceFile,
578+
buildInfoCache: new Map(),
576579
};
577580
}
578581

@@ -986,7 +989,9 @@ namespace ts {
986989
}
987990
}
988991

989-
emittedOutputs.set(toPath(state, name), name);
992+
const path = toPath(state, name);
993+
emittedOutputs.set(path, name);
994+
state.cache?.buildInfoCache.delete(path);
990995
writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
991996
});
992997

@@ -1003,7 +1008,12 @@ namespace ts {
10031008
function emitBuildInfo(writeFileCallback?: WriteFileCallback, cancellationToken?: CancellationToken): EmitResult {
10041009
Debug.assertIsDefined(program);
10051010
Debug.assert(step === BuildStep.EmitBuildInfo);
1006-
const emitResult = program.emitBuildInfo(writeFileCallback, cancellationToken);
1011+
const emitResult = program.emitBuildInfo((name, data, writeByteOrderMark, onError, sourceFiles) => {
1012+
const path = toPath(state, name);
1013+
state.cache?.buildInfoCache.delete(path);
1014+
if (writeFileCallback) writeFileCallback(name, data, writeByteOrderMark, onError, sourceFiles);
1015+
else state.compilerHost.writeFile(name, data, writeByteOrderMark, onError, sourceFiles);
1016+
}, cancellationToken);
10071017
if (emitResult.diagnostics.length) {
10081018
reportErrors(state, emitResult.diagnostics);
10091019
state.diagnostics.set(projectPath, [...state.diagnostics.get(projectPath)!, ...emitResult.diagnostics]);
@@ -1101,7 +1111,9 @@ namespace ts {
11011111
const emitterDiagnostics = createDiagnosticCollection();
11021112
const emittedOutputs = new Map<Path, string>();
11031113
outputFiles.forEach(({ name, text, writeByteOrderMark }) => {
1104-
emittedOutputs.set(toPath(state, name), name);
1114+
const path = toPath(state, name);
1115+
emittedOutputs.set(path, name);
1116+
state.cache?.buildInfoCache.delete(path);
11051117
writeFile(writeFileCallback ? { writeFile: writeFileCallback } : compilerHost, emitterDiagnostics, name, text, writeByteOrderMark);
11061118
});
11071119

@@ -1390,6 +1402,16 @@ namespace ts {
13901402
};
13911403
}
13921404

1405+
function getBuildInfo(state: SolutionBuilderState, buildInfoPath: string): BuildInfo | undefined {
1406+
const path = toPath(state, buildInfoPath);
1407+
const existing = state.cache?.buildInfoCache.get(path);
1408+
if (existing !== undefined) return existing || undefined;
1409+
const value = state.readFileWithCache(buildInfoPath);
1410+
const buildInfo = value ? ts.getBuildInfo(value) : undefined;
1411+
state.cache?.buildInfoCache.set(path, buildInfo || false);
1412+
return buildInfo;
1413+
}
1414+
13931415
function checkConfigFileUpToDateStatus(state: SolutionBuilderState, configFile: string, oldestOutputFileTime: Date, oldestOutputFileName: string): Status.OutOfDateWithSelf | undefined {
13941416
// Check tsconfig time
13951417
const tsconfigTime = getModifiedTime(state, configFile);
@@ -1472,43 +1494,89 @@ namespace ts {
14721494

14731495
if (force) return { type: UpToDateStatusType.ForceBuild };
14741496

1475-
// Collect the expected outputs of this project
1476-
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
1477-
1497+
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options);
14781498
// Now see if all outputs are newer than the newest input
14791499
let oldestOutputFileName = "(none)";
14801500
let oldestOutputFileTime = maximumDate;
14811501
let newestDeclarationFileContentChangedTime;
1482-
for (const output of outputs) {
1483-
// Output is missing; can stop checking
1484-
const outputTime = ts.getModifiedTime(host, output);
1502+
if (buildInfoPath) {
1503+
const outputTime = ts.getModifiedTime(host, buildInfoPath);
14851504
if (outputTime === missingFileModifiedTime) {
14861505
return {
14871506
type: UpToDateStatusType.OutputMissing,
1488-
missingOutputFileName: output
1507+
missingOutputFileName: buildInfoPath
14891508
};
14901509
}
14911510

1492-
if (outputTime < oldestOutputFileTime) {
1493-
oldestOutputFileTime = outputTime;
1494-
oldestOutputFileName = output;
1511+
const buildInfo = Debug.checkDefined(getBuildInfo(state, buildInfoPath));
1512+
if (!state.buildInfoChecked.has(resolvedPath)) {
1513+
state.buildInfoChecked.set(resolvedPath, true);
1514+
if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) {
1515+
return {
1516+
type: UpToDateStatusType.TsVersionOutputOfDate,
1517+
version: buildInfo.version
1518+
};
1519+
}
14951520
}
14961521

14971522
// If an output is older than the newest input, we can stop checking
14981523
if (outputTime < newestInputFileTime) {
14991524
return {
15001525
type: UpToDateStatusType.OutOfDateWithSelf,
1501-
outOfDateOutputFileName: oldestOutputFileName,
1526+
outOfDateOutputFileName: buildInfoPath,
15021527
newerInputFileName: newestInputFileName
15031528
};
15041529
}
15051530

1506-
// Keep track of when the most recent time a .d.ts file was changed.
1507-
// In addition to file timestamps, we also keep track of when a .d.ts file
1508-
// had its file touched but not had its contents changed - this allows us
1509-
// to skip a downstream typecheck
1510-
if (isDeclarationFileName(output)) {
1511-
newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputTime);
1531+
if (buildInfo.program) {
1532+
if (buildInfo.program.hasPendingChange ||
1533+
(!buildInfo.program.options?.noEmit && buildInfo.program.affectedFilesPendingEmit?.length)) {
1534+
return {
1535+
type: UpToDateStatusType.OutOfDateBuildInfo,
1536+
buildInfoFile: buildInfoPath
1537+
};
1538+
}
1539+
}
1540+
1541+
oldestOutputFileTime = outputTime;
1542+
oldestOutputFileName = buildInfoPath;
1543+
}
1544+
// Dont check output timestamps if we have buildinfo telling us output is uptodate
1545+
else {
1546+
// Collect the expected outputs of this project
1547+
const outputs = getAllProjectOutputs(project, !host.useCaseSensitiveFileNames());
1548+
for (const output of outputs) {
1549+
if (buildInfoPath === output) continue;
1550+
// Output is missing; can stop checking
1551+
const outputTime = ts.getModifiedTime(state.host, output);
1552+
if (outputTime === missingFileModifiedTime) {
1553+
return {
1554+
type: UpToDateStatusType.OutputMissing,
1555+
missingOutputFileName: output
1556+
};
1557+
}
1558+
1559+
if (outputTime < oldestOutputFileTime) {
1560+
oldestOutputFileTime = outputTime;
1561+
oldestOutputFileName = output;
1562+
}
1563+
1564+
// If an output is older than the newest input, we can stop checking
1565+
if (outputTime < newestInputFileTime) {
1566+
return {
1567+
type: UpToDateStatusType.OutOfDateWithSelf,
1568+
outOfDateOutputFileName: oldestOutputFileName,
1569+
newerInputFileName: newestInputFileName
1570+
};
1571+
}
1572+
1573+
// Keep track of when the most recent time a .d.ts file was changed.
1574+
// In addition to file timestamps, we also keep track of when a .d.ts file
1575+
// had its file touched but not had its contents changed - this allows us
1576+
// to skip a downstream typecheck
1577+
if (isDeclarationFileName(output)) {
1578+
newestDeclarationFileContentChangedTime = newer(newestDeclarationFileContentChangedTime, outputTime);
1579+
}
15121580
}
15131581
}
15141582

@@ -1557,21 +1625,6 @@ namespace ts {
15571625
);
15581626
if (dependentPackageFileStatus) return dependentPackageFileStatus;
15591627

1560-
if (!state.buildInfoChecked.has(resolvedPath)) {
1561-
state.buildInfoChecked.set(resolvedPath, true);
1562-
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(project.options);
1563-
if (buildInfoPath) {
1564-
const value = state.readFileWithCache(buildInfoPath);
1565-
const buildInfo = value && getBuildInfo(value);
1566-
if (buildInfo && (buildInfo.bundle || buildInfo.program) && buildInfo.version !== version) {
1567-
return {
1568-
type: UpToDateStatusType.TsVersionOutputOfDate,
1569-
version: buildInfo.version
1570-
};
1571-
}
1572-
}
1573-
}
1574-
15751628
if (usesPrepend && pseudoUpToDate) {
15761629
return {
15771630
type: UpToDateStatusType.OutOfDateWithPrepend,
@@ -2103,6 +2156,13 @@ namespace ts {
21032156
relName(state, configFileName),
21042157
relName(state, status.missingOutputFileName)
21052158
);
2159+
case UpToDateStatusType.OutOfDateBuildInfo:
2160+
return reportStatus(
2161+
state,
2162+
Diagnostics.Project_0_is_out_of_date_because_buildinfo_file_1_indicates_that_some_of_the_changes_are_not_emitted,
2163+
relName(state, configFileName),
2164+
relName(state, status.buildInfoFile)
2165+
);
21062166
case UpToDateStatusType.UpToDate:
21072167
if (status.newestInputFileTime !== undefined) {
21082168
return reportStatus(

src/compiler/watchPublic.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,23 @@ namespace ts {
33
useCaseSensitiveFileNames(): boolean;
44
getCurrentDirectory(): string;
55
readFile(fileName: string): string | undefined;
6+
/*@internal*/
7+
getBuildInfo?(fileName: string): BuildInfo | undefined;
68
}
79
export function readBuilderProgram(compilerOptions: CompilerOptions, host: ReadBuildProgramHost) {
810
if (outFile(compilerOptions)) return undefined;
911
const buildInfoPath = getTsBuildInfoEmitOutputFilePath(compilerOptions);
1012
if (!buildInfoPath) return undefined;
11-
const content = host.readFile(buildInfoPath);
12-
if (!content) return undefined;
13-
const buildInfo = getBuildInfo(content);
13+
let buildInfo;
14+
if (host.getBuildInfo) {
15+
buildInfo = host.getBuildInfo(buildInfoPath);
16+
if (!buildInfo) return undefined;
17+
}
18+
else {
19+
const content = host.readFile(buildInfoPath);
20+
if (!content) return undefined;
21+
buildInfo = getBuildInfo(content);
22+
}
1423
if (buildInfo.version !== version) return undefined;
1524
if (!buildInfo.program) return undefined;
1625
return createBuildProgramUsingProgramBuildInfo(buildInfo.program, buildInfoPath, host);

src/testRunner/unittests/tsbuild/helpers.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,7 @@ interface Symbol {
207207
exportedModulesMap?: MapLike<string[]>;
208208
semanticDiagnosticsPerFile?: readonly ReadableProgramBuildInfoDiagnostic[];
209209
affectedFilesPendingEmit?: readonly ReadableProgramBuilderInfoFilePendingEmit[];
210+
hasPendingChange?: boolean
210211
}
211212
type ReadableBuildInfo = Omit<BuildInfo, "program"> & { program: ReadableProgramBuildInfo | undefined; size: number; };
212213
function generateBuildInfoProgramBaseline(sys: System, buildInfoPath: string, buildInfo: BuildInfo) {
@@ -231,6 +232,7 @@ interface Symbol {
231232
emitKind === BuilderFileEmit.Full ? "Full" :
232233
Debug.assertNever(emitKind)
233234
]),
235+
hasPendingChange: buildInfo.program.hasPendingChange,
234236
};
235237
const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version;
236238
const result: ReadableBuildInfo = {
@@ -480,8 +482,7 @@ interface Symbol {
480482
fileNames: undefined,
481483
fileNamesList: undefined,
482484
fileInfos: sanitizedFileInfos,
483-
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
484-
options: { ...readableBuildInfo.program.options, noEmit: undefined },
485+
options: sanatizeCompilerOptions(readableBuildInfo.program.options),
485486
exportedModulesMap: undefined,
486487
affectedFilesPendingEmit: undefined,
487488
},
@@ -491,6 +492,16 @@ interface Symbol {
491492
};
492493
}
493494

495+
function sanatizeCompilerOptions(options: CompilerOptions | undefined) {
496+
if (!options) return options;
497+
let result: CompilerOptions | undefined;
498+
for (const name of getOwnKeys(options).sort(compareStringsCaseSensitive)) {
499+
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
500+
if (name.toLowerCase() !== "noemit") (result ||= {})[name] = options[name];
501+
}
502+
return result;
503+
}
504+
494505
export enum CleanBuildDescrepancy {
495506
CleanFileTextDifferent,
496507
CleanFilePresent,

src/testRunner/unittests/tsbuild/sample.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -285,12 +285,13 @@ namespace ts {
285285
});
286286

287287
describe("downstream-blocked compilations", () => {
288-
verifyTsc({
288+
verifyTscWithEdits({
289289
scenario: "sample1",
290290
subScenario: "does not build downstream projects if upstream projects have errors",
291291
fs: () => projFs,
292292
commandLineArgs: ["--b", "/src/tests", "--verbose"],
293-
modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`)
293+
modifyFs: fs => replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`),
294+
edits: noChangeOnlyRuns
294295
});
295296
});
296297

0 commit comments

Comments
 (0)