Skip to content

Commit 69338b8

Browse files
authored
Update cppbuild task behavior for args and command fields to match VS Code's shell task type (#12026)
1 parent af6287f commit 69338b8

File tree

5 files changed

+296
-42
lines changed

5 files changed

+296
-42
lines changed

Extension/package.json

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,78 @@
313313
"description": "%c_cpp.taskDefinitions.name.description%"
314314
},
315315
"command": {
316-
"type": "string",
317-
"description": "%c_cpp.taskDefinitions.command.description%"
316+
"oneOf": [
317+
{
318+
"type": "string"
319+
},
320+
{
321+
"type": "object",
322+
"required": [
323+
"value",
324+
"quoting"
325+
],
326+
"properties": {
327+
"value": {
328+
"type": "string",
329+
"description": "%c_cpp.taskDefinitions.args.value.description%"
330+
},
331+
"quoting": {
332+
"type": "string",
333+
"enum": [
334+
"escape",
335+
"strong",
336+
"weak"
337+
],
338+
"enumDescriptions": [
339+
"%c_cpp.taskDefinitions.args.quoting.escape.description%",
340+
"%c_cpp.taskDefinitions.args.quoting.strong.description%",
341+
"%c_cpp.taskDefinitions.args.quoting.weak.description%"
342+
],
343+
"default": "strong",
344+
"description": "%c_cpp.taskDefinitions.args.quoting.description%"
345+
}
346+
}
347+
}
348+
]
318349
},
319350
"args": {
320351
"type": "array",
321-
"description": "%c_cpp.taskDefinitions.args.description%"
352+
"description": "%c_cpp.taskDefinitions.args.description%",
353+
"items": {
354+
"oneOf": [
355+
{
356+
"type": "string"
357+
},
358+
{
359+
"type": "object",
360+
"required": [
361+
"value",
362+
"quoting"
363+
],
364+
"properties": {
365+
"value": {
366+
"type": "string",
367+
"description": "%c_cpp.taskDefinitions.args.value.description%"
368+
},
369+
"quoting": {
370+
"type": "string",
371+
"enum": [
372+
"escape",
373+
"strong",
374+
"weak"
375+
],
376+
"enumDescriptions": [
377+
"%c_cpp.taskDefinitions.args.quoting.escape.description%",
378+
"%c_cpp.taskDefinitions.args.quoting.strong.description%",
379+
"%c_cpp.taskDefinitions.args.quoting.weak.description%"
380+
],
381+
"default": "strong",
382+
"description": "%c_cpp.taskDefinitions.args.quoting.description%"
383+
}
384+
}
385+
}
386+
]
387+
}
322388
},
323389
"options": {
324390
"type": "object",

Extension/package.nls.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,11 @@
928928
"c_cpp.taskDefinitions.name.description": "The name of the task.",
929929
"c_cpp.taskDefinitions.command.description": "The path to either a compiler or script that performs compilation.",
930930
"c_cpp.taskDefinitions.args.description": "Additional arguments to pass to the compiler or compilation script.",
931+
"c_cpp.taskDefinitions.args.value.description": "The actual argument value.",
932+
"c_cpp.taskDefinitions.args.quoting.description": "How the argument value should be quoted.",
933+
"c_cpp.taskDefinitions.args.quoting.escape.description": "Escapes characters using the shell's escape character (e.g. \\ under bash).",
934+
"c_cpp.taskDefinitions.args.quoting.strong.description": "Quotes the argument using the shell's strong quote character (e.g. ' under bash).",
935+
"c_cpp.taskDefinitions.args.quoting.weak.description": "Quotes the argument using the shell's weak quote character (e.g. \" under bash).",
931936
"c_cpp.taskDefinitions.options.description": "Additional command options.",
932937
"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.",
933938
"c_cpp.taskDefinitions.detail.description": "Additional details of the task.",

Extension/src/Debugger/configurationProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,7 +413,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv
413413
if (buildTasks.length !== 0) {
414414
configs = (await Promise.all(buildTasks.map<Promise<CppDebugConfiguration | undefined>>(async task => {
415415
const definition: CppBuildTaskDefinition = task.definition as CppBuildTaskDefinition;
416-
const compilerPath: string = definition.command;
416+
const compilerPath: string = util.isString(definition.command) ? definition.command : definition.command.value;
417417
// Filter out the tasks that has an invalid compiler path.
418418
const compilerPathExists: boolean = path.isAbsolute(compilerPath) ?
419419
// Absolute path, just check if it exists

Extension/src/LanguageServer/cppBuildTaskProvider.ts

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ const localize: nls.LocalizeFunc = nls.loadMessageBundle();
2121
export interface CppBuildTaskDefinition extends TaskDefinition {
2222
type: string;
2323
label: string; // The label appears in tasks.json file.
24-
command: string;
25-
args: string[];
24+
command: string | util.IQuotedString;
25+
args: (string | util.IQuotedString)[];
2626
options: cp.ExecOptions | cp.SpawnOptions | undefined;
2727
}
2828

@@ -166,20 +166,24 @@ export class CppBuildTaskProvider implements TaskProvider {
166166
return result;
167167
}
168168

169-
private getTask: (compilerPath: string, appendSourceToName: boolean, compilerArgs?: string[], definition?: CppBuildTaskDefinition, detail?: string) => Task = (compilerPath: string, appendSourceToName: boolean, compilerArgs?: string[], definition?: CppBuildTaskDefinition, detail?: string) => {
170-
const compilerPathBase: string = path.basename(compilerPath);
169+
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) => {
170+
const compilerPathString: string = util.isString(compilerPath) ? compilerPath : compilerPath.value;
171+
const compilerPathBase: string = path.basename(compilerPathString);
171172
const isCl: boolean = compilerPathBase.toLowerCase() === "cl.exe";
172173
const isClang: boolean = !isCl && compilerPathBase.toLowerCase().includes("clang");
173174
// Double-quote the command if needed.
174-
let resolvedcompilerPath: string = isCl ? compilerPathBase : compilerPath;
175-
resolvedcompilerPath = util.quoteArgument(resolvedcompilerPath);
175+
const resolvedCompilerPathString: string = isCl ? compilerPathBase : compilerPathString;
176+
let resolvedCompilerPath: string | util.IQuotedString = compilerPath;
177+
if (isCl) {
178+
resolvedCompilerPath = compilerPathBase;
179+
}
176180

177181
if (!definition) {
178182
const isWindows: boolean = os.platform() === 'win32';
179183
const taskLabel: string = ((appendSourceToName && !compilerPathBase.startsWith(ext.configPrefix)) ?
180184
ext.configPrefix : "") + compilerPathBase + " " + localize("build.active.file", "build active file");
181185
const programName: string = util.defaultExePath();
182-
let args: string[] = isCl ?
186+
let args: (string | util.IQuotedString)[] = isCl ?
183187
['/Zi', '/EHsc', '/nologo', `/Fe${programName}`, '${file}'] :
184188
isClang ?
185189
['-fcolor-diagnostics', '-fansi-escape-codes', '-g', '${file}', '-o', programName] :
@@ -188,30 +192,38 @@ export class CppBuildTaskProvider implements TaskProvider {
188192
if (compilerArgs && compilerArgs.length > 0) {
189193
args = args.concat(compilerArgs);
190194
}
191-
const cwd: string = isWindows && !isCl && !process.env.PATH?.includes(path.dirname(compilerPath)) ? path.dirname(compilerPath) : "${fileDirname}";
195+
const cwd: string = isWindows && !isCl && !process.env.PATH?.includes(path.dirname(compilerPathString)) ? path.dirname(compilerPathString) : "${fileDirname}";
192196
const options: cp.ExecOptions | cp.SpawnOptions | undefined = { cwd: cwd };
193197
definition = {
194198
type: CppBuildTaskProvider.CppBuildScriptType,
195199
label: taskLabel,
196-
command: isCl ? compilerPathBase : compilerPath,
200+
command: compilerPath,
197201
args: args,
198202
options: options
199203
};
204+
if (isCl) {
205+
definition.command = compilerPathBase;
206+
}
200207
}
201208

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

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

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

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

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

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

381393
private async doBuild(): Promise<any> {
382394
// Do build.
383-
let command: string = util.resolveVariables(this.command);
384-
let activeCommand: string = command;
395+
let resolvedCommand: string | util.IQuotedString | undefined;
396+
if (util.isString(this.command)) {
397+
resolvedCommand = util.resolveVariables(this.command);
398+
} else {
399+
resolvedCommand = {
400+
value: util.resolveVariables(this.command.value),
401+
quoting: this.command.quoting
402+
};
403+
}
385404

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

390409
this.args.forEach((value, index) => {
391-
value = util.quoteArgument(util.resolveVariables(value));
392-
activeCommand = activeCommand + " " + value;
393-
this.args[index] = value;
410+
if (util.isString(value)) {
411+
this.args[index] = util.resolveVariables(value);
412+
} else {
413+
value.value = util.resolveVariables(value.value);
414+
}
394415
});
395-
if (this.options) {
396-
this.options.shell = true;
397-
} else {
398-
this.options = { "shell": true };
416+
if (this.options === undefined) {
417+
this.options = { };
399418
}
400419
if (this.options.cwd) {
401420
this.options.cwd = util.resolveVariables(this.options.cwd.toString());
@@ -425,15 +444,12 @@ class CustomBuildTaskTerminal implements Pseudoterminal {
425444
}
426445
};
427446

428-
if (os.platform() === 'win32') {
429-
command = `cmd /c chcp 65001>nul && ${command}`;
430-
}
431-
447+
const activeCommand: string = util.buildShellCommandLine(resolvedCommand, this.command, this.args);
432448
this.writeEmitter.fire(activeCommand + this.endOfLine);
433449

434450
let child: cp.ChildProcess | undefined;
435451
try {
436-
child = cp.spawn(command, this.args, this.options ? this.options : {});
452+
child = cp.exec(activeCommand, this.options);
437453
let error: string = "";
438454
let stdout: string = "";
439455
let stderr: string = "";

0 commit comments

Comments
 (0)