Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache results for readFile, fileExists, directory exists, sourceFiles for .d.ts files across the build (only first time) #28629

Merged
merged 4 commits into from
Nov 21, 2018
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
125 changes: 119 additions & 6 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ namespace ts {
// TODO(shkamat): update this after reworking ts build API
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
const existingDirectories = createMap<boolean>();

function getCanonicalFileName(fileName: string): string {
// if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form.
// otherwise use toLowerCase as a canonical form.
Expand All @@ -84,7 +83,7 @@ namespace ts {
let text: string | undefined;
try {
performance.mark("beforeIORead");
text = system.readFile(fileName, options.charset);
text = compilerHost.readFile(fileName);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
Expand Down Expand Up @@ -113,7 +112,12 @@ namespace ts {
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
const parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
system.createDirectory(directoryPath);
if (compilerHost.createDirectory) {
compilerHost.createDirectory(directoryPath);
}
else {
system.createDirectory(directoryPath);
}
}
}

Expand Down Expand Up @@ -177,8 +181,7 @@ namespace ts {

const newLine = getNewLineCharacter(options, () => system.newLine);
const realpath = system.realpath && ((path: string) => system.realpath!(path));

return {
const compilerHost: CompilerHost = {
getSourceFile,
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
Expand All @@ -194,7 +197,117 @@ namespace ts {
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => system.getDirectories(path),
realpath,
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth)
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth),
createDirectory: d => system.createDirectory(d)
};
return compilerHost;
}

/*@internal*/
export function changeCompilerHostToUseCache(
host: CompilerHost,
toPath: (fileName: string) => Path,
useCacheForSourceFile: boolean
) {
const originalReadFile = host.readFile;
sheetalkamat marked this conversation as resolved.
Show resolved Hide resolved
const originalFileExists = host.fileExists;
const originalDirectoryExists = host.directoryExists;
const originalCreateDirectory = host.createDirectory;
const originalWriteFile = host.writeFile;
const originalGetSourceFile = host.getSourceFile;
const readFileCache = createMap<string | false>();
const fileExistsCache = createMap<boolean>();
const directoryExistsCache = createMap<boolean>();
const sourceFileCache = createMap<SourceFile>();

const readFileWithCache = (fileName: string): string | undefined => {
const key = toPath(fileName);
const value = readFileCache.get(key);
if (value !== undefined) return value || undefined;
return setReadFileCache(key, fileName);
};
const setReadFileCache = (key: Path, fileName: string) => {
const newValue = originalReadFile.call(host, fileName);
readFileCache.set(key, newValue || false);
return newValue;
};
host.readFile = fileName => {
sheetalkamat marked this conversation as resolved.
Show resolved Hide resolved
const key = toPath(fileName);
const value = readFileCache.get(key);
if (value !== undefined) return value; // could be .d.ts from output
if (!fileExtensionIs(fileName, Extension.Json)) {
return originalReadFile.call(host, fileName);
}

return setReadFileCache(key, fileName);
};

if (useCacheForSourceFile) {
host.getSourceFile = (fileName, languageVersion, onError, shouldCreateNewSourceFile) => {
const key = toPath(fileName);
const value = sourceFileCache.get(key);
if (value) return value;

const sourceFile = originalGetSourceFile.call(host, fileName, languageVersion, onError, shouldCreateNewSourceFile);
if (sourceFile && (isDeclarationFileName(fileName) || fileExtensionIs(fileName, Extension.Json))) {
sourceFileCache.set(key, sourceFile);
}
return sourceFile;
};
}

// fileExists for any kind of extension
host.fileExists = fileName => {
const key = toPath(fileName);
const value = fileExistsCache.get(key);
if (value !== undefined) return value;
const newValue = originalFileExists.call(host, fileName);
fileExistsCache.set(key, !!newValue);
return newValue;
};
host.writeFile = (fileName, data, writeByteOrderMark, onError, sourceFiles) => {
const key = toPath(fileName);
fileExistsCache.delete(key);

const value = readFileCache.get(key);
if (value && value !== data) {
readFileCache.delete(key);
sourceFileCache.delete(key);
}
else if (useCacheForSourceFile) {
const sourceFile = sourceFileCache.get(key);
if (sourceFile && sourceFile.text !== data) {
sourceFileCache.delete(key);
}
}
originalWriteFile.call(host, fileName, data, writeByteOrderMark, onError, sourceFiles);
};

// directoryExists
if (originalDirectoryExists && originalCreateDirectory) {
host.directoryExists = directory => {
const key = toPath(directory);
const value = directoryExistsCache.get(key);
if (value !== undefined) return value;
const newValue = originalDirectoryExists.call(host, directory);
directoryExistsCache.set(key, !!newValue);
return newValue;
};
host.createDirectory = directory => {
const key = toPath(directory);
directoryExistsCache.delete(key);
originalCreateDirectory.call(host, directory);
};
}

return {
originalReadFile,
originalFileExists,
originalDirectoryExists,
originalCreateDirectory,
originalWriteFile,
originalGetSourceFile,
readFileWithCache
};
}

Expand Down
19 changes: 18 additions & 1 deletion src/compiler/tsbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ namespace ts {
const missingRoots = createMap<true>();
let globalDependencyGraph: DependencyGraph | undefined;
const writeFileName = (s: string) => host.trace && host.trace(s);
let readFileWithCache = (f: string) => host.readFile(f);

// Watch state
const diagnostics = createFileMap<ReadonlyArray<Diagnostic>>(toPath);
Expand Down Expand Up @@ -1072,7 +1073,7 @@ namespace ts {
let priorChangeTime: Date | undefined;
if (!anyDtsChanged && isDeclarationFile(fileName)) {
// Check for unchanged .d.ts files
if (host.fileExists(fileName) && host.readFile(fileName) === content) {
if (host.fileExists(fileName) && readFileWithCache(fileName) === content) {
priorChangeTime = host.getModifiedTime(fileName);
}
else {
Expand Down Expand Up @@ -1182,6 +1183,15 @@ namespace ts {

function buildAllProjects(): ExitStatus {
if (options.watch) { reportWatchStatus(Diagnostics.Starting_compilation_in_watch_mode); }
// TODO:: In watch mode as well to use caches for incremental build once we can invalidate caches correctly and have right api
// Override readFile for json files and output .d.ts to cache the text
const { originalReadFile, originalFileExists, originalDirectoryExists,
originalCreateDirectory, originalWriteFile, originalGetSourceFile,
readFileWithCache: newReadFileWithCache
} = changeCompilerHostToUseCache(host, toPath, /*useCacheForSourceFile*/ true);
const savedReadFileWithCache = readFileWithCache;
readFileWithCache = newReadFileWithCache;

const graph = getGlobalDependencyGraph();
reportBuildQueue(graph);
let anyFailed = false;
Expand Down Expand Up @@ -1232,6 +1242,13 @@ namespace ts {
anyFailed = anyFailed || !!(buildResult & BuildResultFlags.AnyErrors);
}
reportErrorSummary();
host.readFile = originalReadFile;
host.fileExists = originalFileExists;
host.directoryExists = originalDirectoryExists;
host.createDirectory = originalCreateDirectory;
host.writeFile = originalWriteFile;
readFileWithCache = savedReadFileWithCache;
host.getSourceFile = originalGetSourceFile;
return anyFailed ? ExitStatus.DiagnosticsPresent_OutputsSkipped : ExitStatus.Success;
}

Expand Down
3 changes: 3 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5014,6 +5014,9 @@ namespace ts {
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;

// TODO: later handle this in better way in builder host instead once the api for tsbuild finalizes and doesnt use compilerHost as base
/*@internal*/createDirectory?(directory: string): void;
}

/* @internal */
Expand Down
3 changes: 3 additions & 0 deletions src/tsc/tsc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,9 @@ namespace ts {

function performCompilation(rootNames: string[], projectReferences: ReadonlyArray<ProjectReference> | undefined, options: CompilerOptions, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
const host = createCompilerHost(options);
const currentDirectory = host.getCurrentDirectory();
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames());
changeCompilerHostToUseCache(host, fileName => toPath(fileName, currentDirectory, getCanonicalFileName), /*useCacheForSourceFile*/ false);
enableStatistics(options);

const programOptions: CreateProgramOptions = {
Expand Down