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

Add a Meson cpptools configProvider #218

Merged
merged 9 commits into from
Mar 9, 2024
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ the [Material Design Icons](https://materialdesignicons.com/) project.
`"envFile": "${workspaceFolder}/${config:mesonbuild.buildFolder}/meson-vscode.env"`
See [Meson devenv](https://mesonbuild.com/Commands.html#devenv)
- Configure Intellisense to use the `compile_commands.json` generated by Meson
- Provide an Intellisense C/C++ configuration to use the `meson-info`
introspection files:
`"C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild"`

\* - requires an installation of [muon](https://muon.build).

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,8 @@
"dependencies": {
"adm-zip": "^0.5.10",
"vscode-languageclient": "^9.0.1",
"which": "^4.0.0"
"which": "^4.0.0",
"vscode-cpptools": "^6.1.0"
},
"prettier": {
"proseWrap": "always"
Expand Down
164 changes: 164 additions & 0 deletions src/cpptoolsconfigprovider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import * as vscode from "vscode";
import * as cpptools from "vscode-cpptools";
import { getMesonBuildOptions, getMesonCompilers, getMesonDependencies } from "./introspection";
import { getOutputChannel } from "./utils";
import { Compiler, Dependencies } from "./types";

export class CpptoolsProvider implements cpptools.CustomConfigurationProvider {
cppToolsAPI?: cpptools.CppToolsApi;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this private?

Copy link
Contributor Author

@deribaucourt deribaucourt Feb 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It needs to be accessed by registerCppToolsProvider

private buildDir: string;

constructor(buildDir: string) {
this.buildDir = buildDir;
}

name = "Meson Build";
extensionId = "mesonbuild.mesonbuild";

canProvideBrowseConfiguration(token?: vscode.CancellationToken | undefined): Thenable<boolean> {
return new Promise<boolean>((resolve) => {
if (this.buildDir !== "") {
resolve(true);
} else {
// Wait for this.buildDir to not be ""
const interval = setInterval(() => {
if (this.buildDir !== "") {
clearInterval(interval);
this.refresh(this.buildDir);
resolve(true);
}
}, 100);
}
});
}

async provideBrowseConfiguration(
token?: vscode.CancellationToken | undefined,
): Promise<cpptools.WorkspaceBrowseConfiguration | null> {
let browseConfig: cpptools.WorkspaceBrowseConfiguration = {
browsePath: [],
compilerPath: "${default}",
compilerArgs: [],
};

const dependencies = await getMesonDependencies(this.buildDir);
browseConfig = Object.assign(browseConfig, { browsePath: this.getDependenciesIncludeDirs(dependencies) });

let machine: string | undefined;
const buildOptions = await getMesonBuildOptions(this.buildDir);
for (const option of buildOptions) {
if (option.name === "cpp_std") {
if (option.value != "none") browseConfig = Object.assign({}, browseConfig, { standard: option.value });
machine = option.machine;
} else if (machine === undefined && option.name === "c_std") {
// C++ takes precedence
if (option.value != "none") browseConfig = Object.assign({}, browseConfig, { standard: option.value });
machine = option.machine;
}
}

try {
const compilers = await getMesonCompilers(this.buildDir);
if (machine !== undefined && compilers[machine] !== undefined) {
const compiler = compilers[machine];
if (compiler && compiler["cpp"]) {
browseConfig = this.setCompilerArgs(compiler, "cpp", browseConfig);
} else if (compiler && compiler["c"]) {
browseConfig = this.setCompilerArgs(compiler, "c", browseConfig);
}
}
} catch (e) {
getOutputChannel().appendLine(
`Could not introspect a specific compiler, the default one will be used: ${JSON.stringify(e)}`,
);
}

getOutputChannel().appendLine(`Providing cpptools configuration: ${JSON.stringify(browseConfig)}`);
return browseConfig;
}

private getDependenciesIncludeDirs(dependencies: Dependencies) {
let includeDirs: string[] = [];
for (const dep of dependencies) {
if (dep.compile_args) {
for (const arg of dep.compile_args) {
if (arg.startsWith("-I")) {
includeDirs.push(arg.slice(2));
}
}
}
}
// The cpptools API requires at least one browse path, even when we provide a compiler path.
if (includeDirs.length === 0) {
includeDirs.push("");
}
return includeDirs;
}

private setCompilerArgs(
compiler: Compiler,
standard: string,
browseConfig: cpptools.WorkspaceBrowseConfiguration,
): cpptools.WorkspaceBrowseConfiguration {
if (compiler[standard]) {
const compilerDesc = compiler[standard];
browseConfig = Object.assign({}, browseConfig, {
compilerPath: compilerDesc.exelist[0],
compilerArgs: compilerDesc.exelist.slice(1),
});
}
return browseConfig;
}

// We only handle project-wide configurations.
canProvideBrowseConfigurationsPerFolder(token?: vscode.CancellationToken | undefined): Thenable<boolean> {
return Promise.resolve(false);
}

async provideFolderBrowseConfiguration(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whitespace around functions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

uri: vscode.Uri,
token?: vscode.CancellationToken | undefined,
): Promise<cpptools.WorkspaceBrowseConfiguration | null> {
return null;
}

// We only handle project-wide configurations.
canProvideConfiguration(uri: vscode.Uri, token?: vscode.CancellationToken | undefined): Thenable<boolean> {
return Promise.resolve(false);
}

async provideConfigurations(
uris: vscode.Uri[],
token?: vscode.CancellationToken | undefined,
): Promise<cpptools.SourceFileConfigurationItem[]> {
return [];
}

dispose() {}

refresh(buildDir: string) {
this.buildDir = buildDir;
this.cppToolsAPI?.notifyReady(this);
this.cppToolsAPI?.didChangeCustomConfiguration(this);
this.cppToolsAPI?.didChangeCustomBrowseConfiguration(this);
}
}

// Official implementation from https://classic.yarnpkg.com/en/package/vscode-cpptools
export async function registerCppToolsProvider(
ctx: vscode.ExtensionContext,
provider: CpptoolsProvider,
): Promise<cpptools.CppToolsApi | undefined> {
const cppToolsAPI = await cpptools.getCppToolsApi(cpptools.Version.latest);
if (cppToolsAPI) {
provider.cppToolsAPI = cppToolsAPI;
if (cppToolsAPI.notifyReady) {
cppToolsAPI.registerCustomConfigurationProvider(provider);
cppToolsAPI.notifyReady(provider);
ctx.subscriptions.push(cppToolsAPI);
} else {
throw new Error("CppTools API not available, or not version >2.0");
}
}
return cppToolsAPI;
}
14 changes: 14 additions & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ import {
} from "./utils";
import { DebugConfigurationProviderCppdbg } from "./debug/cppdbg";
import { DebugConfigurationProviderLldb } from "./debug/lldb";
import { CpptoolsProvider, registerCppToolsProvider } from "./cpptoolsconfigprovider";
import { testDebugHandler, testRunHandler, rebuildTests } from "./tests";
import { activateLinters } from "./linters";
import { activateFormatters } from "./formatters";
import { SettingsKey, TaskQuickPickItem } from "./types";
import { createLanguageServerClient } from "./lsp/common";
import { askShouldDownloadLanguageServer, askConfigureOnOpen, askAndSelectRootDir, selectRootDir } from "./dialogs";
import { getIntrospectionFile } from "./introspection";

export let extensionPath: string;
export let workspaceState: vscode.Memento;
let explorer: MesonProjectExplorer;
let cpptools: CpptoolsProvider;
let watcher: vscode.FileSystemWatcher;
let controller: vscode.TestController;

Expand Down Expand Up @@ -69,6 +72,8 @@ export async function activate(ctx: vscode.ExtensionContext) {
const buildDir = getBuildDirectory(sourceDir);
workspaceState.update("mesonbuild.buildDir", buildDir);
workspaceState.update("mesonbuild.sourceDir", sourceDir);
cpptools = new CpptoolsProvider(buildDir);
registerCppToolsProvider(ctx, cpptools);
Comment on lines +75 to +76
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we only do this if it is a C or C++ project?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it matters. If the user does not add the "C_Cpp.default.configurationProvider": "mesonbuild.mesonbuild" setting, then non of the configuration methods will be called. And they do, this only affects C/C++ files detected by the cpptools extension.


explorer = new MesonProjectExplorer(ctx, sourceDir, buildDir);

Expand Down Expand Up @@ -149,6 +154,15 @@ export async function activate(ctx: vscode.ExtensionContext) {
}
});

const mesonInfoFile = getIntrospectionFile(buildDir, "meson-info.json");
whenFileExists(ctx, mesonInfoFile, async () => {
cpptools.refresh(buildDir);
if (shouldModifySetting("ms-vscode.cpptools")) {
const conf = vscode.workspace.getConfiguration("C_Cpp");
conf.update("default.configurationProvider", "mesonbuild.mesonbuild", vscode.ConfigurationTarget.Workspace);
}
});

ctx.subscriptions.push(
vscode.commands.registerCommand("mesonbuild.openBuildFile", async (node: TargetNode) => {
const file = node.getTarget().defined_in;
Expand Down
12 changes: 10 additions & 2 deletions src/introspection.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import * as path from "path";
import { exec, extensionConfiguration, parseJSONFileIfExists, getOutputChannel } from "./utils";
import { Targets, Dependencies, BuildOptions, Tests, ProjectInfo } from "./types";
import { Targets, Dependencies, BuildOptions, Tests, ProjectInfo, Compilers } from "./types";

export function getIntrospectionFile(buildDir: string, filename: string) {
return path.join(buildDir, path.join("meson-info", filename));
}

async function introspectMeson<T>(buildDir: string, filename: string, introspectSwitch: string) {
getOutputChannel().appendLine(`Read introspection file ${filename}`);
const parsed = await parseJSONFileIfExists<T>(path.join(buildDir, path.join("meson-info", filename)));
const parsed = await parseJSONFileIfExists<T>(getIntrospectionFile(buildDir, filename));
if (parsed) {
return parsed;
}
Expand Down Expand Up @@ -40,6 +44,10 @@ export async function getMesonDependencies(buildDir: string) {
return introspectMeson<Dependencies>(buildDir, "intro-dependencies.json", "--dependencies");
}

export async function getMesonCompilers(buildDir: string) {
return introspectMeson<Compilers>(buildDir, "intro-compilers.json", "--compilers");
}

export async function getMesonTests(buildDir: string) {
return introspectMeson<Tests>(buildDir, "intro-tests.json", "--tests");
}
Expand Down
10 changes: 10 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,15 +103,24 @@ export interface BuildOption<T extends OptionType> {
desciption: string;
type: T;
value: OptionTypeMap[T];
machine: OptionTypeMap[T];
}

export interface Dependency {
name: string;
required: boolean;
compile_args: string[];
conditional: boolean;
has_fallback: boolean;
}

export interface CompilerDesc {
id: string;
exelist: string[];
}

export type Compiler = Record<string, CompilerDesc>;

export interface Test {
name: string;
workdir: string | null;
Expand All @@ -126,6 +135,7 @@ export interface Test {
export type Targets = Target[];
export type BuildOptions = BuildOption<any>[];
export type Dependencies = Dependency[];
export type Compilers = Record<string, Compiler>;
export type Tests = Test[];

export enum SettingsKey {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,11 @@ undici-types@~5.25.1:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3"
integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==

vscode-cpptools@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/vscode-cpptools/-/vscode-cpptools-6.1.0.tgz#d89bb225f91da45dbee6acbf45f6940aa3926df1"
integrity sha512-+40xMmzSlvaMwWEDIjhHl9+W1RH9xaEbiFAAgLWgyL1FXxQWBguWRHgS91qBJbuFAB9H4UBuK94iFMs+7BFclA==

vscode-jsonrpc@8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz#f43dfa35fb51e763d17cd94dcca0c9458f35abf9"
Expand Down
Loading