Skip to content

Commit 41b7678

Browse files
authored
implement addNodeAddonIncludePaths (#6731)
* add addNodeAddonIncludePaths, fix for #4854
1 parent 2ad35c8 commit 41b7678

File tree

5 files changed

+92
-3
lines changed

5 files changed

+92
-3
lines changed

Extension/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,12 @@
11021102
"markdownDescription": "%c_cpp.configuration.vcpkg.enabled.markdownDescription%",
11031103
"scope": "resource"
11041104
},
1105+
"C_Cpp.addNodeAddonIncludePaths": {
1106+
"type": "boolean",
1107+
"default": false,
1108+
"markdownDescription": "%c_cpp.configuration.addNodeAddonIncludePaths.description%",
1109+
"scope": "application"
1110+
},
11051111
"C_Cpp.renameRequiresIdentifier": {
11061112
"type": "boolean",
11071113
"default": true,
@@ -2679,4 +2685,4 @@
26792685
"integrity": "CF1A01AA75275F76800F6BC1D289F2066DCEBCD983376D344ABF6B03FDB8FEA0"
26802686
}
26812687
]
2682-
}
2688+
}

Extension/package.nls.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
"c_cpp.configuration.enhancedColorization.description": "If enabled, code is colorized based on IntelliSense. This setting only applies if intelliSenseEngine is set to \"Default\".",
161161
"c_cpp.configuration.codeFolding.description": "If enabled, code folding ranges are provided by the language server.",
162162
"c_cpp.configuration.vcpkg.enabled.markdownDescription": "Enable integration services for the [vcpkg dependency manager](https://aka.ms/vcpkg/).",
163+
"c_cpp.configuration.addNodeAddonIncludePaths.description": "Add include paths from nan and node-addon-api when they're dependencies.",
163164
"c_cpp.configuration.renameRequiresIdentifier.description": "If true, 'Rename Symbol' will require a valid C/C++ identifier.",
164165
"c_cpp.configuration.debugger.useBacktickCommandSubstitution.description": "If true, debugger shell command substitution will use obsolete backtick (`).",
165166
"c_cpp.contributes.views.cppReferencesView.title": "C/C++: Other references results",

Extension/src/LanguageServer/client.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,6 +1367,11 @@ export class DefaultClient implements Client {
13671367
this.semanticTokensProvider = undefined;
13681368
}
13691369
}
1370+
// if addNodeAddonIncludePaths was turned on but no includes have been found yet then 1) presume that nan
1371+
// or node-addon-api was installed so prompt for reload.
1372+
if (changedSettings["addNodeAddonIncludePaths"] && settings.addNodeAddonIncludePaths && this.configuration.nodeAddonIncludesFound() === 0) {
1373+
util.promptForReloadWindowDueToSettingsChange();
1374+
}
13701375
}
13711376
this.configuration.onDidChangeSettings();
13721377
telemetry.logLanguageServerEvent("CppSettingsChange", changedSettings, undefined);

