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

Update cppbuild task behavior for args and command fields to match VS Code's shell task type #12026

Merged
merged 8 commits into from
Feb 27, 2024
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
72 changes: 69 additions & 3 deletions Extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,12 +313,78 @@
"description": "%c_cpp.taskDefinitions.name.description%"
},
"command": {
"type": "string",
"description": "%c_cpp.taskDefinitions.command.description%"
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"required": [
"value",
"quoting"
],
"properties": {
"value": {
"type": "string",
"description": "%c_cpp.taskDefinitions.args.value.description%"
},
"quoting": {
"type": "string",
"enum": [
"escape",
"strong",
"weak"
],
"enumDescriptions": [
"%c_cpp.taskDefinitions.args.quoting.escape.description%",
"%c_cpp.taskDefinitions.args.quoting.strong.description%",
"%c_cpp.taskDefinitions.args.quoting.weak.description%"
],
"default": "strong",
"description": "%c_cpp.taskDefinitions.args.quoting.description%"
}
}
}
]
},
"args": {
"type": "array",
"description": "%c_cpp.taskDefinitions.args.description%"
"description": "%c_cpp.taskDefinitions.args.description%",
"items": {
"oneOf": [
{
"type": "string"
},
{
"type": "object",
"required": [
"value",
"quoting"
],
"properties": {
"value": {
"type": "string",
"description": "%c_cpp.taskDefinitions.args.value.description%"
},
"quoting": {
"type": "string",
"enum": [
"escape",
"strong",
"weak"
],
"enumDescriptions": [
"%c_cpp.taskDefinitions.args.quoting.escape.description%",
"%c_cpp.taskDefinitions.args.quoting.strong.description%",
"%c_cpp.taskDefinitions.args.quoting.weak.description%"
],
"default": "strong",
"description": "%c_cpp.taskDefinitions.args.quoting.description%"
}
}
}
]
}
},
"options": {
"type": "object",
Expand Down
5 changes: 5 additions & 0 deletions Extension/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -928,6 +928,11 @@
"c_cpp.taskDefinitions.name.description": "The name of the task.",
"c_cpp.taskDefinitions.command.description": "The path to either a compiler or script that performs compilation.",
"c_cpp.taskDefinitions.args.description": "Additional arguments to pass to the compiler or compilation script.",
"c_cpp.taskDefinitions.args.value.description": "The actual argument value.",
"c_cpp.taskDefinitions.args.quoting.description": "How the argument value should be quoted.",
"c_cpp.taskDefinitions.args.quoting.escape.description": "Escapes characters using the shell's escape character (e.g. \\ under bash).",
"c_cpp.taskDefinitions.args.quoting.strong.description": "Quotes the argument using the shell's strong quote character (e.g. ' under bash).",
"c_cpp.taskDefinitions.args.quoting.weak.description": "Quotes the argument using the shell's weak quote character (e.g. \" under bash).",
"c_cpp.taskDefinitions.options.description": "Additional command options.",
"c_cpp.taskDefinitions.options.cwd.description": "The current working directory of the executed program or script. If omitted Code's current workspace root is used.",
"c_cpp.taskDefinitions.detail.description": "Additional details of the task.",
Expand Down
2 changes: 1 addition & 1 deletion Extension/src/Debugger/configurationProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
if (buildTasks.length !== 0) {
configs = (await Promise.all(buildTasks.map<Promise<CppDebugConfiguration | undefined>>(async task => {
const definition: CppBuildTaskDefinition = task.definition as CppBuildTaskDefinition;
const compilerPath: string = definition.command;
const compilerPath: string = util.isString(definition.command) ? definition.command : definition.command.value;
// Filter out the tasks that has an invalid compiler path.
const compilerPathExists: boolean = path.isAbsolute(compilerPath) ?
// Absolute path, just check if it exists
Expand Down
70 changes: 43 additions & 27 deletions Extension/src/LanguageServer/cppBuildTaskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ const localize: nls.LocalizeFunc = nls.loadMessageBundle();
export interface CppBuildTaskDefinition extends TaskDefinition {
type: string;
label: string; // The label appears in tasks.json file.
command: string;
args: string[];
command: string | util.IQuotedString;
args: (string | util.IQuotedString)[];
options: cp.ExecOptions | cp.SpawnOptions | undefined;
}

Expand Down Expand Up @@ -166,20 +166,24 @@ export class CppBuildTaskProvider implements TaskProvider {
return result;
}

private getTask: (compilerPath: string, appendSourceToName: boolean, compilerArgs?: string[], definition?: CppBuildTaskDefinition, detail?: string) => Task = (compilerPath: string, appendSourceToName: boolean, compilerArgs?: string[], definition?: CppBuildTaskDefinition, detail?: string) => {
const compilerPathBase: string = path.basename(compilerPath);
private getTask: (compilerPath: string | util.IQuotedString, appendSourceToName: boolean, compilerArgs?: (string | util.IQuotedString)[], definition?: CppBuildTaskDefinition, detail?: string) => Task = (compilerPath: string | util.IQuotedString, appendSourceToName: boolean, compilerArgs?: (string | util.IQuotedString)[], definition?: CppBuildTaskDefinition, detail?: string) => {
const compilerPathString: string = util.isString(compilerPath) ? compilerPath : compilerPath.value;
const compilerPathBase: string = path.basename(compilerPathString);
const isCl: boolean = compilerPathBase.toLowerCase() === "cl.exe";
const isClang: boolean = !isCl && compilerPathBase.toLowerCase().includes("clang");
// Double-quote the command if needed.
let resolvedcompilerPath: string = isCl ? compilerPathBase : compilerPath;
resolvedcompilerPath = util.quoteArgument(resolvedcompilerPath);
const resolvedCompilerPathString: string = isCl ? compilerPathBase : compilerPathString;
let resolvedCompilerPath: string | util.IQuotedString = compilerPath;
if (isCl) {
resolvedCompilerPath = compilerPathBase;
}

if (!definition) {
const isWindows: boolean = os.platform() === 'win32';
const taskLabel: string = ((appendSourceToName && !compilerPathBase.startsWith(ext.configPrefix)) ?
ext.configPrefix : "") + compilerPathBase + " " + localize("build.active.file", "build active file");
const programName: string = util.defaultExePath();
let args: string[] = isCl ?
let args: (string | util.IQuotedString)[] = isCl ?
['/Zi', '/EHsc', '/nologo', `/Fe${programName}`, '${file}'] :
isClang ?
['-fcolor-diagnostics', '-fansi-escape-codes', '-g', '${file}', '-o', programName] :
Expand All @@ -188,30 +192,38 @@ export class CppBuildTaskProvider implements TaskProvider {
if (compilerArgs && compilerArgs.length > 0) {
args = args.concat(compilerArgs);
}
const cwd: string = isWindows && !isCl && !process.env.PATH?.includes(path.dirname(compilerPath)) ? path.dirname(compilerPath) : "${fileDirname}";
const cwd: string = isWindows && !isCl && !process.env.PATH?.includes(path.dirname(compilerPathString)) ? path.dirname(compilerPathString) : "${fileDirname}";
const options: cp.ExecOptions | cp.SpawnOptions | undefined = { cwd: cwd };
definition = {
type: CppBuildTaskProvider.CppBuildScriptType,
label: taskLabel,
command: isCl ? compilerPathBase : compilerPath,
command: compilerPath,
args: args,
options: options
};
if (isCl) {
definition.command = compilerPathBase;
}
}

const editor: TextEditor | undefined = window.activeTextEditor;
const folder: WorkspaceFolder | undefined = editor ? workspace.getWorkspaceFolder(editor.document.uri) : undefined;

const taskUsesActiveFile: boolean = definition.args.some(arg => arg.indexOf('${file}') >= 0); // Need to check this before ${file} is resolved
const taskUsesActiveFile: boolean = definition.args.some(arg => {
if (util.isString(arg)) {
return arg.indexOf('${file}') >= 0;
}
return arg.value.indexOf('${file}') >= 0;
}); // Need to check this before ${file} is resolved
const scope: WorkspaceFolder | TaskScope = folder ? folder : TaskScope.Workspace;
const task: CppBuildTask = new Task(definition, scope, definition.label, ext.CppSourceStr,
new CustomExecution(async (resolvedDefinition: TaskDefinition): Promise<Pseudoterminal> =>
// When the task is executed, this callback will run. Here, we setup for running the task.
new CustomBuildTaskTerminal(resolvedcompilerPath, resolvedDefinition.args, resolvedDefinition.options, { taskUsesActiveFile, insertStd: isClang && os.platform() === 'darwin' })
new CustomBuildTaskTerminal(resolvedCompilerPath, resolvedDefinition.args, resolvedDefinition.options, { taskUsesActiveFile, insertStd: isClang && os.platform() === 'darwin' })
), isCl ? '$msCompile' : '$gcc');

task.group = TaskGroup.Build;
task.detail = detail ? detail : localize("compiler.details", "compiler:") + " " + resolvedcompilerPath;
task.detail = detail ? detail : localize("compiler.details", "compiler:") + " " + resolvedCompilerPathString;

return task;
};
Expand Down Expand Up @@ -354,7 +366,7 @@ class CustomBuildTaskTerminal implements Pseudoterminal {
public get onDidClose(): Event<number> { return this.closeEmitter.event; }
private endOfLine: string = "\r\n";

constructor(private command: string, private args: string[], private options: cp.ExecOptions | cp.SpawnOptions | undefined, private buildOptions: BuildOptions) {
constructor(private command: string | util.IQuotedString, private args: (string | util.IQuotedString)[], private options: cp.ExecOptions | undefined, private buildOptions: BuildOptions) {
}

async open(_initialDimensions: TerminalDimensions | undefined): Promise<void> {
Expand All @@ -380,22 +392,29 @@ class CustomBuildTaskTerminal implements Pseudoterminal {

private async doBuild(): Promise<any> {
// Do build.
let command: string = util.resolveVariables(this.command);
let activeCommand: string = command;
let resolvedCommand: string | util.IQuotedString | undefined;
if (util.isString(this.command)) {
resolvedCommand = util.resolveVariables(this.command);
} else {
resolvedCommand = {
value: util.resolveVariables(this.command.value),
quoting: this.command.quoting
};
}

// Create the exe folder path if it doesn't exist.
const exePath: string | undefined = util.resolveVariables(util.findExePathInArgs(this.args));
util.createDirIfNotExistsSync(exePath);

this.args.forEach((value, index) => {
value = util.quoteArgument(util.resolveVariables(value));
Colengms marked this conversation as resolved.
Show resolved Hide resolved
activeCommand = activeCommand + " " + value;
this.args[index] = value;
if (util.isString(value)) {
this.args[index] = util.resolveVariables(value);
} else {
value.value = util.resolveVariables(value.value);
}
});
if (this.options) {
this.options.shell = true;
} else {
this.options = { "shell": true };
if (this.options === undefined) {
this.options = { };
}
if (this.options.cwd) {
this.options.cwd = util.resolveVariables(this.options.cwd.toString());
Expand Down Expand Up @@ -425,15 +444,12 @@ class CustomBuildTaskTerminal implements Pseudoterminal {
}
};

if (os.platform() === 'win32') {
command = `cmd /c chcp 65001>nul && ${command}`;
}

const activeCommand: string = util.buildShellCommandLine(resolvedCommand, this.command, this.args);
this.writeEmitter.fire(activeCommand + this.endOfLine);

let child: cp.ChildProcess | undefined;
try {
child = cp.spawn(command, this.args, this.options ? this.options : {});
child = cp.exec(activeCommand, this.options);
let error: string = "";
let stdout: string = "";
let stderr: string = "";
Expand Down
Loading
Loading