From 7f1050e7bda10393a4278c96de1f7930b89632ab Mon Sep 17 00:00:00 2001 From: Yiftah Waisman Date: Sun, 20 Oct 2024 22:40:45 +0300 Subject: [PATCH 01/10] update json schema, add getter --- Extension/package.json | 32 ++++++++++++++++++++++++ Extension/package.nls.json | 18 +++++++++++++ Extension/src/LanguageServer/settings.ts | 14 +++++++++++ 3 files changed, 64 insertions(+) diff --git a/Extension/package.json b/Extension/package.json index d147a3cd35..2fbbcbb167 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -957,6 +957,38 @@ ] }, "scope": "resource" + }, + "C_Cpp.mergeCompileCommands": { + "type": [ + "object", + "null" + ], + "markdownDescription": "%c_cpp.configuration.mergeCompileCommands.markdownDescription%", + "scope": "resource", + "default": { + "sources": [], + "destination": "" + }, + "required": [ + "sources", + "destination" + ], + "properties": { + "sources": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "markdownDescription": "%c_cpp.configuration.mergeCompileCommands.sources.markdownDescription%", + "default": [] + }, + "destination": { + "type": "string", + "markdownDescription": "%c_cpp.configuration.mergeCompileCommands.destination.markdownDescription%", + "default": "" + } + } } } }, diff --git a/Extension/package.nls.json b/Extension/package.nls.json index 7f784f7c99..c174befcb0 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -724,6 +724,24 @@ "Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered." ] }, + "c_cpp.configuration.mergeCompileCommands.markdownDescription": { + "message": "Collect and merge all `compile_commands.json` listed in `sources` and save to `destination`", + "comment": [ + "Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered." + ] + }, + "c_cpp.configuration.mergeCompileCommands.sources.markdownDescription": { + "message": "List of `compile_commands.json` files to merge. (glob patterns are not supported)", + "comment": [ + "Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered." + ] + }, + "c_cpp.configuration.mergeCompileCommands.destination.markdownDescription": { + "message": "The destination file to save the merged `compile_commands.json` to.", + "comment": [ + "Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered." + ] + }, "c_cpp.configuration.updateChannel.deprecationMessage": "This setting is deprecated. Pre-release extensions are now available via the Marketplace.", "c_cpp.configuration.default.dotConfig.markdownDescription": { "message": "The value to use in a configuration if `dotConfig` is not specified, or the value to insert if `${default}` is present in `dotConfig`.", diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 8e8d6c0654..6016ae1516 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -33,6 +33,11 @@ export interface Associations { [key: string]: string; } +export interface MergeCompileCommands { + sources: string[]; + destination: string; +} + // Settings that can be undefined have default values assigned in the native code or are meant to return undefined. export interface WorkspaceFolderSettingsParams { uri: string | undefined; @@ -403,6 +408,7 @@ export class CppSettings extends Settings { public get defaultForcedInclude(): string[] | undefined { return this.getArrayOfStringsWithUndefinedDefault("default.forcedInclude"); } public get defaultIntelliSenseMode(): string | undefined { return this.getAsStringOrUndefined("default.intelliSenseMode"); } public get defaultCompilerPath(): string | null { return this.getAsString("default.compilerPath", true); } + public get mergeCompileCommands(): MergeCompileCommands | undefined { return this.getMergeCompileCommands(); } public set defaultCompilerPath(value: string) { const defaultCompilerPathStr: string = "default.compilerPath"; @@ -703,6 +709,14 @@ export class CppSettings extends Settings { return setting.default as Associations; } + private getMergeCompileCommands(): MergeCompileCommands | undefined { + const value: any = super.Section.get("mergeCompileCommands"); + //const setting = getRawSetting("C_Cpp.mergeCompileCommands", true); + // todo: add some validation here + + return value as MergeCompileCommands; + } + // Checks a given enum value against a list of valid enum values from package.json. private isValidEnum(enumDescription: any, value: any): value is string { if (isString(value) && isArray(enumDescription) && enumDescription.length > 0) { From 9a95b0242107819c8e18190ce11707f5faa339c2 Mon Sep 17 00:00:00 2001 From: Yiftah Waisman Date: Mon, 21 Oct 2024 09:30:08 +0300 Subject: [PATCH 02/10] fixes to compile after vscode lmTools API break --- .gitignore | 3 +++ Extension/src/LanguageServer/lmTool.ts | 9 +++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index d2a8df0e94..01e1699399 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ OneLocBuild # ignore imported localization xlf directory vscode-translations-import + +# todo remove +Extension/bin/libc.so diff --git a/Extension/src/LanguageServer/lmTool.ts b/Extension/src/LanguageServer/lmTool.ts index 5951377b4e..a4461a2eed 100644 --- a/Extension/src/LanguageServer/lmTool.ts +++ b/Extension/src/LanguageServer/lmTool.ts @@ -43,15 +43,12 @@ const knownValues: { [Property in keyof ChatContextResult]?: { [id: string]: str 'macos': 'macOS' } }; - -const plainTextContentType = 'text/plain'; +// todo revert changes before pull request export class CppConfigurationLanguageModelTool implements vscode.LanguageModelTool { public async invoke(options: vscode.LanguageModelToolInvocationOptions, token: vscode.CancellationToken): Promise { - const result: vscode.LanguageModelToolResult = {}; - if (options.requestedContentTypes.includes(plainTextContentType)) { - result[plainTextContentType] = await this.getContext(token); - } + const result: vscode.LanguageModelToolResult = { "content": [] }; + result.content.push(await this.getContext(token)); return result; } From 63bb90c80204e718e6d85b33ac40a00060ef5e55 Mon Sep 17 00:00:00 2001 From: Yiftah Waisman Date: Wed, 23 Oct 2024 10:55:31 +0300 Subject: [PATCH 03/10] change scope level --- Extension/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Extension/package.json b/Extension/package.json index f5eb73a424..76eb52a704 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -963,7 +963,7 @@ "null" ], "markdownDescription": "%c_cpp.configuration.mergeCompileCommands.markdownDescription%", - "scope": "resource", + "scope": "machine-overridable", "default": { "sources": [], "destination": "" From 13e3d1129caf1a17aae882a95aa6303b1c5c0752 Mon Sep 17 00:00:00 2001 From: Yiftah Waisman Date: Mon, 28 Oct 2024 19:00:43 +0200 Subject: [PATCH 04/10] wip not working --- Extension/src/LanguageServer/client.ts | 1 + .../src/LanguageServer/configurations.ts | 150 +++++++++++++++++- 2 files changed, 150 insertions(+), 1 deletion(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index e46743815e..a6a9cda790 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -3933,6 +3933,7 @@ export class DefaultClient implements Client { if (this.innerLanguageClient !== undefined && this.configuration !== undefined) { void this.languageClient.sendNotification(IntervalTimerNotification).catch(logAndReturn.undefined); this.configuration.checkCppProperties(); + this.configuration.checkMergeCompileCommands(); this.configuration.checkCompileCommands(); } } diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 3a02622b0a..1eb11a1aeb 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -21,7 +21,7 @@ import * as telemetry from '../telemetry'; import { DefaultClient } from './client'; import { CustomConfigurationProviderCollection, getCustomConfigProviders } from './customProviders'; import { PersistentFolderState } from './persistentState'; -import { CppSettings, OtherSettings } from './settings'; +import { CppSettings, OtherSettings, MergeCompileCommands } from './settings'; import { SettingsPanel } from './settingsPanel'; import { ConfigurationType, getUI } from './ui'; import escapeStringRegExp = require('escape-string-regexp'); @@ -33,6 +33,14 @@ const configVersion: number = 4; type Environment = { [key: string]: string | string[] }; +interface CompileCommand { + directory: string; + file: string; + output?: string; + command: string; // The command string includes both commands and arguments (if any). + arguments?: string[]; +} + // No properties are set in the config since we want to apply vscode settings first (if applicable). // That code won't trigger if another value is already set. // The property defaults are moved down to applyDefaultIncludePathsAndFrameworks. @@ -160,6 +168,10 @@ export class CppProperties { private diagnosticCollection: vscode.DiagnosticCollection; private prevSquiggleMetrics: Map = new Map(); private settingsPanel?: SettingsPanel; + private mergeCompileCommandsSourceFiles: Set = new Set(); + private mergeCompileCommands?: MergeCompileCommands; + private mergeCompileCommandsFileWatchers: fs.FSWatcher[] = []; + private mergeCompileCommandsFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails. // Any time the default settings are parsed and assigned to `this.configurationJson`, // we want to track when the default includes have been added to it. @@ -906,6 +918,65 @@ export class CppProperties { return this.configProviderAutoSelected; } + private resolveMergeCompileCommandsPaths(): void { + if (this.mergeCompileCommands) { + this.mergeCompileCommands.sources?.forEach(path => { this.resolvePath(path); }); + this.mergeCompileCommands.sources = this.mergeCompileCommands.sources?.filter(path => fs.existsSync(path) && fs.statSync(path).isFile()); + this.mergeCompileCommands.destination = this.resolvePath(this.mergeCompileCommands.destination); + // TODO: check that destination is a valid path (not necessarily exists, just ends with 'compile_commands.json') + } + } + + private onMergeCompileCommandsFiles(): void { + // if we got here, it is expected that resolveMergeCompileCommandsPaths() was called + // so, any file path in mergeCompileCommands.sources is a valid file path + // try to make the parent dir of the destination + if (!this.mergeCompileCommands || + this.mergeCompileCommands.destination.length === 0 || + this.mergeCompileCommands.sources.length === 0) + { + return; + } + var dst = this.mergeCompileCommands.destination; + const dst_dir = path.dirname(dst); + try { + fs.mkdirSync(dst_dir, {recursive : true}); + } + catch (err: any) { + const failedToCreate: string = localize("failed.to.create.config.folder", 'Failed to create "{0}"', dst_dir); + void vscode.window.showErrorMessage(`${failedToCreate}: ${err.message}`); + return; + } + if (fs.existsSync(dst) && fs.statSync(dst).isDirectory()) { + dst = path.join(dst, "merged_compile_commands.json"); + } + + // merge all the json files + const mergedCompiledCommands: CompileCommand[] = []; + this.mergeCompileCommands.sources.forEach(src => { + try { + const fileData = fs.readFileSync(src); + const fileCommands = JSON.parse(fileData.toString()) as CompileCommand[]; + mergedCompiledCommands.push(...fileCommands); + } + catch (err: any) { + const failedToRead: string = localize("failed.to.read.compile.commands", 'Failed to read "{0}"', src); + void vscode.window.showErrorMessage(`${failedToRead}: ${err.message}`); + return; + } + }); + + // try to save to the dst file + try { + const output = JSON.stringify(mergedCompiledCommands, null, 4); + fs.writeFileSync(dst, output); + } + catch (e: any) { + const failedToWrite: string = localize("failed.to.write.compile.commands", 'Failed to write "{0}"', dst); + void vscode.window.showErrorMessage(`${failedToWrite}: ${e.message}`); + } + } + private updateServerOnFolderSettingsChange(): void { this.configProviderAutoSelected = false; if (!this.configurationJson) { @@ -1104,12 +1175,60 @@ export class CppProperties { } } + this.mergeCompileCommands = settings.mergeCompileCommands; + this.updateMergeCompileCommandsFileWatchers(); + this.updateCompileCommandsFileWatchers(); if (!this.configurationIncomplete) { this.onConfigurationsChanged(); } } + private mergeCompileCommandsFileWwatcherTimer?: NodeJS.Timeout; + + public updateMergeCompileCommandsFileWatchers(): void { + this.resolveMergeCompileCommandsPaths(); + this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); + this.mergeCompileCommandsFileWatchers = []; // reset it + sources: + if (this.mergeCompileCommands && + this.mergeCompileCommands.sources && + this.mergeCompileCommands.sources.length > 0 && + this.mergeCompileCommands.destination) { + // if we are here, resolveMergeCompileCommands(): + // - filtered out non-existent paths from sources + // - destination path resolved + const filePaths: Set = new Set(); + this.mergeCompileCommands.sources.forEach((source: string) => { + filePaths.add(source); + }); + try { + filePaths.forEach((path: string) => { + this.mergeCompileCommandsFileWatchers.push(fs.watch(path, () => { + // on file changed: + // - clear the old timer if it exists + if (this.mergeCompileCommandsFileWwatcherTimer) { + clearInterval(this.mergeCompileCommandsFileWwatcherTimer); + } + // - set a new timer to wait 1 second before processing the changes + this.mergeCompileCommandsFileWwatcherTimer = setTimeout(() => { + // - merge all the compile_commands.json files even if only one changed + this.onMergeCompileCommandsFiles(); + // - clear the timer + if (this.mergeCompileCommandsFileWwatcherTimer) { + clearInterval(this.mergeCompileCommandsFileWwatcherTimer); + } + this.mergeCompileCommandsFileWwatcherTimer = undefined; + }, 1000); + })); + }); + } catch (e) { + // The file watcher limit is hit. + // TODO: Check if the compile commands file has a higher timestamp during the interval timer. + } + } + } + private compileCommandsFileWatcherTimer?: NodeJS.Timeout; private compileCommandsFileWatcherFiles: Set = new Set(); @@ -2322,6 +2441,32 @@ export class CppProperties { }); } + public checkMergeCompileCommands(): void { + // Check for changes in case of file watcher failure. + this.resolveMergeCompileCommandsPaths(); + const mergeCompileCommands: MergeCompileCommands | undefined = this.mergeCompileCommands; + if (!mergeCompileCommands) { + return; + } + // check if any of the sources changed since last time we manually checked + this.mergeCompileCommands?.sources.forEach((source) => { + fs.stat(source, (err, stats) => { + const uri = vscode.Uri.file(source); + if (err) { + if (err.code === "ENOENT" && this.mergeCompileCommandsSourceFiles.has(uri)) { + this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? + this.onMergeCompileCommandsFiles(); + this.mergeCompileCommandsSourceFiles.delete(uri); // File deleted + } + } else if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { + this.mergeCompileCommandsFileWatcherFallbackTime = new Date(); + this.onMergeCompileCommandsFiles(); + this.mergeCompileCommandsSourceFiles.add(uri); // File created. + } + }); + }); + } + dispose(): void { this.disposables.forEach((d) => d.dispose()); this.disposables = []; @@ -2329,6 +2474,9 @@ export class CppProperties { this.compileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); this.compileCommandsFileWatchers = []; // reset it + this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); + this.mergeCompileCommandsFileWatchers = []; // reset it + this.diagnosticCollection.dispose(); } } From 7bd9c04cc9a55237636ebc8f08df7400b85dd255 Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Tue, 29 Oct 2024 10:13:44 +0200 Subject: [PATCH 05/10] periodic checking working --- .../src/LanguageServer/configurations.ts | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 1eb11a1aeb..4a0dbfb191 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -21,7 +21,7 @@ import * as telemetry from '../telemetry'; import { DefaultClient } from './client'; import { CustomConfigurationProviderCollection, getCustomConfigProviders } from './customProviders'; import { PersistentFolderState } from './persistentState'; -import { CppSettings, OtherSettings, MergeCompileCommands } from './settings'; +import { CppSettings, MergeCompileCommands, OtherSettings } from './settings'; import { SettingsPanel } from './settingsPanel'; import { ConfigurationType, getUI } from './ui'; import escapeStringRegExp = require('escape-string-regexp'); @@ -918,29 +918,29 @@ export class CppProperties { return this.configProviderAutoSelected; } - private resolveMergeCompileCommandsPaths(): void { - if (this.mergeCompileCommands) { - this.mergeCompileCommands.sources?.forEach(path => { this.resolvePath(path); }); - this.mergeCompileCommands.sources = this.mergeCompileCommands.sources?.filter(path => fs.existsSync(path) && fs.statSync(path).isFile()); - this.mergeCompileCommands.destination = this.resolvePath(this.mergeCompileCommands.destination); - // TODO: check that destination is a valid path (not necessarily exists, just ends with 'compile_commands.json') + private resolveMergeCompileCommandsPaths(): MergeCompileCommands | undefined { + if (!this.mergeCompileCommands) { + return undefined; } + var result: MergeCompileCommands = { sources: [], destination: "" }; + this.mergeCompileCommands.sources.forEach(path => { result.sources.push(this.resolvePath(path)); }); + result.destination = this.resolvePath(this.mergeCompileCommands.destination); + return result; } private onMergeCompileCommandsFiles(): void { - // if we got here, it is expected that resolveMergeCompileCommandsPaths() was called - // so, any file path in mergeCompileCommands.sources is a valid file path - // try to make the parent dir of the destination - if (!this.mergeCompileCommands || - this.mergeCompileCommands.destination.length === 0 || - this.mergeCompileCommands.sources.length === 0) - { + console.log("trying to merge compile commands"); + const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); + if (mergeCompileCommands === undefined || + mergeCompileCommands.destination.length === 0 || + mergeCompileCommands.sources.length === 0) { return; } - var dst = this.mergeCompileCommands.destination; + + var dst = mergeCompileCommands.destination; const dst_dir = path.dirname(dst); try { - fs.mkdirSync(dst_dir, {recursive : true}); + fs.mkdirSync(dst_dir, { recursive: true }); } catch (err: any) { const failedToCreate: string = localize("failed.to.create.config.folder", 'Failed to create "{0}"', dst_dir); @@ -953,7 +953,7 @@ export class CppProperties { // merge all the json files const mergedCompiledCommands: CompileCommand[] = []; - this.mergeCompileCommands.sources.forEach(src => { + mergeCompileCommands.sources.forEach(src => { try { const fileData = fs.readFileSync(src); const fileCommands = JSON.parse(fileData.toString()) as CompileCommand[]; @@ -1190,7 +1190,6 @@ export class CppProperties { this.resolveMergeCompileCommandsPaths(); this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); this.mergeCompileCommandsFileWatchers = []; // reset it - sources: if (this.mergeCompileCommands && this.mergeCompileCommands.sources && this.mergeCompileCommands.sources.length > 0 && @@ -2443,22 +2442,31 @@ export class CppProperties { public checkMergeCompileCommands(): void { // Check for changes in case of file watcher failure. + console.log("manually checking merge compile commands"); this.resolveMergeCompileCommandsPaths(); const mergeCompileCommands: MergeCompileCommands | undefined = this.mergeCompileCommands; - if (!mergeCompileCommands) { + if (mergeCompileCommands == undefined) { + console.log("merge compile commands not found, returning"); return; } + // first, check if the destination file doesn't exist, + // if so, try to create it + if (!fs.existsSync(mergeCompileCommands.destination)) { + console.log("destination file not found, trying to create it"); + this.onMergeCompileCommandsFiles(); + } // check if any of the sources changed since last time we manually checked - this.mergeCompileCommands?.sources.forEach((source) => { + mergeCompileCommands.sources.forEach((source) => { + console.log("checking source file: ", source); fs.stat(source, (err, stats) => { const uri = vscode.Uri.file(source); - if (err) { - if (err.code === "ENOENT" && this.mergeCompileCommandsSourceFiles.has(uri)) { - this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? - this.onMergeCompileCommandsFiles(); - this.mergeCompileCommandsSourceFiles.delete(uri); // File deleted - } + if (err && err.code === "ENOENT" && this.mergeCompileCommandsSourceFiles.has(uri)) { + // source file previously existed, but deleted + this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? + this.onMergeCompileCommandsFiles(); + this.mergeCompileCommandsSourceFiles.delete(uri); // File deleted } else if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { + // source file changed since last time we manually checked this.mergeCompileCommandsFileWatcherFallbackTime = new Date(); this.onMergeCompileCommandsFiles(); this.mergeCompileCommandsSourceFiles.add(uri); // File created. From 3d17234d332cc9c6933c7cc463a7947c43a7f4e2 Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Tue, 29 Oct 2024 22:12:08 +0200 Subject: [PATCH 06/10] some fixes --- .../src/LanguageServer/configurations.ts | 61 +++++++++++-------- 1 file changed, 36 insertions(+), 25 deletions(-) diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 4a0dbfb191..7d4725b83d 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -168,10 +168,11 @@ export class CppProperties { private diagnosticCollection: vscode.DiagnosticCollection; private prevSquiggleMetrics: Map = new Map(); private settingsPanel?: SettingsPanel; - private mergeCompileCommandsSourceFiles: Set = new Set(); + private mergeCompileCommandsSourceFiles: Set = new Set(); private mergeCompileCommands?: MergeCompileCommands; private mergeCompileCommandsFileWatchers: fs.FSWatcher[] = []; private mergeCompileCommandsFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails. + private mergeCompileCommandsFileWwatcherTimer?: NodeJS.Timeout; // Any time the default settings are parsed and assigned to `this.configurationJson`, // we want to track when the default includes have been added to it. @@ -1184,26 +1185,24 @@ export class CppProperties { } } - private mergeCompileCommandsFileWwatcherTimer?: NodeJS.Timeout; public updateMergeCompileCommandsFileWatchers(): void { - this.resolveMergeCompileCommandsPaths(); this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); this.mergeCompileCommandsFileWatchers = []; // reset it - if (this.mergeCompileCommands && - this.mergeCompileCommands.sources && - this.mergeCompileCommands.sources.length > 0 && - this.mergeCompileCommands.destination) { - // if we are here, resolveMergeCompileCommands(): - // - filtered out non-existent paths from sources - // - destination path resolved + const mergeCompileCommands = this.resolveMergeCompileCommandsPaths(); + if (mergeCompileCommands && + mergeCompileCommands.sources && + mergeCompileCommands.sources.length > 0 && + mergeCompileCommands.destination) { + // if we are here, sources and destination are resolved const filePaths: Set = new Set(); - this.mergeCompileCommands.sources.forEach((source: string) => { + mergeCompileCommands.sources.forEach((source: string) => { filePaths.add(source); }); try { filePaths.forEach((path: string) => { this.mergeCompileCommandsFileWatchers.push(fs.watch(path, () => { + console.log(path, " file watcher triggered"); // on file changed: // - clear the old timer if it exists if (this.mergeCompileCommandsFileWwatcherTimer) { @@ -2443,8 +2442,7 @@ export class CppProperties { public checkMergeCompileCommands(): void { // Check for changes in case of file watcher failure. console.log("manually checking merge compile commands"); - this.resolveMergeCompileCommandsPaths(); - const mergeCompileCommands: MergeCompileCommands | undefined = this.mergeCompileCommands; + const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); if (mergeCompileCommands == undefined) { console.log("merge compile commands not found, returning"); return; @@ -2456,23 +2454,36 @@ export class CppProperties { this.onMergeCompileCommandsFiles(); } // check if any of the sources changed since last time we manually checked + var shouldMerge: boolean = false; mergeCompileCommands.sources.forEach((source) => { - console.log("checking source file: ", source); - fs.stat(source, (err, stats) => { - const uri = vscode.Uri.file(source); - if (err && err.code === "ENOENT" && this.mergeCompileCommandsSourceFiles.has(uri)) { - // source file previously existed, but deleted - this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? - this.onMergeCompileCommandsFiles(); - this.mergeCompileCommandsSourceFiles.delete(uri); // File deleted - } else if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { + try { + const stats = fs.statSync(source); + if (!this.mergeCompileCommandsSourceFiles.has(source)) { + console.log(source, " exists but was not previously added"); + this.mergeCompileCommandsSourceFiles.add(source); // File created. + shouldMerge = true; + } + else if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { + console.log(source, " was changeddd"); // source file changed since last time we manually checked this.mergeCompileCommandsFileWatcherFallbackTime = new Date(); - this.onMergeCompileCommandsFiles(); - this.mergeCompileCommandsSourceFiles.add(uri); // File created. + shouldMerge = true; } - }); + } + catch (err: any) { + if (err.code === "ENOENT") { + console.log(source, " was deleted"); + // source file previously existed, but deleted + this.mergeCompileCommandsSourceFiles.delete(source); // File deleted + this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? + shouldMerge = true; + } + } }); + console.log("loop done, shouldMerge: ", shouldMerge); + if (shouldMerge) { + this.onMergeCompileCommandsFiles(); + } } dispose(): void { From 3a71fba30405ca2c84694ff51ed94726f17f761e Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:11:29 +0200 Subject: [PATCH 07/10] align logic to state machine --- Extension/src/LanguageServer/client.ts | 2 +- .../src/LanguageServer/configurations.ts | 232 +++++++++--------- 2 files changed, 120 insertions(+), 114 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index a6a9cda790..2d25080f1e 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -3933,7 +3933,7 @@ export class DefaultClient implements Client { if (this.innerLanguageClient !== undefined && this.configuration !== undefined) { void this.languageClient.sendNotification(IntervalTimerNotification).catch(logAndReturn.undefined); this.configuration.checkCppProperties(); - this.configuration.checkMergeCompileCommands(); + //this.configuration.checkMergeCompileCommands(); this.configuration.checkCompileCommands(); } } diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 7d4725b83d..1e54bce5c5 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -168,7 +168,6 @@ export class CppProperties { private diagnosticCollection: vscode.DiagnosticCollection; private prevSquiggleMetrics: Map = new Map(); private settingsPanel?: SettingsPanel; - private mergeCompileCommandsSourceFiles: Set = new Set(); private mergeCompileCommands?: MergeCompileCommands; private mergeCompileCommandsFileWatchers: fs.FSWatcher[] = []; private mergeCompileCommandsFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails. @@ -919,65 +918,6 @@ export class CppProperties { return this.configProviderAutoSelected; } - private resolveMergeCompileCommandsPaths(): MergeCompileCommands | undefined { - if (!this.mergeCompileCommands) { - return undefined; - } - var result: MergeCompileCommands = { sources: [], destination: "" }; - this.mergeCompileCommands.sources.forEach(path => { result.sources.push(this.resolvePath(path)); }); - result.destination = this.resolvePath(this.mergeCompileCommands.destination); - return result; - } - - private onMergeCompileCommandsFiles(): void { - console.log("trying to merge compile commands"); - const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); - if (mergeCompileCommands === undefined || - mergeCompileCommands.destination.length === 0 || - mergeCompileCommands.sources.length === 0) { - return; - } - - var dst = mergeCompileCommands.destination; - const dst_dir = path.dirname(dst); - try { - fs.mkdirSync(dst_dir, { recursive: true }); - } - catch (err: any) { - const failedToCreate: string = localize("failed.to.create.config.folder", 'Failed to create "{0}"', dst_dir); - void vscode.window.showErrorMessage(`${failedToCreate}: ${err.message}`); - return; - } - if (fs.existsSync(dst) && fs.statSync(dst).isDirectory()) { - dst = path.join(dst, "merged_compile_commands.json"); - } - - // merge all the json files - const mergedCompiledCommands: CompileCommand[] = []; - mergeCompileCommands.sources.forEach(src => { - try { - const fileData = fs.readFileSync(src); - const fileCommands = JSON.parse(fileData.toString()) as CompileCommand[]; - mergedCompiledCommands.push(...fileCommands); - } - catch (err: any) { - const failedToRead: string = localize("failed.to.read.compile.commands", 'Failed to read "{0}"', src); - void vscode.window.showErrorMessage(`${failedToRead}: ${err.message}`); - return; - } - }); - - // try to save to the dst file - try { - const output = JSON.stringify(mergedCompiledCommands, null, 4); - fs.writeFileSync(dst, output); - } - catch (e: any) { - const failedToWrite: string = localize("failed.to.write.compile.commands", 'Failed to write "{0}"', dst); - void vscode.window.showErrorMessage(`${failedToWrite}: ${e.message}`); - } - } - private updateServerOnFolderSettingsChange(): void { this.configProviderAutoSelected = false; if (!this.configurationJson) { @@ -1177,7 +1117,7 @@ export class CppProperties { } this.mergeCompileCommands = settings.mergeCompileCommands; - this.updateMergeCompileCommandsFileWatchers(); + this.checkMergeCompileCommands(); this.updateCompileCommandsFileWatchers(); if (!this.configurationIncomplete) { @@ -1185,16 +1125,73 @@ export class CppProperties { } } + private resolveMergeCompileCommandsPaths(): MergeCompileCommands | undefined { + if (!this.mergeCompileCommands) { + return undefined; + } + var result: MergeCompileCommands = { sources: [], destination: "" }; + this.mergeCompileCommands.sources.forEach(path => { result.sources.push(this.resolvePath(path)); }); + result.destination = this.resolvePath(this.mergeCompileCommands.destination); + return result; + } + + public checkMergeCompileCommands(): void { + // Check for changes on settings changed / in case of file watcher failure. + // clear all file watchers + console.log("manually checking merge compile commands"); + this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); + this.mergeCompileCommandsFileWatchers = []; // reset it + + const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); + if (mergeCompileCommands == undefined) { + console.log("merge compile commands not found, returning"); + return; + } + // first, check if the destination file doesn't exist, + // if so, try to create it + if (!fs.existsSync(mergeCompileCommands.destination)) { + console.log("destination file not found, trying to create it"); + this.onMergeCompileCommandsFiles(); + return; + } + + // check if any of the sources changed since last time we manually checked + var shouldMerge: boolean = false; + mergeCompileCommands.sources.forEach((source) => { + try { + const stats = fs.statSync(source); + if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { + // source file changed since last time we manually checked + console.log(source, " is newer than last time we manually checked"); + this.mergeCompileCommandsFileWatcherFallbackTime = new Date(); + shouldMerge = true; + } + else { + console.log(source, " is older than last time we manually checked"); + } + } + catch (err: any) { + if (err.code === "ENOENT") { + // source file doesn't exist + console.log(source, " doesn't exist"); + } + } + }); + if (shouldMerge) { + // + this.onMergeCompileCommandsFiles(); + return; + } + this.updateMergeCompileCommandsFileWatchers(); + } public updateMergeCompileCommandsFileWatchers(): void { this.mergeCompileCommandsFileWatchers.forEach((watcher: fs.FSWatcher) => watcher.close()); this.mergeCompileCommandsFileWatchers = []; // reset it const mergeCompileCommands = this.resolveMergeCompileCommandsPaths(); if (mergeCompileCommands && - mergeCompileCommands.sources && mergeCompileCommands.sources.length > 0 && - mergeCompileCommands.destination) { - // if we are here, sources and destination are resolved + mergeCompileCommands.destination.length > 0) { const filePaths: Set = new Set(); mergeCompileCommands.sources.forEach((source: string) => { filePaths.add(source); @@ -1221,10 +1218,66 @@ export class CppProperties { })); }); } catch (e) { - // The file watcher limit is hit. - // TODO: Check if the compile commands file has a higher timestamp during the interval timer. + // a file watcher has failed, try to manually check for changes + console.log("file watcher limit reached, trying to manually check for changes"); + this.checkMergeCompileCommands(); + } + } + } + + private onMergeCompileCommandsFiles(): void { + console.log("trying to merge compile commands"); + const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); + if (mergeCompileCommands === undefined || + mergeCompileCommands.destination.length === 0 || + mergeCompileCommands.sources.length === 0) { + console.log("merge compile commands settings are null, returning"); + return; + } + + var dst = mergeCompileCommands.destination; + const dst_dir = path.dirname(dst); + try { + fs.mkdirSync(dst_dir, { recursive: true }); + } + catch (err: any) { + const failedToCreate: string = localize("failed.to.create.config.folder", 'Failed to create "{0}"', dst_dir); + void vscode.window.showErrorMessage(`${failedToCreate}: ${err.message}`); + return; + } + if (fs.existsSync(dst) && fs.statSync(dst).isDirectory()) { + dst = path.join(dst, "merged_compile_commands.json"); + } + + // merge all the json files + const mergedCompiledCommands: CompileCommand[] = []; + mergeCompileCommands.sources.forEach(src => { + try { + const fileData = fs.readFileSync(src); + const fileCommands = JSON.parse(fileData.toString()) as CompileCommand[]; + mergedCompiledCommands.push(...fileCommands); + } + catch (err: any) { + const failedToRead: string = localize("failed.to.read.compile.commands", 'Failed to read "{0}"', src); + void vscode.window.showErrorMessage(`${failedToRead}: ${err.message}`); + // NOTE: we don't return here but try to merge the rest of the files } + }); + + // try to save to the dst file + try { + const output = JSON.stringify(mergedCompiledCommands, null, 4); + fs.writeFileSync(dst, output); } + catch (e: any) { + const failedToWrite: string = localize("failed.to.write.compile.commands", 'Failed to write "{0}"', dst); + void vscode.window.showErrorMessage(`${failedToWrite}: ${e.message}`); + return; + } + + // if we got here, the merge was successful + // set up file watchers again + this.updateMergeCompileCommandsFileWatchers(); } private compileCommandsFileWatcherTimer?: NodeJS.Timeout; @@ -2439,53 +2492,6 @@ export class CppProperties { }); } - public checkMergeCompileCommands(): void { - // Check for changes in case of file watcher failure. - console.log("manually checking merge compile commands"); - const mergeCompileCommands: MergeCompileCommands | undefined = this.resolveMergeCompileCommandsPaths(); - if (mergeCompileCommands == undefined) { - console.log("merge compile commands not found, returning"); - return; - } - // first, check if the destination file doesn't exist, - // if so, try to create it - if (!fs.existsSync(mergeCompileCommands.destination)) { - console.log("destination file not found, trying to create it"); - this.onMergeCompileCommandsFiles(); - } - // check if any of the sources changed since last time we manually checked - var shouldMerge: boolean = false; - mergeCompileCommands.sources.forEach((source) => { - try { - const stats = fs.statSync(source); - if (!this.mergeCompileCommandsSourceFiles.has(source)) { - console.log(source, " exists but was not previously added"); - this.mergeCompileCommandsSourceFiles.add(source); // File created. - shouldMerge = true; - } - else if (stats.mtime > this.mergeCompileCommandsFileWatcherFallbackTime) { - console.log(source, " was changeddd"); - // source file changed since last time we manually checked - this.mergeCompileCommandsFileWatcherFallbackTime = new Date(); - shouldMerge = true; - } - } - catch (err: any) { - if (err.code === "ENOENT") { - console.log(source, " was deleted"); - // source file previously existed, but deleted - this.mergeCompileCommandsSourceFiles.delete(source); // File deleted - this.mergeCompileCommandsFileWatchers = []; // reset file watchers - TODO: do I need this? - shouldMerge = true; - } - } - }); - console.log("loop done, shouldMerge: ", shouldMerge); - if (shouldMerge) { - this.onMergeCompileCommandsFiles(); - } - } - dispose(): void { this.disposables.forEach((d) => d.dispose()); this.disposables = []; From f0655246e0ee6c98727505280c91f4ccfff87f76 Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Thu, 31 Oct 2024 07:30:09 +0200 Subject: [PATCH 08/10] avoid infinite cycle on missing files, need to periodically watch for non-existing files --- Extension/src/LanguageServer/configurations.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 1e54bce5c5..42d18efebb 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -1178,7 +1178,6 @@ export class CppProperties { } }); if (shouldMerge) { - // this.onMergeCompileCommandsFiles(); return; } @@ -1217,10 +1216,15 @@ export class CppProperties { }, 1000); })); }); - } catch (e) { - // a file watcher has failed, try to manually check for changes - console.log("file watcher limit reached, trying to manually check for changes"); - this.checkMergeCompileCommands(); + } catch (e: any) { + if (e.code == "ENOENT") { + console.log("file doesn't exist: ", path); + // TODO: add to a low cycle periodic check list until it exists + } else { + console.log("file watcher error: ", e.code) + console.log("file watcher limit reached, trying to manually check for changes"); + this.checkMergeCompileCommands(); + } } } } @@ -1277,6 +1281,7 @@ export class CppProperties { // if we got here, the merge was successful // set up file watchers again + console.log("merge successful"); this.updateMergeCompileCommandsFileWatchers(); } From 1f2a3f835adab6d300fb8c7b0981769a09019fc4 Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Thu, 31 Oct 2024 19:42:10 +0200 Subject: [PATCH 09/10] remove libc ignore --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index 01e1699399..d2a8df0e94 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,3 @@ OneLocBuild # ignore imported localization xlf directory vscode-translations-import - -# todo remove -Extension/bin/libc.so From 431b77e6adc838c2320e4a9a9f5c7b5a88a229b5 Mon Sep 17 00:00:00 2001 From: yiftahw <63462505+yiftahw@users.noreply.github.com> Date: Thu, 31 Oct 2024 20:24:03 +0200 Subject: [PATCH 10/10] typo --- Extension/src/LanguageServer/configurations.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 56bc30fbe2..86ecb6d9ff 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -171,7 +171,7 @@ export class CppProperties { private mergeCompileCommands?: MergeCompileCommands; private mergeCompileCommandsFileWatchers: fs.FSWatcher[] = []; private mergeCompileCommandsFileWatcherFallbackTime: Date = new Date(); // Used when file watching fails. - private mergeCompileCommandsFileWwatcherTimer?: NodeJS.Timeout; + private mergeCompileCommandsFileWatcherTimer?: NodeJS.Timeout; // Any time the default settings are parsed and assigned to `this.configurationJson`, // we want to track when the default includes have been added to it. @@ -1201,18 +1201,18 @@ export class CppProperties { console.log(path, " file watcher triggered"); // on file changed: // - clear the old timer if it exists - if (this.mergeCompileCommandsFileWwatcherTimer) { - clearInterval(this.mergeCompileCommandsFileWwatcherTimer); + if (this.mergeCompileCommandsFileWatcherTimer) { + clearInterval(this.mergeCompileCommandsFileWatcherTimer); } // - set a new timer to wait 1 second before processing the changes - this.mergeCompileCommandsFileWwatcherTimer = setTimeout(() => { + this.mergeCompileCommandsFileWatcherTimer = setTimeout(() => { // - merge all the compile_commands.json files even if only one changed this.onMergeCompileCommandsFiles(); // - clear the timer - if (this.mergeCompileCommandsFileWwatcherTimer) { - clearInterval(this.mergeCompileCommandsFileWwatcherTimer); + if (this.mergeCompileCommandsFileWatcherTimer) { + clearInterval(this.mergeCompileCommandsFileWatcherTimer); } - this.mergeCompileCommandsFileWwatcherTimer = undefined; + this.mergeCompileCommandsFileWatcherTimer = undefined; }, 1000); })); });