Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion Extension/c_cpp_properties.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,16 @@
"type": "string"
},
"compilerPath": {
"description": "Full path of the compiler being used, e.g. /usr/bin/gcc, to enable more accurate IntelliSense. Args can be added to modify the includes/defines used, e.g. -nostdinc++, -m32, etc., but paths with spaces must be surrounded with \\\" if args are used.",
"description": "Full path of the compiler being used, e.g. /usr/bin/gcc, to enable more accurate IntelliSense.",
"type": "string"
},
"compilerArgs": {
"description": "Compiler arguments to modify the includes or defines used, e.g. -nostdinc++, -m32, etc.",
"type": "array",
"items": {
"type": "string"
}
},
"cStandard": {
"description": "Version of the C language standard to use for IntelliSense.",
"type": "string",
Expand Down
12 changes: 12 additions & 0 deletions Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,18 @@
"description": "The value to use in a configuration if \"compilerPath\" is either not specified or set to \"${default}\".",
"scope": "resource"
},
"C_Cpp.default.compilerArgs": {
"type": [
"array",
"null"
],
"items": {
"type": "string"
},
"default": null,
"description": "The value to use in configuration if \"compilerArgs\" is either not specified or set to \"${default}\".",
"scope": "resource"
},
"C_Cpp.default.cStandard": {
"type": [
"string",
Expand Down
65 changes: 51 additions & 14 deletions Extension/src/LanguageServer/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ export interface Client {
logDiagnostics(): Promise<void>;
rescanFolder(): Promise<void>;
getCurrentConfigName(): Thenable<string>;
getCompilerPath(): Thenable<string>;
getCurrentCompilerPathAndArgs(): Thenable<util.CompilerPathAndArgs>;
getKnownCompilers(): Thenable<configs.KnownCompiler[]>;
takeOwnership(document: vscode.TextDocument): void;
queueTask<T>(task: () => Thenable<T>): Thenable<T>;
Expand Down Expand Up @@ -880,10 +880,13 @@ class DefaultClient implements Client {
return this.queueTask(() => Promise.resolve(this.configuration.CurrentConfiguration.name));
}

public getCompilerPath(): Thenable<string> {
return this.queueTask(() => Promise.resolve(this.configuration.CompilerPath));
public getCurrentCompilerPathAndArgs(): Thenable<util.CompilerPathAndArgs> {
return this.queueTask(() => Promise.resolve(
util.extractCompilerPathAndArgs(
this.configuration.CurrentConfiguration.compilerPath,
this.configuration.CurrentConfiguration.compilerArgs)
));
}

public getKnownCompilers(): Thenable<configs.KnownCompiler[]> {
return this.queueTask(() => Promise.resolve(this.configuration.KnownCompiler));
}
Expand Down Expand Up @@ -1383,6 +1386,13 @@ class DefaultClient implements Client {
configurations: configurations,
currentConfiguration: this.configuration.CurrentConfigurationIndex
};
// Separate compiler path and args before sending to language client
params.configurations.forEach((c: configs.Configuration) => {
let compilerPathAndArgs: util.CompilerPathAndArgs =
util.extractCompilerPathAndArgs(c.compilerPath, c.compilerArgs);
c.compilerPath = compilerPathAndArgs.compilerPath;
c.compilerArgs = compilerPathAndArgs.additionalArgs;
});
this.notifyWhenReady(() => {
this.languageClient.sendNotification(ChangeFolderSettingsNotification, params);
this.model.activeConfigName.Value = configurations[params.currentConfiguration].name;
Expand Down Expand Up @@ -1418,8 +1428,13 @@ class DefaultClient implements Client {

private isSourceFileConfigurationItem(input: any): input is SourceFileConfigurationItem {
return (input && (util.isString(input.uri) || util.isUri(input.uri)) &&
input.configuration && util.isArrayOfString(input.configuration.includePath) && util.isArrayOfString(input.configuration.defines) &&
util.isString(input.configuration.intelliSenseMode) && util.isString(input.configuration.standard) && util.isOptionalString(input.configuration.compilerPath) &&
input.configuration &&
util.isArrayOfString(input.configuration.includePath) &&
util.isArrayOfString(input.configuration.defines) &&
util.isString(input.configuration.intelliSenseMode) &&
util.isString(input.configuration.standard) &&
util.isOptionalString(input.configuration.compilerPath) &&
util.isOptionalArrayOfString(input.configuration.compilerArgs) &&
util.isOptionalArrayOfString(input.configuration.forcedInclude));
}

Expand All @@ -1438,17 +1453,26 @@ class DefaultClient implements Client {
let sanitized: SourceFileConfigurationItemAdapter[] = [];
configs.forEach(item => {
if (this.isSourceFileConfigurationItem(item)) {
sanitized.push({
uri: item.uri.toString(),
configuration: item.configuration
});
if (settings.loggingLevel === "Debug") {
out.appendLine(` uri: ${item.uri.toString()}`);
out.appendLine(` config: ${JSON.stringify(item.configuration, null, 2)}`);
}
if (item.configuration.includePath.some(path => path.endsWith('**'))) {
console.warn("custom include paths should not use recursive includes ('**')");
}
// Separate compiler path and args before sending to language client
let itemConfig: util.Mutable<SourceFileConfiguration> = {...item.configuration};
if (util.isString(itemConfig.compilerPath)) {
let compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(
itemConfig.compilerPath,
util.isArrayOfString(itemConfig.compilerArgs) ? itemConfig.compilerArgs : undefined);
itemConfig.compilerPath = compilerPathAndArgs.compilerPath;
itemConfig.compilerArgs = compilerPathAndArgs.additionalArgs;
}
sanitized.push({
uri: item.uri.toString(),
configuration: itemConfig
});
} else {
console.warn("discarding invalid SourceFileConfigurationItem: " + item);
}
Expand All @@ -1470,9 +1494,13 @@ class DefaultClient implements Client {
console.warn("discarding invalid WorkspaceBrowseConfiguration: " + config);
return Promise.resolve();
}
let sanitized: WorkspaceBrowseConfiguration = <WorkspaceBrowseConfiguration>config;
if (!util.isArrayOfString(sanitized.browsePath) || !util.isOptionalString(sanitized.compilerPath) ||
!util.isOptionalString(sanitized.standard) || !util.isOptionalString(sanitized.windowsSdkVersion)) {

let sanitized: util.Mutable<WorkspaceBrowseConfiguration> = {...<WorkspaceBrowseConfiguration>config};
if (!util.isArrayOfString(sanitized.browsePath) ||
!util.isOptionalString(sanitized.compilerPath) ||
!util.isOptionalArrayOfString(sanitized.compilerArgs) ||
!util.isOptionalString(sanitized.standard) ||
!util.isOptionalString(sanitized.windowsSdkVersion)) {
console.warn("discarding invalid WorkspaceBrowseConfiguration: " + config);
return Promise.resolve();
}
Expand All @@ -1483,6 +1511,15 @@ class DefaultClient implements Client {
out.appendLine(`Custom browse configuration received: ${JSON.stringify(sanitized, null, 2)}`);
}

// Separate compiler path and args before sending to language client
if (util.isString(sanitized.compilerPath)) {
let compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(
sanitized.compilerPath,
util.isArrayOfString(sanitized.compilerArgs) ? sanitized.compilerArgs : undefined);
sanitized.compilerPath = compilerPathAndArgs.compilerPath;
sanitized.compilerArgs = compilerPathAndArgs.additionalArgs;
}

let params: CustomBrowseConfigurationParams = {
browseConfiguration: sanitized
};
Expand Down Expand Up @@ -1629,7 +1666,7 @@ class NullClient implements Client {
logDiagnostics(): Promise<void> { return Promise.resolve(); }
rescanFolder(): Promise<void> { return Promise.resolve(); }
getCurrentConfigName(): Thenable<string> { return Promise.resolve(""); }
getCompilerPath(): Thenable<string> { return Promise.resolve(""); }
getCurrentCompilerPathAndArgs(): Thenable<util.CompilerPathAndArgs> { return Promise.resolve(undefined); }
getKnownCompilers(): Thenable<configs.KnownCompiler[]> { return Promise.resolve([]); }
takeOwnership(document: vscode.TextDocument): void {}
queueTask<T>(task: () => Thenable<T>): Thenable<T> { return task(); }
Expand Down
4 changes: 3 additions & 1 deletion Extension/src/LanguageServer/configurations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ConfigurationJson {
export interface Configuration {
name: string;
compilerPath?: string;
compilerArgs?: string[];
knownCompilers?: KnownCompiler[];
cStandard?: string;
cppStandard?: string;
Expand Down Expand Up @@ -88,6 +89,7 @@ export interface KnownCompiler {

export interface CompilerDefaults {
compilerPath: string;
compilerArgs: string[];
knownCompilers: KnownCompiler[];
cStandard: string;
cppStandard: string;
Expand Down Expand Up @@ -148,7 +150,6 @@ export class CppProperties {
public get Configurations(): Configuration[] { return this.configurationJson ? this.configurationJson.configurations : null; }
public get CurrentConfigurationIndex(): number { return this.currentConfigurationIndex.Value; }
public get CurrentConfiguration(): Configuration { return this.Configurations ? this.Configurations[this.CurrentConfigurationIndex] : null; }
public get CompilerPath(): string { return this.CurrentConfiguration ? this.CurrentConfiguration.compilerPath : null; }
public get KnownCompiler(): KnownCompiler[] { return this.knownCompilers; }

public get CurrentConfigurationProvider(): string|null {
Expand Down Expand Up @@ -544,6 +545,7 @@ export class CppProperties {
configuration.forcedInclude = this.updateConfiguration(configuration.forcedInclude, settings.defaultForcedInclude, env);
configuration.compileCommands = this.updateConfiguration(configuration.compileCommands, settings.defaultCompileCommands, env);
configuration.compilerPath = this.updateConfiguration(configuration.compilerPath, settings.defaultCompilerPath, env);
configuration.compilerArgs = this.updateConfiguration(configuration.compilerArgs, settings.defaultCompilerArgs, env);
configuration.cStandard = this.updateConfiguration(configuration.cStandard, settings.defaultCStandard, env);
configuration.cppStandard = this.updateConfiguration(configuration.cppStandard, settings.defaultCppStandard, env);
configuration.intelliSenseMode = this.updateConfiguration(configuration.intelliSenseMode, settings.defaultIntelliSenseMode, env);
Expand Down
96 changes: 47 additions & 49 deletions Extension/src/LanguageServer/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ export interface BuildTaskDefinition extends vscode.TaskDefinition {
/**
* Generate tasks to build the current file based on the user's detected compilers, the user's compilerPath setting, and the current file's extension.
*/
export async function getBuildTasks(returnComplerPath: boolean): Promise<vscode.Task[]> {
export async function getBuildTasks(returnCompilerPath: boolean): Promise<vscode.Task[]> {
const editor: vscode.TextEditor = vscode.window.activeTextEditor;
if (!editor) {
return [];
}

const fileExt: string = path.extname(editor.document.fileName);
if (!fileExt) {
return;
return [];
}

// Don't offer tasks for header files.
Expand All @@ -147,9 +147,7 @@ export async function getBuildTasks(returnComplerPath: boolean): Promise<vscode.
return [];
}

// Get a list of compilers found from the C++ side, then filter them based on the file type to get a reduced list appropriate
// for the active file, remove duplicate compiler names, then finally add the user's compilerPath setting.
let compilerPaths: string[];
// Get compiler paths.
const isWindows: boolean = os.platform() === 'win32';
let activeClient: Client;
try {
Expand All @@ -160,8 +158,11 @@ export async function getBuildTasks(returnComplerPath: boolean): Promise<vscode.
}
return []; // Language service features may be disabled.
}
let userCompilerPath: string = await activeClient.getCompilerPath();
if (userCompilerPath) {

// Get user compiler path.
const userCompilerPathAndArgs: util.CompilerPathAndArgs = await activeClient.getCurrentCompilerPathAndArgs();
let userCompilerPath: string = userCompilerPathAndArgs.compilerPath;
if (userCompilerPath && userCompilerPathAndArgs.compilerName) {
userCompilerPath = userCompilerPath.trim();
if (isWindows && userCompilerPath.startsWith("/")) { // TODO: Add WSL compiler support.
userCompilerPath = null;
Expand All @@ -170,38 +171,20 @@ export async function getBuildTasks(returnComplerPath: boolean): Promise<vscode.
}
}

// Get known compiler paths. Do not include the known compiler path that is the same as user compiler path.
// Filter them based on the file type to get a reduced list appropriate for the active file.
let knownCompilerPaths: string[];
let knownCompilers: configs.KnownCompiler[] = await activeClient.getKnownCompilers();
if (knownCompilers) {
knownCompilers = knownCompilers.filter(info => {
return ((fileIsCpp && !info.isC) || (fileIsC && info.isC)) &&
(path.basename(info.path) !== userCompilerPathAndArgs.compilerName) &&
(!isWindows || !info.path.startsWith("/")); // TODO: Add WSL compiler support.
});
compilerPaths = knownCompilers.map<string>(info => info.path);

let map: Map<string, string> = new Map<string, string>();
const insertOrAssignEntry: (compilerPath: string) => void = (compilerPath: string): void => {
let basename: string = compilerPath;
if (compilerPath === userCompilerPath) {
// Make sure the compiler args are not part of the basename.
const compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(compilerPath);
basename = compilerPathAndArgs.compilerPath;
}
basename = path.basename(basename);
map.set(basename, compilerPath);
};
compilerPaths.forEach(insertOrAssignEntry);

// Ensure that the user's compilerPath setting is used by inserting/assigning last.
if (userCompilerPath) {
insertOrAssignEntry(userCompilerPath);
}

compilerPaths = [...map.values()];
} else if (userCompilerPath) {
compilerPaths = [userCompilerPath];
knownCompilerPaths = knownCompilers.map<string>(info => info.path);
}

if (!compilerPaths) {
if (!knownCompilerPaths || !userCompilerPath) {
// Don't prompt a message yet until we can make a data-based decision.
telemetry.logLanguageServerEvent('noCompilerFound');
// Display a message prompting the user to install compilers if none were found.
Expand All @@ -228,41 +211,56 @@ export async function getBuildTasks(returnComplerPath: boolean): Promise<vscode.
return [];
}

// Generate tasks.
return compilerPaths.map<vscode.Task>(compilerPath => {
// Handle compiler args in compilerPath.
let compilerPathAndArgs: util.CompilerPathAndArgs = util.extractCompilerPathAndArgs(compilerPath);
compilerPath = compilerPathAndArgs.compilerPath;
let createTask: (compilerPath: string, compilerArgs?: string []) => vscode.Task = (compilerPath: string, compilerArgs?: string []) => {
const filePath: string = path.join('${fileDirname}', '${fileBasenameNoExtension}');
const compilerPathBase: string = path.basename(compilerPath);
const taskName: string = compilerPathBase + " build active file";
const isCl: boolean = taskName.startsWith("cl.exe");
let args: string[] = isCl ? [ '/Zi', '/EHsc', '/Fe:', filePath + '.exe', '${file}' ] : ['-g', '${file}', '-o', filePath + (isWindows ? '.exe' : '')];
if (compilerPathAndArgs.additionalArgs) {
args = args.concat(compilerPathAndArgs.additionalArgs);
}
const isCl: boolean = compilerPathBase === "cl.exe";
const cwd: string = isCl ? "" : path.dirname(compilerPath);
const kind: BuildTaskDefinition = {
let args: string[] = isCl ? ['/Zi', '/EHsc', '/Fe:', filePath + '.exe', '${file}'] : ['-g', '${file}', '-o', filePath + (isWindows ? '.exe' : '')];
if (compilerArgs && compilerArgs.length > 0) {
args = args.concat(compilerArgs);
}

let kind: vscode.TaskDefinition = {
type: 'shell',
label: taskName,
command: isCl ? compilerPathBase : compilerPath,
args: args,
options: isCl ? undefined : {"cwd": cwd},
compilerPath: isCl ? compilerPathBase : compilerPath
};

if (returnCompilerPath) {
kind = kind as BuildTaskDefinition;
kind.compilerPath = isCl ? compilerPathBase : compilerPath;
}

const command: vscode.ShellExecution = new vscode.ShellExecution(compilerPath, [...args], { cwd: cwd });
const target: vscode.WorkspaceFolder = vscode.workspace.getWorkspaceFolder(clients.ActiveClient.RootUri);
let task: vscode.Task = new vscode.Task(kind, target, taskName, taskSourceStr, command, '$gcc');
task.definition = kind; // The constructor for vscode.Task will eat the definition. Reset it by reassigning.
task.definition = kind; // The constructor for vscode.Task will consume the definition. Reset it by reassigning.
task.group = vscode.TaskGroup.Build;

if (!returnComplerPath) {
delete task.definition.compilerPath;
}

return task;
});
};

// Create a build task per compiler path
let buildTasks: vscode.Task[] = [];

// Tasks for known compiler paths
if (knownCompilerPaths) {
buildTasks = knownCompilerPaths.map<vscode.Task>(compilerPath => {
return createTask(compilerPath);
});
}

// Task for user compiler path setting
if (userCompilerPath) {
let task: vscode.Task = createTask(userCompilerPath, userCompilerPathAndArgs.additionalArgs);
buildTasks.push(task);
}

return buildTasks;
}

function onDidOpenTextDocument(document: vscode.TextDocument): void {
Expand Down
1 change: 1 addition & 0 deletions Extension/src/LanguageServer/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export class CppSettings extends Settings {
public get defaultForcedInclude(): string[] { return super.Section.get<string[]>("default.forcedInclude"); }
public get defaultIntelliSenseMode(): string { return super.Section.get<string>("default.intelliSenseMode"); }
public get defaultCompilerPath(): string { return super.Section.get<string>("default.compilerPath"); }
public get defaultCompilerArgs(): string[] { return super.Section.get<string[]>("default.compilerArgs"); }
public get defaultCStandard(): string { return super.Section.get<string>("default.cStandard"); }
public get defaultCppStandard(): string { return super.Section.get<string>("default.cppStandard"); }
public get defaultConfigurationProvider(): string { return super.Section.get<string>("default.configurationProvider"); }
Expand Down
Loading