mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-08 05:06:33 +00:00
158 lines
5.3 KiB
TypeScript
158 lines
5.3 KiB
TypeScript
/*
|
|
* CMSIS Debug Adapter
|
|
* Copyright (c) 2017-2019 Marcel Ball
|
|
* Copyright (c) 2019 Arm Limited
|
|
*
|
|
* The MIT License (MIT)
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
* SOFTWARE.
|
|
*/
|
|
|
|
import { EOL } from 'os';
|
|
import { spawn, ChildProcess } from 'child_process';
|
|
import { EventEmitter } from 'events';
|
|
import { dirname } from 'path';
|
|
import { CmsisRequestArguments } from './cmsis-debug-session';
|
|
|
|
const TIMEOUT = 1000 * 10; // 10 seconds
|
|
|
|
export abstract class AbstractServer extends EventEmitter {
|
|
protected process?: ChildProcess;
|
|
protected outBuffer: string = '';
|
|
protected errBuffer: string = '';
|
|
protected launchResolve?: () => void;
|
|
protected launchReject?: (error: any) => void;
|
|
protected timer?: NodeJS.Timer;
|
|
|
|
public spawn(args: CmsisRequestArguments): Promise<void> {
|
|
return new Promise(async (resolve, reject) => {
|
|
this.launchResolve = resolve;
|
|
this.launchReject = reject;
|
|
|
|
try {
|
|
this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT);
|
|
|
|
const command = args.gdbServer;
|
|
if (!command) {
|
|
throw new Error('Missing parameter: gdbServer');
|
|
}
|
|
const varRegexp = /\$\{.*\}/;
|
|
if (varRegexp.test(command)) {
|
|
throw new Error(`Unresolved variable: ${command}`)
|
|
}
|
|
const serverArguments = await this.resolveServerArguments(args);
|
|
this.process = spawn(command, serverArguments, {
|
|
cwd: dirname(command),
|
|
});
|
|
|
|
if (!this.process) {
|
|
throw new Error('Unable to spawn gdb server');
|
|
}
|
|
|
|
this.process.on('exit', this.onExit.bind(this));
|
|
this.process.on('error', this.onSpawnError.bind(this));
|
|
|
|
if (this.process.stdout) {
|
|
this.process.stdout.on('data', this.onStdout.bind(this));
|
|
}
|
|
if (this.process.stderr) {
|
|
this.process.stderr.on('data', this.onStderr.bind(this));
|
|
}
|
|
} catch (error) {
|
|
this.onSpawnError(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
public kill() {
|
|
if (this.process) {
|
|
this.process.kill('SIGINT');
|
|
}
|
|
}
|
|
|
|
protected async resolveServerArguments(req: CmsisRequestArguments): Promise<string[]> {
|
|
return req.gdbServerArguments || [];
|
|
}
|
|
|
|
protected onExit(code: number, signal: string) {
|
|
this.emit('exit', code, signal);
|
|
|
|
// Code can be undefined, null or 0 and we want to ignore those values
|
|
if (!!code) {
|
|
this.emit('error', `GDB server stopped unexpectedly with exit code ${code}`);
|
|
}
|
|
}
|
|
|
|
protected onSpawnError(error: Error) {
|
|
if (this.launchReject) {
|
|
this.clearTimer();
|
|
this.launchReject(error);
|
|
this.clearPromises();
|
|
}
|
|
}
|
|
|
|
protected onStdout(chunk: string | Buffer) {
|
|
this.onData(chunk, this.outBuffer, 'stdout');
|
|
}
|
|
|
|
protected onStderr(chunk: string | Buffer) {
|
|
this.onData(chunk, this.errBuffer, 'stderr');
|
|
}
|
|
|
|
protected onData(chunk: string | Buffer, buffer: string, event: string) {
|
|
buffer += typeof chunk === 'string' ? chunk
|
|
: chunk.toString('utf8');
|
|
|
|
const end = buffer.lastIndexOf('\n');
|
|
if (end !== -1) {
|
|
const data = buffer.substring(0, end);
|
|
this.emit(event, data);
|
|
this.handleData(data);
|
|
buffer = buffer.substring(end + 1);
|
|
}
|
|
}
|
|
|
|
protected handleData(data: string) {
|
|
if (this.launchResolve && this.serverStarted(data)) {
|
|
this.clearTimer();
|
|
this.launchResolve();
|
|
this.clearPromises();
|
|
}
|
|
|
|
if (this.serverError(data)) {
|
|
this.emit('error', data.split(EOL)[0]);
|
|
}
|
|
}
|
|
|
|
protected clearTimer() {
|
|
if (this.timer) {
|
|
clearTimeout(this.timer);
|
|
this.timer = undefined;
|
|
}
|
|
}
|
|
|
|
protected clearPromises() {
|
|
this.launchResolve = undefined;
|
|
this.launchReject = undefined;
|
|
}
|
|
|
|
protected abstract serverStarted(data: string): boolean;
|
|
protected abstract serverError(data: string): boolean;
|
|
}
|