From 67eea35d8f2fd9a7c1f59cb68674fd9b20c547be Mon Sep 17 00:00:00 2001 From: Matheus Castello Date: Tue, 15 Feb 2022 15:15:38 -0300 Subject: [PATCH] Add dotconfig property for c_cpp_properties.json (#7845) * Add dotConfig property for c_cpp_properties.json --- Extension/c_cpp_properties.schema.json | 4 ++ Extension/package.json | 6 ++ Extension/package.nls.json | 1 + .../src/LanguageServer/configurations.ts | 67 ++++++++++++++++++- Extension/src/LanguageServer/settings.ts | 1 + Extension/src/LanguageServer/settingsPanel.ts | 4 ++ Extension/ui/settings.html | 9 +++ Extension/ui/settings.ts | 4 ++ 8 files changed, 95 insertions(+), 1 deletion(-) diff --git a/Extension/c_cpp_properties.schema.json b/Extension/c_cpp_properties.schema.json index a1619a9c18..3497abf8c9 100644 --- a/Extension/c_cpp_properties.schema.json +++ b/Extension/c_cpp_properties.schema.json @@ -88,6 +88,10 @@ "type": "string", "pattern": "^\\d{2}\\.\\d{1}\\.\\d{5}\\.\\d{1}$|^8\\.1$" }, + "dotConfig": { + "description": "A path to a .config file created by Kconfig system. Kconfig system generates a file with all the defines to build a project. Examples of projects that use Kconfig system are the Linux Kernel and NuttX RTOS.", + "type": "string" + }, "defines": { "markdownDescription": "A list of preprocessor definitions for the IntelliSense engine to use while parsing files. Optionally, use `=` to set a value, e.g. `VERSION=1`.", "descriptionHint": "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.", diff --git a/Extension/package.json b/Extension/package.json index 3c3e82ca3d..424517cffd 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -2472,6 +2472,12 @@ "markdownDescription": "%c_cpp.configuration.default.enableConfigurationSquiggles.markdownDescription%", "scope": "resource" }, + "C_Cpp.default.dotConfig": { + "type": "string", + "default": null, + "markdownDescription": "%c_cpp.configuration.default.dotConfig.markdownDescription%", + "scope": "resource" + }, "C_Cpp.updateChannel": { "type": "string", "enum": [ diff --git a/Extension/package.nls.json b/Extension/package.nls.json index ef572d8cec..9e8ac51db4 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -196,6 +196,7 @@ "c_cpp.configuration.default.customConfigurationVariables.markdownDescription": { "message": "The value to use in a configuration if `customConfigurationVariables` is not set, or the values to insert if `${default}` is present as a key in `customConfigurationVariables`.", "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.markdownDescription": { "message": "Set to `Insiders` to automatically download and install the latest Insiders builds of the extension, which include upcoming features and bug fixes.", "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`.", "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.experimentalFeatures.description": "Controls whether \"experimental\" features are usable.", "c_cpp.configuration.suggestSnippets.markdownDescription": { "message": "If `true`, snippets are provided by the language server.", "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.enhancedColorization.markdownDescription": { "message": "If enabled, code is colorized based on IntelliSense. This setting only applies if `#C_Cpp.intelliSenseEngine#` is set to `Default`.", "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." ] }, diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 967ffc353a..90d5f284c3 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -20,6 +20,7 @@ import * as nls from 'vscode-nls'; import { setTimeout } from 'timers'; import * as which from 'which'; import { WorkspaceBrowseConfiguration } from 'vscode-cpptools'; +import { getOutputChannelLogger } from '../logger'; nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })(); const localize: nls.LocalizeFunc = nls.loadMessageBundle(); @@ -67,6 +68,7 @@ export interface Configuration { includePath?: string[]; macFrameworkPath?: string[]; windowsSdkVersion?: string; + dotConfig?: string; defines?: string[]; intelliSenseMode?: string; intelliSenseModeIsExplicit?: boolean; @@ -86,6 +88,7 @@ export interface ConfigurationErrors { macFrameworkPath?: string; forcedInclude?: string; compileCommands?: string; + dotConfig?: string; browsePath?: string; databaseFilename?: string; } @@ -787,6 +790,23 @@ export class CppProperties { return this.resolveDefaultsDictionary(property, defaultValue, env); } + private getDotconfigDefines(dotConfigPath: string): string[] { + const isWindows: boolean = os.platform() === 'win32'; + + if (dotConfigPath !== undefined) { + const path: string = this.resolvePath(dotConfigPath, isWindows); + try { + const configContent: string[] = fs.readFileSync(path, "utf-8").split("\n"); + return configContent.filter(i => !i.startsWith("#") && i !== ""); + } catch (errJS) { + const err: Error = errJS as Error; + getOutputChannelLogger().appendLine(`Invalid input, cannot resolve .config path: ${err.message}`); + } + } + + return []; + } + private updateServerOnFolderSettingsChange(): void { if (!this.configurationJson) { return; @@ -805,7 +825,15 @@ export class CppProperties { configuration.includePath = includePath.concat(this.nodeAddonIncludes.filter(i => includePath.indexOf(i) < 0)); } configuration.defines = this.updateConfigurationStringArray(configuration.defines, settings.defaultDefines, env); - configuration.macFrameworkPath = this.updateConfigurationPathsArray(configuration.macFrameworkPath, settings.defaultMacFrameworkPath, env); + + // in case we have dotConfig + configuration.dotConfig = this.updateConfigurationString(configuration.dotConfig, settings.defaultDotconfig, env); + if (configuration.dotConfig !== undefined) { + configuration.defines = configuration.defines || []; + configuration.defines = configuration.defines.concat(this.getDotconfigDefines(configuration.dotConfig)); + } + + configuration.macFrameworkPath = this.updateConfigurationStringArray(configuration.macFrameworkPath, settings.defaultMacFrameworkPath, env); configuration.windowsSdkVersion = this.updateConfigurationString(configuration.windowsSdkVersion, settings.defaultWindowsSdkVersion, env); configuration.forcedInclude = this.updateConfigurationPathsArray(configuration.forcedInclude, settings.defaultForcedInclude, env); configuration.compileCommands = this.updateConfigurationString(configuration.compileCommands, settings.defaultCompileCommands, env); @@ -1445,6 +1473,7 @@ export class CppProperties { // Validate files errors.forcedInclude = this.validatePath(config.forcedInclude, false, true); errors.compileCommands = this.validatePath(config.compileCommands, false); + errors.dotConfig = this.validatePath(config.dotConfig, false); errors.databaseFilename = this.validatePath((config.browse ? config.browse.databaseFilename : undefined), false); // Validate intelliSenseMode @@ -1708,6 +1737,9 @@ export class CppProperties { const compilerPathStart: number = curText.search(/\s*\"compilerPath\"\s*:\s*\"/); const compilerPathValueStart: number = curText.indexOf('"', curText.indexOf(":", compilerPathStart)); const compilerPathEnd: number = compilerPathStart === -1 ? -1 : curText.indexOf('"', compilerPathValueStart + 1) + 1; + const dotConfigStart: number = curText.search(/\s*\"dotConfig\"\s*:\s*\"/); + const dotConfigValueStart: number = curText.indexOf('"', curText.indexOf(":", dotConfigStart)); + const dotConfigEnd: number = dotConfigStart === -1 ? -1 : curText.indexOf('"', dotConfigValueStart + 1) + 1; const processedPaths: Set = new Set(); // Validate compiler paths @@ -1753,6 +1785,39 @@ export class CppProperties { diagnostics.push(diagnostic); } + // validate .config path + let dotConfigPath: string | undefined; + let dotConfigPathExists: boolean = true; + let dotConfigMessage: string | undefined; + + dotConfigPath = currentConfiguration.dotConfig; + dotConfigPath = util.resolveVariables(dotConfigPath, this.ExtendedEnvironment).trim(); + dotConfigPath = this.resolvePath(dotConfigPath, isWindows); + const isWSLDotConfig: boolean = isWindows && dotConfigPath.startsWith("/"); + // does not try resolve if the dotConfig property is empty + dotConfigPath = dotConfigPath !== '' ? dotConfigPath : undefined; + + if (dotConfigPath && this.rootUri) { + const checkPathExists: any = util.checkPathExistsSync(dotConfigPath, this.rootUri.fsPath + path.sep, isWindows, isWSLDotConfig, true); + dotConfigPathExists = checkPathExists.pathExists; + dotConfigPath = checkPathExists.path; + } + if (!dotConfigPathExists) { + dotConfigMessage = localize('cannot.find2', "Cannot find \"{0}\".", dotConfigPath); + newSquiggleMetrics.PathNonExistent++; + } else if (dotConfigPath && !util.checkFileExistsSync(dotConfigPath)) { + dotConfigMessage = localize("path.is.not.a.file", "Path is not a file: {0}", dotConfigPath); + newSquiggleMetrics.PathNotAFile++; + } + + if (dotConfigMessage) { + const diagnostic: vscode.Diagnostic = new vscode.Diagnostic( + new vscode.Range(document.positionAt(curTextStartOffset + dotConfigValueStart), + document.positionAt(curTextStartOffset + dotConfigEnd)), + dotConfigMessage, vscode.DiagnosticSeverity.Warning); + diagnostics.push(diagnostic); + } + // Validate paths for (const curPath of paths) { if (processedPaths.has(curPath)) { diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 33022ca96d..6860e2e211 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -218,6 +218,7 @@ export class CppSettings extends Settings { public get filesExclude(): vscode.WorkspaceConfiguration | undefined { return super.Section.get("files.exclude"); } public get defaultIncludePath(): string[] | undefined { return super.getWithUndefinedDefault("default.includePath"); } public get defaultDefines(): string[] | undefined { return super.getWithUndefinedDefault("default.defines"); } + public get defaultDotconfig(): string | undefined { return super.Section.get("default.dotConfig"); } public get defaultMacFrameworkPath(): string[] | undefined { return super.getWithUndefinedDefault("default.macFrameworkPath"); } public get defaultWindowsSdkVersion(): string | undefined { return super.Section.get("default.windowsSdkVersion"); } public get defaultCompileCommands(): string | undefined { return super.Section.get("default.compileCommands"); } diff --git a/Extension/src/LanguageServer/settingsPanel.ts b/Extension/src/LanguageServer/settingsPanel.ts index 7396c23cc3..ad27fa2aa1 100644 --- a/Extension/src/LanguageServer/settingsPanel.ts +++ b/Extension/src/LanguageServer/settingsPanel.ts @@ -39,6 +39,7 @@ const elementId: { [key: string]: string } = { windowsSdkVersion: "windowsSdkVersion", macFrameworkPath: "macFrameworkPath", compileCommands: "compileCommands", + dotConfig: "dotConfig", mergeConfigurations: "mergeConfigurations", configurationProvider: "configurationProvider", forcedInclude: "forcedInclude", @@ -326,6 +327,9 @@ export class SettingsPanel { case elementId.compileCommands: this.configValues.compileCommands = message.value; break; + case elementId.dotConfig: + this.configValues.dotConfig = message.value; + break; case elementId.mergeConfigurations: this.configValues.mergeConfigurations = message.value; break; diff --git a/Extension/ui/settings.html b/Extension/ui/settings.html index 0ccce51cda..e1d7a93ec6 100644 --- a/Extension/ui/settings.html +++ b/Extension/ui/settings.html @@ -656,6 +656,15 @@ +
+
Dot Config
+
A path to a .config file created by Kconfig system. Kconfig system generates a file with all the defines to build a project. Examples of projects that use Kconfig system are the Linux Kernel and NuttX RTOS.
+
+ +
+
+
+
Compile commands
diff --git a/Extension/ui/settings.ts b/Extension/ui/settings.ts index c925ff5211..3a99cfc0f0 100644 --- a/Extension/ui/settings.ts +++ b/Extension/ui/settings.ts @@ -40,6 +40,8 @@ const elementId: { [key: string]: string } = { forcedInclude: "forcedInclude", forcedIncludeInvalid: "forcedIncludeInvalid", mergeConfigurations: "mergeConfigurations", + dotConfig: "dotConfig", + dotConfigInvalid: "dotConfigInvalid", // Browse properties browsePath: "browsePath", @@ -273,6 +275,7 @@ class SettingsApp { (document.getElementById(elementId.mergeConfigurations)).checked = config.mergeConfigurations; (document.getElementById(elementId.configurationProvider)).value = config.configurationProvider ? config.configurationProvider : ""; (document.getElementById(elementId.forcedInclude)).value = joinEntries(config.forcedInclude); + (document.getElementById(elementId.dotConfig)).value = config.dotConfig ? config.dotConfig : ""; if (config.browse) { (document.getElementById(elementId.browsePath)).value = joinEntries(config.browse.path); @@ -301,6 +304,7 @@ class SettingsApp { this.showErrorWithInfo(elementId.compileCommandsInvalid, errors.compileCommands); this.showErrorWithInfo(elementId.browsePathInvalid, errors.browsePath); this.showErrorWithInfo(elementId.databaseFilenameInvalid, errors.databaseFilename); + this.showErrorWithInfo(elementId.dotConfigInvalid, errors.dotConfig); } finally { this.updating = false; }