Extension/src/LanguageServer/configurations.ts

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ export class CppProperties {
130130
private defaultWindowsSdkVersion: string | null = null;
131131
private vcpkgIncludes: string[] = [];
132132
private vcpkgPathReady: boolean = false;
133+
private nodeAddonIncludes: string[] = [];
133134
private defaultIntelliSenseMode?: string;
134135
private defaultCustomConfigurationVariables?: { [key: string]: string };
135136
private readonly configurationGlobPattern: string = "c_cpp_properties.json";
@@ -155,6 +156,7 @@ export class CppProperties {
155156
this.configFolder = path.join(rootPath, ".vscode");
156157
this.diagnosticCollection = vscode.languages.createDiagnosticCollection(rootPath);
157158
this.buildVcpkgIncludePath();
159+
this.readNodeAddonIncludeLocations(rootPath);
158160
this.disposables.push(vscode.Disposable.from(this.configurationsChanged, this.selectionChanged, this.compileCommandsChanged));
159161
}
160162

@@ -317,6 +319,7 @@ export class CppProperties {
317319
} else {
318320
configuration.includePath = [defaultFolder];
319321
}
322+
320323
// browse.path is not set by default anymore. When it is not set, the includePath will be used instead.
321324
if (isUnset(settings.defaultDefines)) {
322325
configuration.defines = (process.platform === 'win32') ? ["_DEBUG", "UNICODE", "_UNICODE"] : [];
@@ -386,6 +389,72 @@ export class CppProperties {
386389
}
387390
}
388391

392+
public nodeAddonIncludesFound(): number {
393+
return this.nodeAddonIncludes.length;
394+
}
395+
396+
private async readNodeAddonIncludeLocations(rootPath: string): Promise<void> {
397+
let error: Error | undefined;
398+
let pdjFound: boolean = false;
399+
const package_json: any = await fs.promises.readFile(path.join(rootPath, "package.json"), "utf8")
400+
.then(pdj => {pdjFound = true; return JSON.parse(pdj); })
401+
.catch(e => (error = e));
402+
403+
if (!error) {
404+
try {
405+
const pathToNode: string = which.sync("node");
406+
const nodeAddonMap: { [dependency: string]: string } = {
407+
"nan": `"${pathToNode}" --no-warnings -e "require('nan')"`,
408+
"node-addon-api": `"${pathToNode}" --no-warnings -p "require('node-addon-api').include"`
409+
};
410+
411+
for (const dep in nodeAddonMap) {
412+
if (dep in package_json.dependencies) {
413+
const execCmd: string = nodeAddonMap[dep];
414+
let stdout: string = await util.execChildProcess(execCmd, rootPath);
415+
if (!stdout) {
416+
continue;
417+
}
418+
419+
// cleanup newlines
420+
if (stdout[stdout.length - 1] === "\n") {
421+
stdout = stdout.slice(0, -1);
422+
}
423+
// node-addon-api returns a quoted string, e.g., '"/home/user/dir/node_modules/node-addon-api"'.
424+
if (stdout[0] === "\"" && stdout[stdout.length - 1] === "\"") {
425+
stdout = stdout.slice(1, -1);
426+
}
427+
428+
// at this time both node-addon-api and nan return their own directory so this test is not really
429+
// needed. but it does future proof the code.
430+
if (!await util.checkDirectoryExists(stdout)) {
431+
// nan returns a path relative to rootPath causing the previous check to fail because this code
432+
// is executing in vscode's working directory.
433+
stdout = path.join(rootPath, stdout);
434+
if (!await util.checkDirectoryExists(stdout)) {
435+
error = new Error(`${dep} directory ${stdout} doesn't exist`);
436+
stdout = '';
437+
}
438+
}
439+
if (stdout) {
440+
this.nodeAddonIncludes.push(stdout);
441+
}
442+
}
443+
}
444+
} catch (e) {
445+
error = e;
446+
}
447+
}
448+
if (error) {
449+
if (pdjFound) {
450+
// only log an error if package.json exists.
451+
console.log('readNodeAddonIncludeLocations', error.message);
452+
}
453+
} else {
454+
this.handleConfigurationChange();
455+
}
456+
}
457+
389458
private getConfigIndexForPlatform(config: any): number | undefined {
390459
if (!this.configurationJson) {
391460
return undefined;
@@ -627,6 +696,12 @@ export class CppProperties {
627696
const configuration: Configuration = this.configurationJson.configurations[i];
628697

629698
configuration.includePath = this.updateConfigurationStringArray(configuration.includePath, settings.defaultIncludePath, env);
699+
// in case includePath is reset below
700+
const origIncludePath: string[] | undefined = configuration.includePath;
701+
if (settings.addNodeAddonIncludePaths) {
702+
const includePath: string[] = origIncludePath || [];
703+
configuration.includePath = includePath.concat(this.nodeAddonIncludes.filter(i => includePath.indexOf(i) < 0));
704+
}
630705
configuration.defines = this.updateConfigurationStringArray(configuration.defines, settings.defaultDefines, env);
631706
configuration.macFrameworkPath = this.updateConfigurationStringArray(configuration.macFrameworkPath, settings.defaultMacFrameworkPath, env);
632707
configuration.windowsSdkVersion = this.updateConfigurationString(configuration.windowsSdkVersion, settings.defaultWindowsSdkVersion, env);
@@ -663,8 +738,9 @@ export class CppProperties {
663738
if (!configuration.windowsSdkVersion && !!this.defaultWindowsSdkVersion) {
664739
configuration.windowsSdkVersion = this.defaultWindowsSdkVersion;
665740
}
666-
if (!configuration.includePath && !!this.defaultIncludes) {
667-
configuration.includePath = this.defaultIncludes;
741+
if (!origIncludePath && !!this.defaultIncludes) {
742+
const includePath: string[] = configuration.includePath || [];
743+
configuration.includePath = includePath.concat(this.defaultIncludes);
668744
}
669745
if (!configuration.macFrameworkPath && !!this.defaultFrameworks) {
670746
configuration.macFrameworkPath = this.defaultFrameworks;

Extension/src/LanguageServer/settings.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export class CppSettings extends Settings {
141141
public get preferredPathSeparator(): string | undefined { return super.Section.get<string>("preferredPathSeparator"); }
142142
public get updateChannel(): string | undefined { return super.Section.get<string>("updateChannel"); }
143143
public get vcpkgEnabled(): boolean | undefined { return super.Section.get<boolean>("vcpkg.enabled"); }
144+
public get addNodeAddonIncludePaths(): boolean | undefined { return super.Section.get<boolean>("addNodeAddonIncludePaths"); }
144145
public get renameRequiresIdentifier(): boolean | undefined { return super.Section.get<boolean>("renameRequiresIdentifier"); }
145146
public get defaultIncludePath(): string[] | undefined { return super.Section.get<string[]>("default.includePath"); }
146147
public get defaultDefines(): string[] | undefined { return super.Section.get<string[]>("default.defines"); }

0 commit comments

Comments
 (0)