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

Bugfix miDebuggerPath problem with fallback behavior #429

Merged
merged 21 commits into from
Jun 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ addons:
key_url: https://packages.microsoft.com/keys/microsoft.asc
packages:
- powershell
- gdb

before_install:
- |
Expand Down
99 changes: 63 additions & 36 deletions src/cmake-tools.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/**
* Root of the extension
*/
import {CMakeCache} from '@cmt/cache';
import {CMakeExecutable, getCMakeExecutableInformation} from '@cmt/cmake/cmake-executable';
import * as debugger_config from '@cmt/debugger';
import {versionToString} from '@cmt/util';
import {DirectoryContext} from '@cmt/workspace';
import * as http from 'http';
Expand Down Expand Up @@ -696,7 +698,12 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
/**
* Implementation of `cmake.selectLaunchTarget`
*/
async selectLaunchTarget(): Promise<string|null> {
async selectLaunchTarget(): Promise<string|null> { return this.setLaunchTargetByName(); }

/**
* Used by vscode and as test interface
*/
async setLaunchTargetByName(name?: string|null) {
if (await this._needsReconfigure()) {
const rc = await this.configure();
if (rc !== 0) {
Expand All @@ -713,7 +720,12 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
description: '',
detail: e.path,
}));
const chosen = await vscode.window.showQuickPick(choices);
let chosen: {label: string, detail: string}|undefined = undefined;
if (!name) {
chosen = await vscode.window.showQuickPick(choices);
} else {
chosen = choices.find(choice => choice.label == name);
}
if (!chosen) {
return null;
}
Expand All @@ -722,12 +734,21 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
return chosen.detail;
}

async getCurrentLaunchTarget(): Promise<api.ExecutableTarget|null> {
const target_name = this.workspaceContext.state.launchTargetName;
const target = (await this.executableTargets).find(e => e.name == target_name);

if (!target) {
return null;
}
return target;
}

