* Send different compilerArgs entries for bundled switches or pairs of switch-value if separated by space as a shell would see them

* Use a shell script to parse compiler arguments that are sent to CppTools.

* Fix running script with space in path. Use vscode.workspace.workspaceFolders instead of vscode.workspace.rootPath
This commit is contained in:
Andreea Isac 2021-04-28 01:17:29 -07:00 коммит произвёл GitHub
Родитель bc710c5fff
Коммит 4a02fb0df3
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
7 изменённых файлов: 126 добавлений и 41 удалений

Просмотреть файл

@ -578,7 +578,7 @@ export function getBuildLogForConfiguration(configuration: string | undefined):
logger.message('Found build log path setting "' + configurationBuildLog + '" defined for configuration "' + configuration);
if (!path.isAbsolute(configurationBuildLog)) {
configurationBuildLog = path.join(vscode.workspace.rootPath || "", configurationBuildLog);
configurationBuildLog = path.join(util.getWorkspaceRoot(), configurationBuildLog);
logger.message('Resolving build log path to "' + configurationBuildLog + '"');
}

Просмотреть файл

@ -102,7 +102,7 @@ export class CppConfigurationProvider implements cpp.CustomConfigurationProvider
}
public async provideFolderBrowseConfiguration(_uri: vscode.Uri): Promise<cpp.WorkspaceBrowseConfiguration> {
if (_uri.fsPath !== vscode.workspace.rootPath) {
if (_uri.fsPath !== util.getWorkspaceRoot()) {
logger.message("Makefile Tools supports single root for now.");
}

Просмотреть файл

@ -322,7 +322,19 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
// Delete the extension log file, if exists
let extensionLog : string | undefined = configuration.getExtensionLog();
if (extensionLog && util.checkFileExistsSync(extensionLog)) {
fs.unlinkSync(extensionLog);
util.deleteFileSync(extensionLog);
}
// Delete the script that is created by this extension in the temporary folder
// with the purpose of spliting a compilation command fragment into switch arguments
// as the shell sees them. See more about this script in parser.ts, parseAnySwitchFromToolArguments.
// We need to delete this file occasionally to ensure that the extension will not use indefinitely
// an eventual old version, especially because for performance reasons we don't create this file
// every time we use it (the configure process creates it every time it's not found on disk).
// Deleting this script here ensures that every new VSCode instance will operate on up to date script functionality.
let parseCompilerArgsScript: string = util.parseCompilerArgsScriptFile();
if (util.checkFileExistsSync(parseCompilerArgsScript)) {
util.deleteFileSync(parseCompilerArgsScript);
}
// Read configuration info from settings

Просмотреть файл

@ -52,7 +52,7 @@ export class Launcher implements vscode.Disposable {
if (launchConfiguration) {
return launchConfiguration.cwd;
} else {
return vscode.workspace.rootPath || "";
return util.getWorkspaceRoot();
}
}

Просмотреть файл

@ -314,7 +314,7 @@ export async function doBuildTarget(progress: vscode.Progress<{}>, target: strin
logger.messageNoCR(result, "Normal");
};
const result: util.SpawnProcessResult = await util.spawnChildProcess(configuration.getConfigurationMakeCommand(), makeArgs, vscode.workspace.rootPath || "", stdout, stderr);
const result: util.SpawnProcessResult = await util.spawnChildProcess(configuration.getConfigurationMakeCommand(), makeArgs, util.getWorkspaceRoot(), stdout, stderr);
if (result.returnCode !== ConfigureBuildReturnCodeTypes.success) {
logger.message(`Target ${target} failed to build.`);
} else {
@ -480,7 +480,7 @@ export async function generateParseContent(progress: vscode.Progress<{}>,
}
}, 5 * 1000);
const result: util.SpawnProcessResult = await util.spawnChildProcess(configuration.getConfigurationMakeCommand(), makeArgs, vscode.workspace.rootPath || "", stdout, stderr);
const result: util.SpawnProcessResult = await util.spawnChildProcess(configuration.getConfigurationMakeCommand(), makeArgs, util.getWorkspaceRoot(), stdout, stderr);
clearInterval(timeout);
let elapsedTime: number = util.elapsedTimeSince(startTime);
logger.message(`Generating dry-run elapsed time: ${elapsedTime}`);
@ -656,7 +656,7 @@ export async function runPreConfigureScript(progress: vscode.Progress<{}>, scrip
logger.messageNoCR(result, "Normal");
};
const result: util.SpawnProcessResult = await util.spawnChildProcess(runCommand, scriptArgs, vscode.workspace.rootPath || "", stdout, stderr);
const result: util.SpawnProcessResult = await util.spawnChildProcess(runCommand, scriptArgs, util.getWorkspaceRoot(), stdout, stderr);
if (result.returnCode === ConfigureBuildReturnCodeTypes.success) {
if (someErr) {
// Depending how the preconfigure scripts (and any inner called sub-scripts) are written,

Просмотреть файл

@ -141,7 +141,7 @@ export async function preprocessDryRunOutput(cancel: vscode.CancellationToken, d
// Sometimes the ending of lines ends up being a mix and match of \n and \r\n.
// Make it uniform to \n to ease other processing later.
preprocessTasks.push(function (): void {
preprocessedDryRunOutputStr = preprocessedDryRunOutputStr.replace(/\\r\\n/mg, "\n");
preprocessedDryRunOutputStr = preprocessedDryRunOutputStr.replace(/\r\n/mg, "\n");
});
// Some compiler/linker commands are split on multiple lines.
@ -364,7 +364,11 @@ async function parseLineAsTool(
// The result is passed to IntelliSense custom configuration provider as compilerArgs.
// excludeArgs helps with narrowing down the search, when we know for sure that we're not
// interested in some switches. For example, -D, -I, -FI, -include, -std are treated separately.
function parseAnySwitchFromToolArguments(args: string, excludeArgs: string[]): string[] {
// Once we identified what looks to be the switches in the given command line, for each region
// between two consecutive switches we let the shell parse it into arguments via a script invocation
// (instead of us using other parser helpers in this module) to be in sync with how CppTools
// expects the compiler arguments to be passed in.
async function parseAnySwitchFromToolArguments(args: string, excludeArgs: string[]): Promise<string[]> {
// Identify the non value part of the switch: prefix, switch name
// and what may separate this from an eventual switch value
let switches: string[] = [];
@ -379,6 +383,13 @@ function parseAnySwitchFromToolArguments(args: string, excludeArgs: string[]): s
let index1: number = -1;
let index2: number = -1;
// This contains all the compilation command fragments in between two different consecutive switches
// (except the ones we plan to ignore, specified by excludeArgs).
// Once this function is done concatenating into compilerArgRegions,
// we call the compiler args parsing script once for the whole list of regions
// (as opposed to invoking it for each fragment separately).
let compilerArgRegions: string = "";
// With every loop iteration we need 2 switch matches so that we analyze the text
// that is between them. If the current match is the last one, then we will analyze
// everything until the end of line.
@ -414,32 +425,75 @@ function parseAnySwitchFromToolArguments(args: string, excludeArgs: string[]): s
}
if (!exclude) {
// The other parser helpers differ from this one by the fact that they know
// what switch they are looking for. This helper first identifies anything
// that looks like a switch and then calls parseMultipleSwitchFromToolArguments
// which knows how to parse complex scenarios of spaces, quotes and other characters.
// Don't allow parseMultipleSwitchFromToolArguments to remove surrounding quotes for switch values.
let swiValues: string[] = parseMultipleSwitchFromToolArguments(partialArgs, swi, false);
// If no values are found, it means the switch has simple syntax.
// Add this to the array.
if (swiValues.length === 0) {
swiValues.push(swi);
}
swiValues.forEach(value => {
// The end of the current switch value
let index3: number = partialArgs.indexOf(value) + value.length;
let finalSwitch: string = partialArgs.substring(0, index3);
finalSwitch = finalSwitch.trim();
switches.push(finalSwitch);
});
compilerArgRegions += partialArgs;
}
match1 = match2;
}
let parseCompilerArgsScriptFile: string = util.parseCompilerArgsScriptFile();
let parseCompilerArgsScriptContent: string;
if (process.platform === "win32") {
// There is a potential problem with the windows version of the script:
// A fragment like "-sw1,-sw2,-sw3" gets split by comma and a fragment like
// "-SwDef=Val" is split by equal. Opened GitHub issue
// https://github.com/microsoft/vscode-makefile-tools/issues/149.
// For now, these scenarios don't happen on windows (the comma syntax is linux only
// and the equal syntax is encountered for defines and c/c++ standard which are parsed
// separately, not via this script invocation).
parseCompilerArgsScriptContent = `@echo off\r\n`;
parseCompilerArgsScriptContent += `for %%i in (%*) do echo %%i \r\n`;
} else {
parseCompilerArgsScriptContent = `#!/bin/bash \n`;
parseCompilerArgsScriptContent += `while (( "$#" )); do \n`;
parseCompilerArgsScriptContent += ` echo $1 \n`;
parseCompilerArgsScriptContent += ` shift \n`;
parseCompilerArgsScriptContent += `done \n`;
}
// Create the script only when not found. The extension makes sure to delete it regularly
// (at project load time).
if (!util.checkFileExistsSync(parseCompilerArgsScriptFile)) {
util.writeFile(parseCompilerArgsScriptFile, parseCompilerArgsScriptContent);
}
let scriptArgs: string[] = [];
let runCommand: string;
if (process.platform === 'win32') {
runCommand = "cmd";
scriptArgs.push("/c");
scriptArgs.push(`""${parseCompilerArgsScriptFile}" ${compilerArgRegions}"`);
} else {
runCommand = "/bin/bash";
scriptArgs.push("-c");
scriptArgs.push(`"source '${parseCompilerArgsScriptFile}' ${compilerArgRegions}"`);
}
try {
let stdout: any = (result: string): void => {
let results: string[] = result.replace(/\r\n/mg, "\n").split("\n");
// In case of concatenated separators, the shell sees different empty arguments
// which we can remove (most common is more spaces not being seen as a single space).
results.forEach(res => {
if (res !== "") {
switches.push(res.trim());
}
});
};
let stderr: any = (result: string): void => {
logger.messageNoCR(`Error while running the compiler args parser script '${parseCompilerArgsScriptFile}'` +
`for regions "${compilerArgRegions}": "${result}"`, "Normal");
};
const result: util.SpawnProcessResult = await util.spawnChildProcess(runCommand, scriptArgs, util.getWorkspaceRoot(), stdout, stderr);
if (result.returnCode !== 0) {
logger.messageNoCR(`The compiler args parser script '${parseCompilerArgsScriptFile}' failed with error code ${result.returnCode}`, "Normal");
}
} catch (error) {
logger.message(error);
}
return switches;
}
@ -797,7 +851,7 @@ export async function parseCustomConfigProvider(cancel: vscode.CancellationToken
// Current path starts with workspace root and can be modified
// with prompt commands like cd, cd-, pushd/popd or with -C make switch
let currentPath: string = vscode.workspace.rootPath || "";
let currentPath: string = util.getWorkspaceRoot();
let currentPathHistory: string[] = [currentPath];
// Read the dry-run output line by line, searching for compilers and directory changing commands
@ -806,6 +860,7 @@ export async function parseCustomConfigProvider(cancel: vscode.CancellationToken
let numberOfLines: number = dryRunOutputLines.length;
let index: number = 0;
let done: boolean = false;
async function doParsingChunk(): Promise<void> {
let chunkIndex: number = 0;
while (index < numberOfLines && chunkIndex <= chunkSize) {
@ -833,7 +888,7 @@ export async function parseCustomConfigProvider(cancel: vscode.CancellationToken
// Exclude switches that are being processed separately (I, FI, include, D, std)
// and switches that don't affect IntelliSense but are causing errors.
let compilerArgs: string[] = [];
compilerArgs = parseAnySwitchFromToolArguments(compilerTool.arguments, ["I", "FI", "include", "D", "std", "MF"]);
compilerArgs = await parseAnySwitchFromToolArguments(compilerTool.arguments, ["I", "FI", "include", "D", "std", "MF"]);
// Parse and log the includes, forced includes and the defines
let includes: string[] = parseMultipleSwitchFromToolArguments(compilerTool.arguments, 'I');
@ -954,7 +1009,7 @@ export async function parseLaunchConfigurations(cancel: vscode.CancellationToken
// Current path starts with workspace root and can be modified
// with prompt commands like cd, cd-, pushd/popd or with -C make switch
let currentPath: string = vscode.workspace.rootPath || "";
let currentPath: string = util.getWorkspaceRoot();
let currentPathHistory: string[] = [currentPath];
// array of full path executables built by this makefile
@ -1156,7 +1211,7 @@ export async function parseLaunchConfigurations(cancel: vscode.CancellationToken
// Reset the current path since we are going to analyze path transitions again
// with this second pass through the dry-run output lines,
// while building the launch custom provider data.
currentPath = vscode.workspace.rootPath || "";
currentPath = util.getWorkspaceRoot();
currentPathHistory = [currentPath];
// Since an executable can be called without its extension,

Просмотреть файл

@ -77,6 +77,24 @@ export function tmpDir(): string {
}
}
// Returns the full path to a temporary script generated by the extension
// and used to parse any additional compiler switches that need to be sent to CppTools.
export function parseCompilerArgsScriptFile(): string {
let scriptFile: string = path.join(tmpDir(), "parseCompilerArgs");
if (process.platform === "win32") {
scriptFile += ".bat";
} else {
scriptFile += ".sh";
}
return scriptFile;
}
export function getWorkspaceRoot(): string {
return vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : "";
}
// Evaluate whether a string looks like a path or not,
// without using fs.stat, since dry-run may output tools
// that are not found yet at certain locations,
@ -165,7 +183,7 @@ export async function killTree(progress: vscode.Progress<{}>, pid: number): Prom
};
try {
const result: SpawnProcessResult = await spawnChildProcess('pgrep', ['-P', pid.toString()], vscode.workspace.rootPath || "", stdout);
const result: SpawnProcessResult = await spawnChildProcess('pgrep', ['-P', pid.toString()], getWorkspaceRoot(), stdout);
if (!!stdoutStr.length) {
children = stdoutStr.split('\n').map((line: string) => Number.parseInt(line));
@ -451,7 +469,7 @@ export function reportDryRunError(dryrunOutputFile: string): void {
// Helper to make paths absolute until the extension handles variables expansion.
export function resolvePathToRoot(relPath: string): string {
if (!path.isAbsolute(relPath)) {
return path.join(vscode.workspace.rootPath || "", relPath);
return path.join(getWorkspaceRoot(), relPath);
}
return relPath;