* CMakeToolsWrapper doesn't inherit from API but rather aggregates it
   and provides access.
 * Added logger which writes to the same output as CMakeTools to enable
   users fix problems with the envrironment (e.g. missing CMake binary).
 * Disable CMakeTools commands and key bindings if CMake Server has
   failed to initialize. This avoids 'command not found' messages.
 * Minor fixes to make tslint happy.
This commit is contained in:
Yuri Timenkov 2017-06-06 15:19:29 +02:00
Родитель 2ca30bc2b1
Коммит d15468c738
15 изменённых файлов: 437 добавлений и 290 удалений

3
.gitattributes поставляемый
Просмотреть файл

@ -1 +1,2 @@
* eol=lf
* eol=lf
*.png binary

2
.vscode/tasks.json поставляемый
Просмотреть файл

@ -23,7 +23,7 @@
"args": ["run", "compile", "--loglevel", "silent"],
// The tsc compiler is started in watching mode
"isWatching": true,
"isBackground": true,
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
"problemMatcher": "$tsc-watch"

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

@ -140,6 +140,78 @@
"category": "CMake"
}
],
"menus": {
"commandPalette": [
{
"command": "cmake.configure",
"when": "cmaketools:enabled"
},
{
"command": "cmake.build",
"when": "cmaketools:enabled"
},
{
"command": "cmake.install",
"when": "cmaketools:enabled"
},
{
"command": "cmake.buildWithTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.setBuildType",
"when": "cmaketools:enabled"
},
{
"command": "cmake.setDefaultTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.setDefaultTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.cleanConfigure",
"when": "cmaketools:enabled"
},
{
"command": "cmake.clean",
"when": "cmaketools:enabled"
},
{
"command": "cmake.cleanRebuild",
"when": "cmaketools:enabled"
},
{
"command": "cmake.ctest",
"when": "cmaketools:enabled"
},
{
"command": "cmake.jumpToCacheFile",
"when": "cmaketools:enabled"
},
{
"command": "cmake.debugTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.launchTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.selectLaunchTarget",
"when": "cmaketools:enabled"
},
{
"command": "cmake.selectEnvironments",
"when": "cmaketools:enabled"
},
{
"command": "cmake.toggleCoverageDecorations",
"when": "cmaketools:enabled"
}
]
},
"configuration": {
"type": "object",
"title": "CMake Tools configuration",
@ -506,26 +578,38 @@
"type": "boolean",
"default": true,
"description": "Enable CMake server"
},
"cmake.loggingLevel": {
"type": "string",
"default": "normal",
"enum": [
"verbose",
"normal",
"minimal"
]
}
}
},
"keybindings": [
{
"key": "f7",
"command": "cmake.build"
"command": "cmake.build",
"when": "cmaketools:enabled"
},
{
"key": "shift+f7",
"command": "cmake.buildWithTarget"
"command": "cmake.buildWithTarget",
"when": "cmaketools:enabled"
},
{
"key": "ctrl+f5",
"command": "cmake.debugTarget",
"when": "!inDebugMode"
"when": "!inDebugMode && cmaketools:enabled"
},
{
"key": "shift+f5",
"command": "cmake.launchTarget"
"command": "cmake.launchTarget",
"when": "cmaketools:enabled"
}
],
"jsonValidation": [

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

@ -119,9 +119,9 @@ export interface CMakeToolsAPI extends Disposable {
// Clean the build output and rebuild
cleanRebuild(): Promise<number>;
// Build a target selected by the user
buildWithTarget(): Promise<number|null>;
buildWithTarget(): Promise<number>;
// Show a selector for the user to set the default build target
setDefaultTarget(): Promise<string|null>;
setDefaultTarget(): Promise<void>;
// Set the active build variant
setBuildType(): Promise<number>;
// Execute CTest
@ -129,17 +129,17 @@ export interface CMakeToolsAPI extends Disposable {
// Stop the currently running build/configure/test/install process
stop(): Promise<boolean>;
// Show a quickstart
quickStart(): Promise<number|null>;
quickStart(): Promise<number>;
// Start the executable target without a debugger
launchTarget(): Promise<void>;
// Start the debugger with the selected build target
debugTarget(): Promise<void>;
// Get the path to the active debugging target
launchTargetProgramPath(): Promise<null|string>;
launchTargetProgramPath(): Promise<string|null>;
// Allow the user to select target to debug
selectLaunchTarget(): Promise<string|null>;
selectLaunchTarget(): Promise<void>;
// Show the environment selection quickpick
selectEnvironments(): Promise<string[]|null>;
selectEnvironments(): Promise<void>;
// Sets the variant based on keyword settings
setActiveVariantCombination(settings: VariantKeywordSettings): Promise<void>;
// Toggle code coverage view on/off

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

@ -11,9 +11,10 @@ import * as util from './util';
import * as common from './common';
import {config} from './config';
import * as cms from './server-client';
import { log } from "./logging";
export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
private _client: cms.CMakeServerClient;
private _client?: cms.CMakeServerClient;
private _globalSettings: cms.GlobalSettingsContent;
private _dirty = true;
private _cacheEntries = new Map<string, cache.Entry>();
@ -96,7 +97,10 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
}
async dangerousShutdownClient() {
await this._client.shutdown();
if (this._client) {
await this._client.shutdown();
this._client = undefined;
}
}
async dangerousRestartClient() {
@ -107,7 +111,7 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
const build_dir = this.binaryDir;
const cache = this.cachePath;
const cmake_files = path.join(build_dir, 'CMakeFiles');
await this._client.shutdown();
await this.dangerousShutdownClient();
if (await async.exists(cache)) {
this._channel.appendLine('[vscode] Removing ' + cache);
await async.unlink(cache);
@ -167,8 +171,10 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
return null;
}
async configure(extraArgs: string[] = [], runPreBuild = true):
Promise<number> {
async configure(extraArgs: string[] = [], runPreBuild = true): Promise<number> {
if (!this._client)
return -1;
if (!await this._preconfigure()) {
return -1;
}
@ -179,6 +185,7 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
}
const args = await this.prepareConfigure();
this.statusMessage = 'Configuring...';
const parser = new diagnostics.BuildParser(
this.binaryDir, ['cmake'], this.activeGenerator);
@ -250,7 +257,7 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
const config = this._workspaceCacheContent.codeModel.configurations.find(
conf => conf.name == this.selectedBuildType);
if (!config) {
console.error(
log.error(
`Found no matching codemodel config for active build type ${this
.selectedBuildType}`);
return [];
@ -273,36 +280,30 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
super(_ctx);
}
private _restartClient(): Promise<void> {
return cms.CMakeServerClient
.start({
binaryDir: this.binaryDir,
sourceDir: this.sourceDir,
cmakePath: config.cmakePath,
environment: util.mergeEnvironment(config.environment, this.currentEnvironmentVariables),
onDirty: async() => {
this._dirty = true;
},
onMessage: async(msg) => {
const line = `-- ${msg.message}`;
this._accumulatedMessages.push(line);
this._channel.appendLine(line);
},
onProgress: async(prog) => {
this.buildProgress = (prog.progressCurrent - prog.progressMinimum) /
(prog.progressMaximum - prog.progressMinimum);
this.statusMessage = prog.progressMessage;
},
})
.then(async (cl): Promise<void> => {
this._client = cl;
if (await async.exists(this.cachePath)) {
await this._refreshAfterConfigure();
}
})
.catch(e => {
console.error('Error setting up client:', e);
});
private async _restartClient(): Promise<void> {
this._client = await cms.CMakeServerClient
.start({
binaryDir: this.binaryDir,
sourceDir: this.sourceDir,
cmakePath: config.cmakePath,
environment: util.mergeEnvironment(config.environment, this.currentEnvironmentVariables),
onDirty: async () => {
this._dirty = true;
},
onMessage: async (msg) => {
const line = `-- ${msg.message}`;
this._accumulatedMessages.push(line);
this._channel.appendLine(line);
},
onProgress: async (prog) => {
this.buildProgress = (prog.progressCurrent - prog.progressMinimum) /
(prog.progressMaximum - prog.progressMinimum);
this.statusMessage = prog.progressMessage;
},
});
if (await async.exists(this.cachePath)) {
await this._refreshAfterConfigure();
}
}
protected async _refreshAfterConfigure() {
@ -310,11 +311,11 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
}
private async _refreshCodeModel() {
this.codeModel = await this._client.codemodel();
this.codeModel = await this._client!.codemodel();
}
private async _refreshCacheEntries() {
const clcache = await this._client.getCMakeCacheContent();
const clcache = await this._client!.getCMakeCacheContent();
return this._cacheEntries = clcache.cache.reduce((acc, el) => {
const type: api.EntryType = {
BOOL: api.EntryType.Bool,
@ -337,8 +338,7 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
protected async _init(): Promise<ServerClientCMakeTools> {
await super._init();
await this._restartClient();
const cl = this._client;
this._globalSettings = await cl.getGlobalSettings();
this._globalSettings = await this._client!.getGlobalSettings();
this.codeModel = this._workspaceCacheContent.codeModel || null;
this._statusBar.statusMessage = 'Ready';
this._statusBar.isBusy = false;
@ -362,4 +362,4 @@ export class ServerClientCMakeTools extends common.CommonCMakeToolsBase {
cmt._statusBar.statusMessage = 'Ready';
return cmt._init();
}
}
}

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

@ -18,6 +18,7 @@ import * as status from './status';
import * as util from './util';
import {Maybe} from './util';
import {VariantManager} from './variants';
import { log } from './logging';
const CMAKETOOLS_HELPER_SCRIPT = `
get_cmake_property(is_set_up _CMAKETOOLS_SET_UP)
@ -110,7 +111,7 @@ interface WsMessage {
async function readWorkspaceCache(
path: string, defaultContent: util.WorkspaceCache) {
console.log(`Loading CMake Tools from ${path}`);
log.info(`Loading CMake Tools from ${path}`);
try {
if (await async.exists(path)) {
const buf = await async.readFile(path);
@ -129,7 +130,7 @@ async function readWorkspaceCache(
return defaultContent;
}
} catch (err) {
console.error('Error reading CMake Tools workspace cache', err);
log.error(`Error reading CMake Tools workspace cache: ${err}`);
return defaultContent;
}
}
@ -165,7 +166,7 @@ export abstract class CommonCMakeToolsBase implements api.CMakeToolsAPI {
Promise<api.CompilationInfo>;
abstract cleanConfigure(): Promise<number>;
abstract stop(): Promise<boolean>;
abstract selectLaunchTarget();
abstract selectLaunchTarget(): Promise<void>;
abstract get reconfigured(): vscode.Event<void>;
private _targetChangedEventEmitter = new vscode.EventEmitter<void>();
@ -607,7 +608,7 @@ export abstract class CommonCMakeToolsBase implements api.CMakeToolsAPI {
options: api.ExecuteOptions = {silent: false, environment: {}},
parser: util.OutputParser = new util.NullParser):
Promise<api.ExecutionResult> {
console.info('Execute cmake with arguments:', args);
log.info(`Execute cmake with arguments: ${args}`);
return this.execute(config.cmakePath, args, options, parser);
}
@ -817,7 +818,7 @@ export abstract class CommonCMakeToolsBase implements api.CMakeToolsAPI {
}
public getLaunchTargetInfo() {
return this.executableTargets.find(e => e.name == this.currentLaunchTarget);
return this.executableTargets.find(e => e.name === this.currentLaunchTarget) || null;
}
public async launchTargetProgramPath() {
@ -850,7 +851,7 @@ export abstract class CommonCMakeToolsBase implements api.CMakeToolsAPI {
term.show();
}
public async debugTarget() {
public async debugTarget(): Promise<void> {
const target = await this._prelaunchTarget();
if (!target) return;
const real_config = {
@ -865,7 +866,7 @@ export abstract class CommonCMakeToolsBase implements api.CMakeToolsAPI {
const user_config = config.debugConfig;
Object.assign(real_config, user_config);
real_config['program'] = target.path;
return vscode.commands.executeCommand('vscode.startDebug', real_config);
vscode.commands.executeCommand('vscode.startDebug', real_config);
}
public async prepareConfigure(): Promise<string[]> {

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

@ -6,6 +6,7 @@ import * as vscode from 'vscode';
import * as async from './async'
import {config} from './config';
import * as util from './util';
import { log } from './logging';
type Maybe<T> = util.Maybe<T>;
@ -108,7 +109,7 @@ async function tryCreateVCEnvironment(dist: VSDistribution, arch: string):
}
const variables = output.split('\n')
.map(l => l.trim())
.filter(l => l.length != 0)
.filter(l => l.length !== 0)
.reduce<Map<string, string>>((acc, line) => {
const mat = /(\w+) := ?(.*)/.exec(line);
console.assert(!!mat, line);
@ -214,11 +215,11 @@ export class EnvironmentManager {
}
public readonly environmentsLoaded: Promise<void> =
Promise.all(availableEnvironments().map(async(pr) => {
<Promise<void>>Promise.all(availableEnvironments().map(async(pr) => {
try {
const env = await pr;
if (env.variables) {
console.log(`Detected available environment "${env.name}`);
log.info(`Detected available environment "${env.name}`);
this._availableEnvironments.set(env.name, {
name: env.name,
variables: env.variables,

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

@ -2,50 +2,50 @@
import * as vscode from 'vscode';
import * as api from './api';
import * as wrapper from './wrapper';
import { CMakeToolsWrapper } from './wrapper';
import { log } from './logging';
export async function activate(context: vscode.ExtensionContext): Promise<api.CMakeToolsAPI | null> {
let cmake: wrapper.CMakeToolsWrapper | null = null;
try {
cmake = await wrapper.CMakeToolsWrapper.startup(context);
} catch (e) {
debugger;
console.error('Error during CMake Tools initialization!', e);
}
if (cmake) {
function register(name, fn) {
fn = fn.bind(cmake);
return vscode.commands.registerCommand(name, _ => fn());
}
export async function activate(context: vscode.ExtensionContext): Promise<CMakeToolsWrapper> {
log.initialize(context);
for (const key of [
'configure',
'build',
'install',
'jumpToCacheFile',
'clean',
'cleanConfigure',
'cleanRebuild',
'buildWithTarget',
'setDefaultTarget',
'setBuildType',
'ctest',
'stop',
'quickStart',
'launchTargetProgramPath',
'debugTarget',
'launchTarget',
'selectLaunchTarget',
'selectEnvironments',
'toggleCoverageDecorations',
]) {
context.subscriptions.push(register('cmake.' + key, cmake[key as string]));
}
const cmake = new CMakeToolsWrapper(context);
context.subscriptions.push(cmake);
function register(name, fn) {
fn = fn.bind(cmake);
return vscode.commands.registerCommand(name, _ => fn());
}
for (const key of [
'configure',
'build',
'install',
'jumpToCacheFile',
'clean',
'cleanConfigure',
'cleanRebuild',
'buildWithTarget',
'setDefaultTarget',
'setBuildType',
'ctest',
'stop',
'quickStart',
'launchTargetProgramPath',
'debugTarget',
'launchTarget',
'selectLaunchTarget',
'selectEnvironments',
'toggleCoverageDecorations',
]) {
context.subscriptions.push(register('cmake.' + key, cmake[key]));
}
await cmake.start();
return cmake;
}
// this method is called when your extension is deactivated
export function deactivate() {
log.dispose();
}

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

@ -23,6 +23,7 @@ import {config} from './config';
import {Entry, CMakeCache} from './cache';
import {CommonCMakeToolsBase} from './common';
import { log } from './logging';
type Maybe<T> = util.Maybe<T>;
@ -245,7 +246,7 @@ export class CMakeTools extends CommonCMakeToolsBase implements api.CMakeToolsAP
this._lastConfigureSettings = config.configureSettings;
this._needsReconfigure = true;
vscode.workspace.onDidChangeConfiguration(() => {
console.log('Reloading CMakeTools after configuration change');
log.info('Reloading CMakeTools after configuration change');
this._reloadConfiguration();
});
@ -369,7 +370,7 @@ export class CMakeTools extends CommonCMakeToolsBase implements api.CMakeToolsAP
args.push('-G' + generator);
is_multi_conf = util.isMultiConfGenerator(generator);
} else {
console.error('None of the preferred generators were selected');
log.error('None of the preferred generators were selected');
}
}

60
src/logging.ts Normal file
Просмотреть файл

@ -0,0 +1,60 @@
'use strict';
import * as vscode from 'vscode';
export type LogLevel = 'verbose' | 'normal' | 'minimal';
export const LogLevel = {
Verbose: 'verbose' as LogLevel,
Normal: 'normal' as LogLevel,
Minimal: 'minimal' as LogLevel
};
export class Logger {
private _logChannel?: vscode.OutputChannel;
private get logChannel(): vscode.OutputChannel {
if (!this._logChannel) {
this._logChannel = vscode.window.createOutputChannel('CMake/Build');
}
return this._logChannel!;
}
private currentLevel: LogLevel = LogLevel.Normal;
private onConfigurationChanged(): void {
const newLevel = vscode.workspace.getConfiguration('cmake').get<LogLevel>('loggingLevel');
this.currentLevel = newLevel;
}
public initialize(context: vscode.ExtensionContext) {
vscode.workspace.onDidChangeConfiguration(this.onConfigurationChanged, this, context.subscriptions);
this.onConfigurationChanged();
}
public dispose() {
if (this._logChannel) {
this._logChannel.dispose();
this._logChannel = undefined;
}
}
public error(message: string): void {
this.logChannel.appendLine(message);
}
public info(message: string): void {
if (this.currentLevel !== LogLevel.Minimal) {
this.logChannel.appendLine(message);
}
}
public verbose(message: string): void {
if (this.currentLevel === LogLevel.Verbose) {
this.logChannel.appendLine(message);
}
}
}
// TODO: Use global object for now (following current config pattern).
// change to some factory later for DI/testing.
export const log = new Logger();

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

@ -5,7 +5,8 @@ import * as vscode from 'vscode';
import * as async from './async';
import * as cache from './cache';
import {config} from './config';
import { config } from './config';
import { log } from './logging';
import * as util from './util';
const MESSAGE_WRAPPER_RE =
@ -386,6 +387,9 @@ export class ServerError extends global.Error implements ErrorMessage {
public cookie = e.cookie, public inReplyTo = e.inReplyTo) {
super(e.errorMessage);
}
toString(): string {
return `[cmake-server] ${this.errorMessage}`;
}
}
interface MessageResolutionCallbacks {
@ -418,6 +422,7 @@ export class CMakeServerClient {
'Protocol error talking to CMake! Got this input: ' + input);
}
this._accInput = tail;
console.log(`Received message from cmake-server: ${content}`);
const message: SomeMessage = JSON.parse(content);
this._onMessage(message);
}
@ -505,14 +510,15 @@ export class CMakeServerClient {
const pr = new Promise((resolve, reject) => {
this._promisesResolvers.set(cookie, {resolve: resolve, reject: reject});
});
console.log(`Sending message to cmake-server: ${JSON.stringify(cp)}`);
const msg = JSON.stringify(cp);
console.log(`Sending message to cmake-server: ${msg}`);
this._pipe.write('\n[== "CMake Server" ==[\n');
this._pipe.write(JSON.stringify(cp));
this._pipe.write(msg);
this._pipe.write('\n]== "CMake Server" ==]\n');
return pr;
}
setGlobalSettings(params: SetGlobalSettingsParams): Promise<void> {
setGlobalSettings(params: SetGlobalSettingsParams): Promise<SetGlobalSettingsContent> {
return this.sendRequest('setGlobalSettings', params);
}
@ -537,7 +543,7 @@ export class CMakeServerClient {
}
private _onErrorData(data: Uint8Array) {
console.error(data.toString());
log.error(`[cmake-server] ${data.toString()}`);
}
public async shutdown() {
@ -548,7 +554,7 @@ export class CMakeServerClient {
private constructor(params: ClientInitPrivate) {
this._params = params;
let pipe_file = path.join(params.tmpdir, '.cmserver-pipe');
if (process.platform == 'win32') {
if (process.platform === 'win32') {
pipe_file = '\\\\?\\pipe\\' + pipe_file;
} else {
pipe_file = path.join(params.binaryDir, `.cmserver.${process.pid}`);
@ -558,10 +564,9 @@ export class CMakeServerClient {
['-E', 'server', '--experimental', `--pipe=${pipe_file}`], {
env: params.environment,
});
child.stderr.on('data', (dat) => {
console.error('Error from cmake-server process:', dat.toString());
});
console.log('Started new CMake Server instance with PID', child.pid);
log.info(`Started new CMake Server instance with PID ${child.pid}`);
child.stdout.on('data', this._onErrorData.bind(this));
child.stderr.on('data', this._onErrorData.bind(this));
setTimeout(() => {
const end_promise = new Promise(resolve => {
const pipe = this._pipe = net.createConnection(pipe_file);
@ -580,17 +585,14 @@ export class CMakeServerClient {
resolve();
});
});
this._endPromise = Promise.all([end_promise, exit_promise]);
this._endPromise = <Promise<void>>Promise.all([end_promise, exit_promise]);
this._proc = child;
child.stdout.on('data', this._onErrorData.bind(this));
child.stderr.on('data', this._onErrorData.bind(this));
child.on('close', (retc: number, signal: string) => {
if (retc !== 0) {
console.error(
'The connection to cmake-server was terminated unexpectedly');
console.error(`cmake-server exited with status ${retc} (${signal})`);
log.error("The connection to cmake-server was terminated unexpectedly");
log.error(`cmake-server exited with status ${retc} (${signal})`);
params.onCrash(retc, signal).catch(e => {
console.error('Unhandled error in onCrash', e);
log.error(`Unhandled error in onCrash ${e}`);
});
}
});
@ -623,13 +625,17 @@ export class CMakeServerClient {
const cache_path = path.join(params.binaryDir, 'CMakeCache.txt');
const have_cache = await async.exists(cache_path);
const tmpcache = have_cache ? await cache.CMakeCache.fromPath(cache_path) : null;
const generator = tmpcache
? tmpcache.get('CMAKE_GENERATOR')!.as<string>()
: await util.pickGenerator(config.preferredGenerators);
let generator: string | null = null;
if (tmpcache) {
generator = tmpcache.get('CMAKE_GENERATOR')!.as<string>();
log.info(`Determining CMake startup parameters from existing cache ${cache_path}`);
}
else {
generator = await util.pickGenerator(config.preferredGenerators);
}
if (!generator) {
vscode.window.showErrorMessage(
'Unable to determine CMake Generator to use');
throw new global.Error('No generator!');
log.error('None of preferred generators available on the system.');
throw new global.Error('Unable to determine CMake Generator to use');
}
let src_dir = params.sourceDir;
// Work-around: CMake Server checks that CMAKE_HOME_DIRECTORY
@ -641,7 +647,7 @@ export class CMakeServerClient {
if (tmpcache) {
const home = tmpcache.get('CMAKE_HOME_DIRECTORY');
if (home &&
util.normalizePath(home.as<string>()) ==
util.normalizePath(home.as<string>()) ===
util.normalizePath(src_dir)) {
src_dir = home.as<string>();
}
@ -668,4 +674,4 @@ export class CMakeServerClient {
export function createCooke(): string {
return 'cookie-' + Math.random().toString();
}
}

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

@ -269,7 +269,6 @@ export class StatusBar implements vscode.Disposable {
} else {
this._environmentSelectionButton.hide();
}
this._environmentSelectionButton.text
}
private _environmentsAvailable: boolean = false;

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

@ -6,9 +6,10 @@ import * as vscode from 'vscode';
import * as api from './api';
import * as async from './async';
import {config} from './config';
import {CodeModelContent} from './server-client';
import {VariantCombination} from './variants';
import { config } from './config';
import { CodeModelContent } from './server-client';
import { VariantCombination } from './variants';
import { log } from './logging';
export class ThrottledOutputChannel implements vscode.OutputChannel {
private _channel: vscode.OutputChannel;
@ -18,7 +19,7 @@ export class ThrottledOutputChannel implements vscode.OutputChannel {
constructor(name: string) {
this._channel = vscode.window.createOutputChannel(name);
this._accumulatedData = '';
this._throttler = new async.Throttler();
this._throttler = new async.Throttler<void>();
}
get name(): string {
@ -71,7 +72,7 @@ export function isTruthy(value: (boolean|string|null|undefined|number)) {
return !!value;
}
export function rmdir(dirpath: string): Promise<void> {
return new Promise((resolve, reject) => {
return new Promise<void>((resolve, reject) => {
rimraf(dirpath, err => {
if (err) {
reject(err);
@ -303,7 +304,7 @@ export function execute(
}
});
stream.on('line', (line: string) => {
console.log(`[${program} output]: ${line}`);
log.verbose(`[${program} output]: ${line}`);
if (outputChannel) {
outputChannel.appendLine(line);
}
@ -313,10 +314,12 @@ export function execute(
pipe.on('error', reject);
pipe.on('close', (retc: number) => {
const msg = `${program} exited with return code ${retc}`;
console.log(msg);
if (outputChannel) {
outputChannel.appendLine(`[vscode] ${msg}`)
}
else {
log.verbose(msg);
}
resolve({retc, stdout: acc.stdout, stderr: acc.stderr});
})
});
@ -344,8 +347,10 @@ export async function pickGenerator(candidates: string[]):
const generator = config.generator;
if (generator) {
// User has explicitly requested a certain generator. Use that one.
log.verbose(`Using generator from configuration: ${generator}`);
return generator;
}
log.verbose("Trying to detect generator supported by system");
for (const gen of candidates) {
const delegate = {
Ninja: async() => {
@ -370,10 +375,12 @@ export async function pickGenerator(candidates: string[]):
vscode.window.showErrorMessage('Unknown CMake generator "' + gen + '"');
continue;
}
if (await delegate.bind(this)())
if (await delegate.bind(this)()) {
return gen;
else
console.log('Generator "' + gen + '" is not supported');
}
else {
log.info(`Build program for generator ${gen} is not found. Skipping...`);
}
}
return null;
}
@ -500,5 +507,14 @@ export function parseCompileDefinition(str: string): [string, string | null] {
}
export function pause(time: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, time));
}
return new Promise<void>(resolve => setTimeout(resolve, time));
}
export type CommandContext = 'cmaketools:enabled';
export const CommandContext = {
Enabled: 'cmaketools:enabled' as CommandContext
};
export function setCommandContext(key: CommandContext | string, value: any) {
return vscode.commands.executeCommand('setContext', key, value);
}

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

@ -6,158 +6,131 @@ import * as api from './api';
import * as legacy from './legacy';
import * as client from './client';
import * as util from './util';
import {config} from './config';
import { config } from './config';
import { log } from './logging';
export class CMakeToolsWrapper implements api.CMakeToolsAPI {
private _impl: Promise<api.CMakeToolsAPI>;
export class CMakeToolsWrapper {
private _impl?: api.CMakeToolsAPI = undefined;
constructor(private _ctx: vscode.ExtensionContext) {}
constructor(private _ctx: vscode.ExtensionContext) { }
async dispose() {
public async dispose() {
await this.shutdown();
this._reconfiguredEmitter.dispose();
}
private async _sourceDir() {
return (await this._impl).sourceDir;
}
get sourceDir() {
return this._sourceDir();
public get toolsApi(): api.CMakeToolsAPI {
if (this._impl)
return this._impl;
else
throw new Error("CMakeTools is not initialized");
}
private async _mainListFile() {
return (await this._impl).mainListFile;
}
get mainListFile() {
return this._mainListFile();
public async configure(extraArgs?: string[], runPrebuild?: boolean) {
if (this._impl)
await this._impl.configure(extraArgs, runPrebuild);
}
private async _binaryDir() {
return (await this._impl).binaryDir;
}
get binaryDir() {
return this._binaryDir();
public async build(target?: string) {
if (this._impl)
await this._impl.build(target);
}
private async _cachePath() {
return (await this._impl).cachePath;
}
get cachePath() {
return this._cachePath();
public async install() {
if (this._impl)
await this._impl.install();
}
private async _executableTargets() {
return (await this._impl).executableTargets;
}
get executableTargets() {
return this._executableTargets();
public async jumpToCacheFile() {
if (this._impl)
await this._impl.jumpToCacheFile();
}
private async _diagnostics() {
return (await this._impl).diagnostics;
}
get diagnostics() {
return this._diagnostics();
public async clean() {
if (this._impl)
await this._impl.clean();
}
private async _targets() {
return (await this._impl).targets;
}
get targets() {
return this._targets();
public async cleanConfigure() {
if (this._impl) {
try {
await this._impl.cleanConfigure();
} catch (error) {
log.error(`Failed to reconfigure project: ${error}`);
// TODO: show error message?
}
}
}
async executeCMakeCommand(args: string[], options?: api.ExecuteOptions) {
return (await this._impl).executeCMakeCommand(args, options);
public async cleanRebuild() {
if (this._impl)
await this._impl.cleanRebuild();
}
async execute(program: string, args: string[], options?: api.ExecuteOptions) {
return (await this._impl).execute(program, args, options);
public async buildWithTarget() {
if (this._impl)
await this._impl.buildWithTarget();
}
async compilationInfoForFile(filepath: string) {
return (await this._impl).compilationInfoForFile(filepath);
public async setDefaultTarget() {
if (this._impl)
await this._impl.setDefaultTarget();
}
async configure(extraArgs?: string[], runPrebuild?: boolean) {
return (await this._impl).configure(extraArgs, runPrebuild);
public async setBuildType() {
if (this._impl)
await this._impl.setBuildType();
}
async build(target?: string) {
return (await this._impl).build(target);
public async ctest() {
if (this._impl)
await this._impl.ctest();
}
async install() {
return (await this._impl).install();
public async stop() {
if (this._impl)
await this._impl.stop();
}
async jumpToCacheFile() {
return (await this._impl).jumpToCacheFile();
public async quickStart() {
if (this._impl)
await this._impl.quickStart();
}
async clean() {
return (await this._impl).clean();
public async debugTarget() {
if (this._impl)
await this._impl.debugTarget();
}
async cleanConfigure() {
return (await this._impl).cleanConfigure();
}
async cleanRebuild() {
return (await this._impl).cleanRebuild();
}
async buildWithTarget() {
return (await this._impl).buildWithTarget();
}
async setDefaultTarget() {
return (await this._impl).setDefaultTarget();
}
async setBuildType() {
return (await this._impl).setBuildType();
}
async ctest() {
return (await this._impl).ctest();
}
async stop() {
return (await this._impl).stop();
}
async quickStart() {
return (await this._impl).quickStart();
}
async debugTarget() {
return (await this._impl).debugTarget();
}
async launchTarget() {
return (await this._impl).launchTarget();
public async launchTarget() {
if (this._impl)
await this._impl.launchTarget();
}
async launchTargetProgramPath() {
return (await this._impl).launchTargetProgramPath();
public async launchTargetProgramPath() {
if (this._impl)
await this._impl.launchTargetProgramPath();
}
async selectLaunchTarget() {
return (await this._impl).selectLaunchTarget();
public async selectLaunchTarget() {
if (this._impl)
await this._impl.selectLaunchTarget();
}
async selectEnvironments() {
return (await this._impl).selectEnvironments();
public async selectEnvironments() {
if (this._impl)
await this._impl.selectEnvironments();
}
async setActiveVariantCombination(settings: api.VariantKeywordSettings) {
return (await this._impl).setActiveVariantCombination(settings);
public async setActiveVariantCombination(settings: api.VariantKeywordSettings) {
if (this._impl)
await this._impl.setActiveVariantCombination(settings);
}
async toggleCoverageDecorations() {
return (await this._impl).toggleCoverageDecorations();
public async toggleCoverageDecorations() {
if (this._impl)
await this._impl.toggleCoverageDecorations();
}
private _reconfiguredEmitter = new vscode.EventEmitter<void>();
@ -166,54 +139,59 @@ export class CMakeToolsWrapper implements api.CMakeToolsAPI {
private _targetChangedEventEmitter = new vscode.EventEmitter<void>();
readonly targetChangedEvent = this._targetChangedEventEmitter.event;
private async _setupEvents() {
const cmt = await this._impl;
cmt.targetChangedEvent(() => {
this._targetChangedEventEmitter.fire();
});
cmt.reconfigured(() => {
this._reconfiguredEmitter.fire();
});
private setToolsApi(cmt?: api.CMakeToolsAPI): void {
this._impl = cmt;
util.setCommandContext(util.CommandContext.Enabled, !!cmt);
if (this._impl) {
this._impl.targetChangedEvent(() => { this._targetChangedEventEmitter.fire(); });
this._impl.reconfigured(() => { this._reconfiguredEmitter.fire(); });
}
}
public async reload(): Promise<CMakeToolsWrapper> {
await this.shutdown();
if (config.useCMakeServer) {
const cmpath = config.cmakePath;
const version_ex = await util.execute(config.cmakePath, ['--version']).onComplete;
console.assert(version_ex.stdout);
const version_re = /cmake version (.*?)\r?\n/;
const version = util.parseVersion(version_re.exec(version_ex.stdout!)![1]);
// We purposefully exclude versions <3.7.1, which have some major CMake
// server bugs
if (util.versionGreater(version, '3.7.1')) {
this._impl = client.ServerClientCMakeTools.startup(this._ctx);
await this._impl;
await this._setupEvents();
return this;
public async start(): Promise<void> {
console.assert(!this._impl);
try {
if (config.useCMakeServer) {
const cmpath = config.cmakePath;
const version_ex = await util.execute(config.cmakePath, ['--version']).onComplete;
console.assert(version_ex.stdout);
const version_re = /cmake version (.*?)\r?\n/;
const version = util.parseVersion(version_re.exec(version_ex.stdout!)![1]);
// We purposefully exclude versions <3.7.1, which have some major CMake
// server bugs
if (util.versionGreater(version, '3.7.1')) {
const impl = await client.ServerClientCMakeTools.startup(this._ctx);
await impl;
this.setToolsApi(impl);
return;
}
log.error('CMake Server is not available with the current CMake executable. Please upgrade to CMake 3.7.2 or newer first.');
}
console.warn('CMake Server is not available with the current CMake executable. Please upgrade to CMake 3.7.2 or newer first.');
// Fall back to use the legacy plugin
const cmt = new legacy.CMakeTools(this._ctx);
const impl = await cmt.initFinished;
this.setToolsApi(impl);
} catch (error) {
this.setToolsApi();
log.error(`Failed to start CMakeTools: ${error}`);
vscode.window.showErrorMessage('CMakeTools extension was unable to initialize. Please check output window for details.');
}
// Fall back to use the legacy plugin
const cmt = new legacy.CMakeTools(this._ctx);
this._impl = cmt.initFinished;
await this._impl;
await this._setupEvents();
return this;
}
public async shutdown() {
const impl = await this._impl;
if (impl instanceof client.ServerClientCMakeTools) {
await impl.dangerousShutdownClient();
}
if (impl) {
impl.dispose();
if (!this._impl)
return;
if (this._impl instanceof client.ServerClientCMakeTools) {
await this._impl.dangerousShutdownClient();
}
this._impl.dispose();
this.setToolsApi();
}
static startup(ct: vscode.ExtensionContext): Promise<CMakeToolsWrapper> {
const cmt = new CMakeToolsWrapper(ct);
return cmt.reload();
public async restart(): Promise<void> {
await this.shutdown();
await this.start();
}
};

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

@ -21,8 +21,8 @@ function testFilePath(filename: string): string {
return path.normalize(path.join(here, '../..', 'test', filename));
}
async function getExtension(): Promise<api.CMakeToolsAPI> {
const cmt = vscode.extensions.getExtension<api.CMakeToolsAPI>('vector-of-bool.cmake-tools')!;
async function getExtension(): Promise<wrapper.CMakeToolsWrapper> {
const cmt = vscode.extensions.getExtension<wrapper.CMakeToolsWrapper>('vector-of-bool.cmake-tools');
return cmt.isActive ? Promise.resolve(cmt.exports) : cmt.activate();
}
@ -348,7 +348,7 @@ suite("Utility tests", () => {
test('Can access the extension API', async function () {
this.timeout(40000);
const api = await getExtension();
assert(await api.binaryDir);
assert(await api.toolsApi.binaryDir);
});
function smokeTests(context, tag, setupHelper) {
context.timeout(60 * 1000); // These tests are slower than just unit tests
@ -359,7 +359,7 @@ suite("Utility tests", () => {
await cmt.setActiveVariantCombination({
buildType: 'debug'
});
const bd = await cmt.binaryDir;
const bd = await cmt.toolsApi.binaryDir;
const exists = await new Promise<boolean>(resolve => {
fs.exists(bd, resolve);
});
@ -516,14 +516,14 @@ suite("Utility tests", () => {
teardown(async function() {
const cmt: wrapper.CMakeToolsWrapper = this.cmt;
await cmt.shutdown();
if (fs.existsSync(await cmt.binaryDir)) {
rimraf.sync(await cmt.binaryDir);
if (fs.existsSync(await cmt.toolsApi.binaryDir)) {
rimraf.sync(await cmt.toolsApi.binaryDir);
}
const output_file = testFilePath('output-file.txt');
if (fs.existsSync(output_file)) {
fs.unlinkSync(output_file);
}
await cmt.reload();
await cmt.restart();
});
};
// suite('Extension smoke tests [without cmake-server]', function() {