/**
* Implementation of `cmake.launchTargetPath`
*/
async launchTargetPath(): Promise<string|null> {
const target_name = this.workspaceContext.state.launchTargetName;
const chosen = (await this.executableTargets).find(e => e.name == target_name);
const chosen = await this.getCurrentLaunchTarget();
if (!chosen) {
log.showChannel();
log.warning('=======================================================');
Expand All @@ -740,15 +761,14 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
return chosen.path;
}

launchTargetProgramPath(): Promise<string|null> { return this.launchTargetPath(); }

async getLaunchTargetPath(): Promise<string|null> {
const current = await this.launchTargetPath();
async getOrSelectLaunchTarget(): Promise<api.ExecutableTarget|null> {
const current = await this.getCurrentLaunchTarget();
if (current) {
return current;
}
// Ask the user if we don't already have a target
const chosen = await this.selectLaunchTarget();
await this.selectLaunchTarget();
const chosen = await this.getCurrentLaunchTarget();
return chosen;
}

Expand All @@ -774,42 +794,49 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
});
return null;
}
// Ensure that we've configured the project already. If we haven't, `getLaunchTargetPath` won't see any executable
// targets and may show an uneccessary prompt to the user
// Ensure that we've configured the project already. If we haven't, `getOrSelectLaunchTarget` won't see any
// executable targets and may show an uneccessary prompt to the user
if (await this._needsReconfigure()) {
const rc = await this.configure();
if (rc !== 0) {
log.debug('Configuration of project failed.');
return null;
}
}
const target_path = await this.getLaunchTargetPath();
if (!target_path) {
const target = await this.getOrSelectLaunchTarget();
if (!target) {
// The user has nothing selected and cancelled the prompt to select a target.
log.debug('No target selected.');
return null;
}
const is_msvc
= drv.compilerID ? drv.compilerID.includes('MSVC') : (drv.linkerID ? drv.linkerID.includes('MSVC') : false);
const mi_mode = process.platform == 'darwin' ? 'lldb' : 'gdb';
const debug_config: vscode.DebugConfiguration = {
type: is_msvc ? 'cppvsdbg' : 'cppdbg',
name: `Debug ${target_path}`,
request: 'launch',
cwd: '${workspaceRoot}',
args: [],
MIMode: mi_mode,
};
if (mi_mode == 'gdb') {
debug_config['setupCommands'] = [
{
description: 'Enable pretty-printing for gdb',
text: '-enable-pretty-printing',
ignoreFailures: true,
},
];

let debug_config;
try {
const cache = await CMakeCache.fromPath(drv.cachePath);
debug_config = await debugger_config.getDebugConfigurationFromCache(cache, target, process.platform);

log.info( "Debug configuration from cache: ", JSON.stringify(debug_config));
} catch (error) {
vscode.window
.showErrorMessage(error.message, {
title: 'Learn more',
isLearnMore: true,
})
.then(item => {
if (item && item.isLearnMore) {
open('https://vector-of-bool.github.io/docs/vscode-cmake-tools/debugging.html');
}
});
log.debug('Problem to get debug from cache.', error);
return null;
}

// add debug configuration from settings
const user_config = this.workspaceContext.config.debugConfig;
Object.assign(debug_config, user_config);
debug_config.program = target_path;
log.info( "Starting debugger with following configuration. ", JSON.stringify({
workspace: vscode.workspace.workspaceFolders![0].uri.toString(),
config: debug_config}));
await vscode.debug.startDebugging(vscode.workspace.workspaceFolders![0], debug_config);
return vscode.debug.activeDebugSession!;
}
Expand All @@ -820,15 +847,15 @@ export class CMakeTools implements vscode.Disposable, api.CMakeToolsAPI {
* Implementation of `cmake.launchTarget`
*/
async launchTarget() {
const target_path = await this.getLaunchTargetPath();
if (!target_path) {
const target = await this.getOrSelectLaunchTarget();
if (!target) {
// The user has nothing selected and cancelled the prompt to select
// a target.
return null;
}
if (!this._launchTerminal)
this._launchTerminal = vscode.window.createTerminal('CMake/Launch');
this._launchTerminal.sendText(target_path);
this._launchTerminal.sendText(target.path);
this._launchTerminal.show();
return this._launchTerminal;
}
Expand Down
165 changes: 165 additions & 0 deletions src/debugger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import {ExecutableTarget} from '@cmt/api';
import {CMakeCache} from '@cmt/cache';
import * as proc from '@cmt/proc';


export enum DebuggerType {
VISUALSTUDIO = 'Visual Studio',
LLDB = 'LLDB',
GDB = 'GDB',
// LAUNCH // Future
}

/**
* Describes configuration options for debugger in kit
*/
export interface DebuggerConfiguration {
/**
* Identifier of the type to launch
*/
type: DebuggerType;

/**
* Path to gdb or lldb executable.
*/
debuggerPath?: string;

/**
* Name of a existing launch configuration
*/
// launchConfiguration?: string; // Future
}


export interface Configuration {
type: string;
name: string;
request: string;
[key: string]: any;
}

async function createGDBDebugConfiguration(debuggerPath: string, target: ExecutableTarget): Promise<Configuration> {
if (!await checkDebugger(debuggerPath)) {
debuggerPath = 'gdb';
if (!await checkDebugger(debuggerPath)) {
throw new Error(`Unable to find GDB in default search path and ${debuggerPath}.`);
}
}

return {
type: 'cppdbg',
name: `Debug ${target.name}`,
request: 'launch',
cwd: '${workspaceRoot}',
args: [],
MIMode: 'gdb',
miDebuggerPath: debuggerPath,
setupCommands: [
{
description: 'Enable pretty-printing for gdb',
text: '-enable-pretty-printing',
ignoreFailures: true,
},
],
program: target.path
};
}

async function createLLDBDebugConfiguration(debuggerPath: string, target: ExecutableTarget): Promise<Configuration> {
if (!await checkDebugger(debuggerPath)) {
throw new Error(`Unable to find GDB in default search path and ${debuggerPath}.`);
}

return {
type: 'cppdbg',
name: `Debug ${target.name}`,
request: 'launch',
cwd: '${workspaceRoot}',
args: [],
MIMode: 'lldb',
miDebuggerPath: debuggerPath,
program: target.path
};
}

function createMSVCDebugConfiguration(target: ExecutableTarget): Configuration {
return {
type: 'cppvsdbg',
name: `Debug ${target.name}`,
request: 'launch',
cwd: '${workspaceRoot}',
args: [],
program: target.path
};
}

const possible_debuggers: {
[debugger_name: string]:
{mi_mode: string, config_factory: (debugger_path: string, target: ExecutableTarget) => Promise<Configuration>}
}
= {
gdb: {mi_mode: 'gdb', config_factory: createGDBDebugConfiguration},
lldb: {mi_mode: 'lldb', config_factory: createLLDBDebugConfiguration}
};

function searchForCompilerPathInCache(cache: CMakeCache): string|null {
const languages = ['CXX', 'C', 'CUDA'];
for (const lang of languages) {
const entry = cache.get(`CMAKE_${lang}_COMPILER`);
if (!entry) {
continue;
}
return entry.value as string;
}
return null;
}


export async function getDebugConfigurationFromCache(cache: CMakeCache, target: ExecutableTarget, platform: string):
Promise<Configuration> {
const entry = cache.get('CMAKE_LINKER');
if (entry !== null) {
const linker = entry.value as string;
const is_msvc_linker = linker.endsWith('link.exe');
if (is_msvc_linker) {
return createMSVCDebugConfiguration(target);
}
}

const compiler_path = searchForCompilerPathInCache(cache);
if (compiler_path === null) {
throw Error('No compiler found in cache file.'); // MSVC should be already found by CMAKE_LINKER
}

const clang_compiler_regex = /(clang[\+]{0,2})+(?!-cl)/gi;
let clang_debugger_path = compiler_path.replace(clang_compiler_regex, 'lldb');
if ((clang_debugger_path.search(new RegExp('lldb')) != -1) && await checkDebugger(clang_debugger_path)) {
return createLLDBDebugConfiguration(clang_debugger_path, target);
} else {
clang_debugger_path = compiler_path.replace(clang_compiler_regex, 'gdb');

if ((clang_debugger_path.search(new RegExp('gdb')) != -1)) {
return createGDBDebugConfiguration(clang_debugger_path, target);
}
}

const debugger_name = platform == 'darwin' ? 'lldb' : 'gdb';
const description = possible_debuggers[debugger_name];
const gcc_compiler_regex = /(g\+\+|gcc)+/gi;
const gdb_debugger_path = compiler_path.replace(gcc_compiler_regex, description.mi_mode);
if (gdb_debugger_path.search(new RegExp(description.mi_mode)) != -1) {
return description.config_factory(gdb_debugger_path, target);
}

const is_msvc_compiler = compiler_path.endsWith('cl.exe');
if (is_msvc_compiler) {
return createMSVCDebugConfiguration(target);
}

return {type: '', name: '', request: ''} as Configuration;
}

export async function checkDebugger(debugger_path: string): Promise<boolean> {
const res = await proc.execute(debugger_path, ['--version'], null, {shell: true}).result;
return res.retc == 0;
}
46 changes: 46 additions & 0 deletions test/extension-tests/successful-build/test/debugger.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import {CMakeTools} from '@cmt/cmake-tools';
import {DefaultEnvironment, expect} from '@test/util';
import sinon = require('sinon');

// tslint:disable:no-unused-expression

suite('[Debug/Lauch interface]', async () => {
let cmt: CMakeTools;
let testEnv: DefaultEnvironment;

setup(async function(this: Mocha.IBeforeAndAfterContext) {
this.timeout(100000);

testEnv = new DefaultEnvironment('test/extension-tests/successful-build/project-folder', 'build', 'output.txt');
cmt = await CMakeTools.create(testEnv.vsContext, testEnv.wsContext);
await cmt.scanForKits();

testEnv.projectFolder.buildDirectory.clear();
await cmt.selectKit();
expect(await cmt.build()).to.be.eq(0);
});

teardown(async function(this: Mocha.IBeforeAndAfterContext) {
this.timeout(30000);
await cmt.asyncDispose();
testEnv.teardown();
});

test('Test call of debugger', async () => {
const executablesTargets = await cmt.executableTargets;
expect(executablesTargets.length).to.be.not.eq(0);
await cmt.setLaunchTargetByName(executablesTargets[0].name);

await cmt.debugTarget();
sinon.assert.calledWith(testEnv.vs_debug_start_debugging);
}).timeout(60000);

test('Test launchTargetPath for use in other extensions or launch.json', async () => {
const executablesTargets = await cmt.executableTargets;
expect(executablesTargets.length).to.be.not.eq(0);

await cmt.setLaunchTargetByName(executablesTargets[0].name);

expect(await cmt.launchTargetPath()).to.be.eq(executablesTargets[0].path);
});
});
Loading