Remove project status, watches etc when project is no longer part of build order

This commit is contained in:
Sheetal Nandi 2019-07-26 14:06:33 -07:00
parent 4efcfb7120
commit 2db8a13d81
6 changed files with 186 additions and 7 deletions

View File

@ -564,8 +564,51 @@ namespace ts {
}
function getBuildOrder(state: SolutionBuilderState) {
return state.buildOrder ||
(state.buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f))));
return state.buildOrder || createStateBuildOrder(state);
}
function createStateBuildOrder(state: SolutionBuilderState) {
const buildOrder = createBuildOrder(state, state.rootNames.map(f => resolveProjectName(state, f)));
if (arrayIsEqualTo(state.buildOrder, buildOrder)) return state.buildOrder!;
// Clear all to ResolvedConfigFilePaths cache to start fresh
state.resolvedConfigFilePaths.clear();
const currentProjects = arrayToSet(
buildOrder,
resolved => toResolvedConfigFilePath(state, resolved)
) as ConfigFileMap<true>;
const noopOnDelete = { onDeleteValue: noop };
// Config file cache
mutateMapSkippingNewValues(state.configFileCache, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.projectStatus, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.buildInfoChecked, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.builderPrograms, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.diagnostics, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.projectPendingBuild, currentProjects, noopOnDelete);
mutateMapSkippingNewValues(state.projectErrorsReported, currentProjects, noopOnDelete);
// Remove watches for the program no longer in the solution
if (state.watch) {
mutateMapSkippingNewValues(
state.allWatchedConfigFiles,
currentProjects,
{ onDeleteValue: closeFileWatcher }
);
mutateMapSkippingNewValues(
state.allWatchedWildcardDirectories,
currentProjects,
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcherOf) }
);
mutateMapSkippingNewValues(
state.allWatchedInputFiles,
currentProjects,
{ onDeleteValue: existingMap => existingMap.forEach(closeFileWatcher) }
);
}
return state.buildOrder = buildOrder;
}
function getBuildOrderFor(state: SolutionBuilderState, project: string | undefined, onlyReferences: boolean | undefined) {

View File

@ -4466,8 +4466,7 @@ namespace ts {
map.clear();
}
export interface MutateMapOptions<T, U> {
createNewValue(key: string, valueInNewMap: U): T;
export interface MutateMapSkippingNewValuesOptions<T, U> {
onDeleteValue(existingValue: T, key: string): void;
/**
@ -4482,8 +4481,12 @@ namespace ts {
/**
* Mutates the map with newMap such that keys in map will be same as newMap.
*/
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
const { createNewValue, onDeleteValue, onExistingValue } = options;
export function mutateMapSkippingNewValues<T, U>(
map: Map<T>,
newMap: ReadonlyMap<U>,
options: MutateMapSkippingNewValuesOptions<T, U>
) {
const { onDeleteValue, onExistingValue } = options;
// Needs update
map.forEach((existingValue, key) => {
const valueInNewMap = newMap.get(key);
@ -4497,7 +4500,20 @@ namespace ts {
onExistingValue(existingValue, valueInNewMap, key);
}
});
}
export interface MutateMapOptions<T, U> extends MutateMapSkippingNewValuesOptions<T, U> {
createNewValue(key: string, valueInNewMap: U): T;
}
/**
* Mutates the map with newMap such that keys in map will be same as newMap.
*/
export function mutateMap<T, U>(map: Map<T>, newMap: ReadonlyMap<U>, options: MutateMapOptions<T, U>) {
// Needs update
mutateMapSkippingNewValues(map, newMap, options);
const { createNewValue } = options;
// Add new values that are not already present
newMap.forEach((valueInNewMap, key) => {
if (!map.has(key)) {

View File

@ -405,7 +405,7 @@ interface Array<T> {}`
return s;
}
private now() {
now() {
this.time += timeIncrements;
return new Date(this.time);
}

View File

@ -102,6 +102,7 @@
"unittests/tsbuild/resolveJsonModule.ts",
"unittests/tsbuild/sample.ts",
"unittests/tsbuild/transitiveReferences.ts",
"unittests/tsbuild/watchEnvironment.ts",
"unittests/tsbuild/watchMode.ts",
"unittests/tscWatch/consoleClearing.ts",
"unittests/tscWatch/emit.ts",

View File

@ -0,0 +1,111 @@
namespace ts.tscWatch {
describe("unittests:: tsbuild:: watchEnvironment:: tsbuild:: watchMode:: with different watch environments", () => {
it("watchFile on same file multiple times because file is part of multiple projects", () => {
const project = `${TestFSWithWatch.tsbuildProjectsLocation}/myproject`;
let maxPkgs = 4;
const configPath = `${project}/tsconfig.json`;
const typing: File = {
path: `${project}/typings/xterm.d.ts`,
content: "export const typing = 10;"
};
const allPkgFiles = pkgs(pkgFiles);
const system = createWatchedSystem([libFile, typing, ...flatArray(allPkgFiles)], { currentDirectory: project });
writePkgReferences();
const host = createSolutionBuilderWithWatchHost(system);
const solutionBuilder = createSolutionBuilderWithWatch(host, ["tsconfig.json"], { watch: true, verbose: true });
solutionBuilder.build();
checkOutputErrorsInitial(system, emptyArray, /*disableConsoleClears*/ undefined, [
`Projects in this build: \r\n${
concatenate(
pkgs(index => ` * pkg${index}/tsconfig.json`),
[" * tsconfig.json"]
).join("\r\n")}\n\n`,
...flatArray(pkgs(index => [
`Project 'pkg${index}/tsconfig.json' is out of date because output file 'pkg${index}/index.js' does not exist\n\n`,
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`
]))
]);
const watchFilesDetailed = arrayToMap(flatArray(allPkgFiles), f => f.path, () => 1);
watchFilesDetailed.set(configPath, 1);
watchFilesDetailed.set(typing.path, maxPkgs);
checkWatchedFilesDetailed(system, watchFilesDetailed);
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
verifyInvoke();
// Make change
maxPkgs--;
writePkgReferences();
system.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(system, emptyArray);
const lastFiles = last(allPkgFiles);
lastFiles.forEach(f => watchFilesDetailed.delete(f.path));
watchFilesDetailed.set(typing.path, maxPkgs);
checkWatchedFilesDetailed(system, watchFilesDetailed);
system.writeFile(typing.path, typing.content);
verifyInvoke();
// Make change to remove all the watches
maxPkgs = 0;
writePkgReferences();
system.checkTimeoutQueueLengthAndRun(1);
checkOutputErrorsIncremental(system, [
`tsconfig.json(1,10): error TS18002: The 'files' list in config file '${configPath}' is empty.\n`
]);
checkWatchedFilesDetailed(system, [configPath], 1);
system.writeFile(typing.path, `${typing.content}export const typing1 = 10;`);
system.checkTimeoutQueueLength(0);
function flatArray<T>(arr: T[][]): readonly T[] {
return flatMap(arr, identity);
}
function pkgs<T>(cb: (index: number) => T): T[] {
const result: T[] = [];
for (let index = 0; index < maxPkgs; index++) {
result.push(cb(index));
}
return result;
}
function createPkgReference(index: number) {
return { path: `./pkg${index}` };
}
function pkgFiles(index: number): File[] {
return [
{
path: `${project}/pkg${index}/index.ts`,
content: `export const pkg${index} = ${index};`
},
{
path: `${project}/pkg${index}/tsconfig.json`,
content: JSON.stringify({
complerOptions: { composite: true },
include: [
"**/*.ts",
"../typings/xterm.d.ts"
]
})
}
];
}
function writePkgReferences() {
system.writeFile(configPath, JSON.stringify({
files: [],
include: [],
references: pkgs(createPkgReference)
}));
}
function verifyInvoke() {
pkgs(() => system.checkTimeoutQueueLengthAndRun(1));
checkOutputErrorsIncremental(system, emptyArray, /*disableConsoleClears*/ undefined, /*logsBeforeWatchDiagnostics*/ undefined, [
...flatArray(pkgs(index => [
`Project 'pkg${index}/tsconfig.json' is out of date because oldest output 'pkg${index}/index.js' is older than newest input 'typings/xterm.d.ts'\n\n`,
`Building project '${project}/pkg${index}/tsconfig.json'...\n\n`,
`Updating unchanged output timestamps of project '${project}/pkg${index}/tsconfig.json'...\n\n`
]))
]);
}
});
});
}

View File

@ -16,11 +16,19 @@ namespace ts.tscWatch {
return host;
}
export function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderHost(system);
host.now = system.now.bind(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || {});
}
export function createSolutionBuilderWithWatchHost(system: WatchedSystem) {
const host = ts.createSolutionBuilderWithWatchHost(system);
host.now = system.now.bind(system);
return host;
}
function createSolutionBuilderWithWatch(system: TsBuildWatchSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderWithWatchHost(system);
const solutionBuilder = ts.createSolutionBuilderWithWatch(host, rootNames, defaultOptions || { watch: true });