compilerArgs entries fix (#143)
* 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:
Родитель
bc710c5fff
Коммит
4a02fb0df3
|
@ -578,7 +578,7 @@ export function getBuildLogForConfiguration(configuration: string | undefined):
|
||||||
logger.message('Found build log path setting "' + configurationBuildLog + '" defined for configuration "' + configuration);
|
logger.message('Found build log path setting "' + configurationBuildLog + '" defined for configuration "' + configuration);
|
||||||
|
|
||||||
if (!path.isAbsolute(configurationBuildLog)) {
|
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 + '"');
|
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> {
|
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.");
|
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
|
// Delete the extension log file, if exists
|
||||||
let extensionLog : string | undefined = configuration.getExtensionLog();
|
let extensionLog : string | undefined = configuration.getExtensionLog();
|
||||||
if (extensionLog && util.checkFileExistsSync(extensionLog)) {
|
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
|
// Read configuration info from settings
|
||||||
|
|
|
@ -52,7 +52,7 @@ export class Launcher implements vscode.Disposable {
|
||||||
if (launchConfiguration) {
|
if (launchConfiguration) {
|
||||||
return launchConfiguration.cwd;
|
return launchConfiguration.cwd;
|
||||||
} else {
|
} 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");
|
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) {
|
if (result.returnCode !== ConfigureBuildReturnCodeTypes.success) {
|
||||||
logger.message(`Target ${target} failed to build.`);
|
logger.message(`Target ${target} failed to build.`);
|
||||||
} else {
|
} else {
|
||||||
|
@ -480,7 +480,7 @@ export async function generateParseContent(progress: vscode.Progress<{}>,
|
||||||
}
|
}
|
||||||
}, 5 * 1000);
|
}, 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);
|
clearInterval(timeout);
|
||||||
let elapsedTime: number = util.elapsedTimeSince(startTime);
|
let elapsedTime: number = util.elapsedTimeSince(startTime);
|
||||||
logger.message(`Generating dry-run elapsed time: ${elapsedTime}`);
|
logger.message(`Generating dry-run elapsed time: ${elapsedTime}`);
|
||||||
|
@ -656,7 +656,7 @@ export async function runPreConfigureScript(progress: vscode.Progress<{}>, scrip
|
||||||
logger.messageNoCR(result, "Normal");
|
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 (result.returnCode === ConfigureBuildReturnCodeTypes.success) {
|
||||||
if (someErr) {
|
if (someErr) {
|
||||||
// Depending how the preconfigure scripts (and any inner called sub-scripts) are written,
|
// Depending how the preconfigure scripts (and any inner called sub-scripts) are written,
|
||||||
|
|
117
src/parser.ts
117
src/parser.ts
|
@ -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.
|
// 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.
|
// Make it uniform to \n to ease other processing later.
|
||||||
preprocessTasks.push(function (): void {
|
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.
|
// Some compiler/linker commands are split on multiple lines.
|
||||||
|
@ -364,21 +364,32 @@ async function parseLineAsTool(
|
||||||
// The result is passed to IntelliSense custom configuration provider as compilerArgs.
|
// 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
|
// 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.
|
// 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
|
// Identify the non value part of the switch: prefix, switch name
|
||||||
// and what may separate this from an eventual switch value
|
// and what may separate this from an eventual switch value
|
||||||
let switches: string[] = [];
|
let switches: string[] = [];
|
||||||
let regExpStr: string = "(^|\\s+)(--|-" +
|
let regExpStr: string = "(^|\\s+)(--|-" +
|
||||||
// On Win32 allow '/' as switch prefix as well,
|
// On Win32 allow '/' as switch prefix as well,
|
||||||
// otherwise it conflicts with path character
|
// otherwise it conflicts with path character
|
||||||
(process.platform === "win32" ? "|\\/" : "") +
|
(process.platform === "win32" ? "|\\/" : "") +
|
||||||
")([a-zA-Z0-9_]+)";
|
")([a-zA-Z0-9_]+)";
|
||||||
let regexp: RegExp = RegExp(regExpStr, "mg");
|
let regexp: RegExp = RegExp(regExpStr, "mg");
|
||||||
let match1: RegExpExecArray | null;
|
let match1: RegExpExecArray | null;
|
||||||
let match2: RegExpExecArray | null;
|
let match2: RegExpExecArray | null;
|
||||||
let index1: number = -1;
|
let index1: number = -1;
|
||||||
let index2: 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
|
// 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
|
// that is between them. If the current match is the last one, then we will analyze
|
||||||
// everything until the end of line.
|
// everything until the end of line.
|
||||||
|
@ -414,32 +425,75 @@ function parseAnySwitchFromToolArguments(args: string, excludeArgs: string[]): s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!exclude) {
|
if (!exclude) {
|
||||||
// The other parser helpers differ from this one by the fact that they know
|
compilerArgRegions += partialArgs;
|
||||||
// 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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match1 = match2;
|
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;
|
return switches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -797,7 +851,7 @@ export async function parseCustomConfigProvider(cancel: vscode.CancellationToken
|
||||||
|
|
||||||
// Current path starts with workspace root and can be modified
|
// Current path starts with workspace root and can be modified
|
||||||
// with prompt commands like cd, cd-, pushd/popd or with -C make switch
|
// 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];
|
let currentPathHistory: string[] = [currentPath];
|
||||||
|
|
||||||
// Read the dry-run output line by line, searching for compilers and directory changing commands
|
// 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 numberOfLines: number = dryRunOutputLines.length;
|
||||||
let index: number = 0;
|
let index: number = 0;
|
||||||
let done: boolean = false;
|
let done: boolean = false;
|
||||||
|
|
||||||
async function doParsingChunk(): Promise<void> {
|
async function doParsingChunk(): Promise<void> {
|
||||||
let chunkIndex: number = 0;
|
let chunkIndex: number = 0;
|
||||||
while (index < numberOfLines && chunkIndex <= chunkSize) {
|
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)
|
// Exclude switches that are being processed separately (I, FI, include, D, std)
|
||||||
// and switches that don't affect IntelliSense but are causing errors.
|
// and switches that don't affect IntelliSense but are causing errors.
|
||||||
let compilerArgs: string[] = [];
|
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
|
// Parse and log the includes, forced includes and the defines
|
||||||
let includes: string[] = parseMultipleSwitchFromToolArguments(compilerTool.arguments, 'I');
|
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
|
// Current path starts with workspace root and can be modified
|
||||||
// with prompt commands like cd, cd-, pushd/popd or with -C make switch
|
// 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];
|
let currentPathHistory: string[] = [currentPath];
|
||||||
|
|
||||||
// array of full path executables built by this makefile
|
// 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
|
// Reset the current path since we are going to analyze path transitions again
|
||||||
// with this second pass through the dry-run output lines,
|
// with this second pass through the dry-run output lines,
|
||||||
// while building the launch custom provider data.
|
// while building the launch custom provider data.
|
||||||
currentPath = vscode.workspace.rootPath || "";
|
currentPath = util.getWorkspaceRoot();
|
||||||
currentPathHistory = [currentPath];
|
currentPathHistory = [currentPath];
|
||||||
|
|
||||||
// Since an executable can be called without its extension,
|
// Since an executable can be called without its extension,
|
||||||
|
|
24
src/util.ts
24
src/util.ts
|
@ -77,7 +77,25 @@ export function tmpDir(): string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate whether a string looks like a path or not,
|
// 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
|
// without using fs.stat, since dry-run may output tools
|
||||||
// that are not found yet at certain locations,
|
// that are not found yet at certain locations,
|
||||||
// without running the prep targets that would copy them there
|
// without running the prep targets that would copy them there
|
||||||
|
@ -165,7 +183,7 @@ export async function killTree(progress: vscode.Progress<{}>, pid: number): Prom
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
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) {
|
if (!!stdoutStr.length) {
|
||||||
children = stdoutStr.split('\n').map((line: string) => Number.parseInt(line));
|
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.
|
// Helper to make paths absolute until the extension handles variables expansion.
|
||||||
export function resolvePathToRoot(relPath: string): string {
|
export function resolvePathToRoot(relPath: string): string {
|
||||||
if (!path.isAbsolute(relPath)) {
|
if (!path.isAbsolute(relPath)) {
|
||||||
return path.join(vscode.workspace.rootPath || "", relPath);
|
return path.join(getWorkspaceRoot(), relPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
return relPath;
|
return relPath;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче