First stop towards compile/verify

This commit is contained in:
Christian Weichel 2019-05-06 19:58:36 +02:00
parent c48d80b137
commit cc79d53dc4
12 changed files with 145 additions and 31 deletions

View File

@ -11,6 +11,7 @@ import { ArduinoCommands } from './arduino-commands';
import { ConnectedBoards } from './components/connected-boards'; import { ConnectedBoards } from './components/connected-boards';
import { CoreService } from '../common/protocol/core-service'; import { CoreService } from '../common/protocol/core-service';
import { WorkspaceServiceExt } from './workspace-service-ext'; import { WorkspaceServiceExt } from './workspace-service-ext';
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
@injectable() @injectable()
@ -28,6 +29,9 @@ export class ArduinoFrontendContribution extends DefaultFrontendApplicationContr
@inject(WorkspaceServiceExt) @inject(WorkspaceServiceExt)
protected readonly workspaceServiceExt: WorkspaceServiceExt; protected readonly workspaceServiceExt: WorkspaceServiceExt;
@inject(ToolOutputServiceClient)
protected readonly toolOutputServiceClient: ToolOutputServiceClient;
@postConstruct() @postConstruct()
protected async init(): Promise<void> { protected async init(): Promise<void> {
// This is a hack. Otherwise, the backend services won't bind. // This is a hack. Otherwise, the backend services won't bind.

View File

@ -17,8 +17,11 @@ import { BoardsListWidget } from './boards/boards-list-widget';
import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution'; import { BoardsListWidgetFrontendContribution } from './boards/boards-widget-frontend-contribution';
import { WorkspaceServiceExt, WorkspaceServiceExtPath } from './workspace-service-ext'; import { WorkspaceServiceExt, WorkspaceServiceExtPath } from './workspace-service-ext';
import { WorkspaceServiceExtImpl } from './workspace-service-ext-impl'; import { WorkspaceServiceExtImpl } from './workspace-service-ext-impl';
import { ToolOutputServiceClient } from '../common/protocol/tool-output-service';
import '../../src/browser/style/index.css'; import '../../src/browser/style/index.css';
import { ToolOutputService } from '../common/protocol/tool-output-service';
import { ToolOutputServiceClientImpl } from './tool-output/client-service-impl';
export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => { export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Unbind, isBound: interfaces.IsBound, rebind: interfaces.Rebind) => {
// Commands and toolbar items // Commands and toolbar items
@ -58,6 +61,14 @@ export default new ContainerModule((bind: interfaces.Bind, unbind: interfaces.Un
.toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath)) .toDynamicValue(context => WebSocketConnectionProvider.createProxy(context.container, CoreServicePath))
.inSingletonScope(); .inSingletonScope();
// Tool output service client
bind(ToolOutputServiceClientImpl).toSelf().inSingletonScope();
bind(ToolOutputServiceClient).toDynamicValue(context => {
const client = context.container.get(ToolOutputServiceClientImpl);
WebSocketConnectionProvider.createProxy(context.container, ToolOutputService.SERVICE_PATH, client);
return client;
}).inSingletonScope();
// The workspace service extension // The workspace service extension
bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => { bind(WorkspaceServiceExt).to(WorkspaceServiceExtImpl).inSingletonScope().onActivation(({ container }, workspaceServiceExt) => {
WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt); WebSocketConnectionProvider.createProxy(container, WorkspaceServiceExtPath, workspaceServiceExt);

View File

@ -52,6 +52,7 @@
padding: 4px; padding: 4px;
font-size: 10px; font-size: 10px;
font-weight: bold; font-weight: bold;
max-height: calc(1em + 4px);
} }
.component-list-item .footer { .component-list-item .footer {

View File

@ -0,0 +1,16 @@
import { ToolOutputServiceClient } from "../../common/protocol/tool-output-service";
import { injectable, inject } from "inversify";
import { OutputChannelManager } from "@theia/output/lib/common/output-channel";
@injectable()
export class ToolOutputServiceClientImpl implements ToolOutputServiceClient {
@inject(OutputChannelManager)
protected readonly outputChannelManager: OutputChannelManager;
onNewOutput(tool: string, chunk: string): void {
const channel = this.outputChannelManager.getChannel(`Arduino: ${tool}`);
channel.append(chunk);
}
}

View File

@ -1,7 +1,7 @@
export const CoreServicePath = '/services/core-service'; export const CoreServicePath = '/services/core-service';
export const CoreService = Symbol('CoreService'); export const CoreService = Symbol('CoreService');
export interface CoreService { export interface CoreService {
compile(options: CoreService.Compile.Options): Promise<string>; compile(options: CoreService.Compile.Options): Promise<void>;
upload(): Promise<void>; upload(): Promise<void>;
} }

View File

@ -0,0 +1,16 @@
import { JsonRpcServer } from "@theia/core";
export const ToolOutputServiceServer = Symbol("ToolOutputServiceServer");
export interface ToolOutputServiceServer extends JsonRpcServer<ToolOutputServiceClient> {
publishNewOutput(tool: string, chunk: string): void;
disposeClient(client: ToolOutputServiceClient): void;
}
export const ToolOutputServiceClient = Symbol("ToolOutputServiceClient");
export interface ToolOutputServiceClient {
onNewOutput(tool: string, chunk: string): void;
}
export namespace ToolOutputService {
export const SERVICE_PATH = "/tool-output-service";
}

View File

@ -12,6 +12,9 @@ import { ConnectionContainerModule } from '@theia/core/lib/node/messaging/connec
import { WorkspaceServiceExtPath, WorkspaceServiceExt } from '../browser/workspace-service-ext'; import { WorkspaceServiceExtPath, WorkspaceServiceExt } from '../browser/workspace-service-ext';
import { CoreClientProviderImpl } from './core-client-provider-impl'; import { CoreClientProviderImpl } from './core-client-provider-impl';
import { CoreClientProviderPath, CoreClientProvider } from './core-client-provider'; import { CoreClientProviderPath, CoreClientProvider } from './core-client-provider';
import { ToolOutputService, ToolOutputServiceClient, ToolOutputServiceServer } from '../common/protocol/tool-output-service';
import { ConnectionHandler, JsonRpcConnectionHandler } from '@theia/core';
import { ToolOutputServiceServerImpl } from './tool-output-service-impl';
export default new ContainerModule((bind, unbind, isBound, rebind) => { export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(ArduinoDaemon).toSelf().inSingletonScope(); bind(ArduinoDaemon).toSelf().inSingletonScope();
@ -51,6 +54,17 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
}); });
bind(ConnectionContainerModule).toConstantValue(connectionConnectionModule); bind(ConnectionContainerModule).toConstantValue(connectionConnectionModule);
// Tool output service -> feedback from the daemon, compile and flash
bind(ToolOutputServiceServer).to(ToolOutputServiceServerImpl).inSingletonScope();
bind(ConnectionHandler).toDynamicValue(context =>
new JsonRpcConnectionHandler<ToolOutputServiceClient>(ToolOutputService.SERVICE_PATH, client => {
const server = context.container.get<ToolOutputServiceServer>(ToolOutputServiceServer);
server.setClient(client);
client.onDidCloseConnection(() => server.disposeClient(client));
return server;
})
).inSingletonScope();
// Bind the workspace service extension to the backend per Theia connection. // Bind the workspace service extension to the backend per Theia connection.
// So that we can access the workspace roots of the frontend. // So that we can access the workspace roots of the frontend.
const workspaceServiceExtConnectionModule = ConnectionContainerModule.create(({ bindFrontendService }) => { const workspaceServiceExtConnectionModule = ConnectionContainerModule.create(({ bindFrontendService }) => {

View File

@ -6,6 +6,7 @@ import { ILogger } from '@theia/core/lib/common/logger';
import { BackendApplicationContribution } from '@theia/core/lib/node'; import { BackendApplicationContribution } from '@theia/core/lib/node';
import { Deferred } from '@theia/core/lib/common/promise-util'; import { Deferred } from '@theia/core/lib/common/promise-util';
import { DaemonLog } from './daemon-log'; import { DaemonLog } from './daemon-log';
import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
const EXECUTABLE_PATH = resolve(join(__dirname, '..', '..', 'build', `arduino-cli.${os.platform()}`)) const EXECUTABLE_PATH = resolve(join(__dirname, '..', '..', 'build', `arduino-cli.${os.platform()}`))
@ -16,6 +17,9 @@ export class ArduinoDaemon implements BackendApplicationContribution {
@named('daemon') @named('daemon')
protected readonly logger: ILogger protected readonly logger: ILogger
@inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
protected isReady = new Deferred<boolean>(); protected isReady = new Deferred<boolean>();
async onStart() { async onStart() {
@ -28,10 +32,16 @@ export class ArduinoDaemon implements BackendApplicationContribution {
console.log(stdout); console.log(stdout);
}); });
if (daemon.stdout) { if (daemon.stdout) {
daemon.stdout.on('data', data => DaemonLog.log(this.logger, data.toString())); daemon.stdout.on('data', data => {
this.toolOutputService.publishNewOutput("daeomn", data.toString());
DaemonLog.log(this.logger, data.toString());
});
} }
if (daemon.stderr) { if (daemon.stderr) {
daemon.stderr.on('data', data => DaemonLog.log(this.logger, data.toString())); daemon.stderr.on('data', data => {
this.toolOutputService.publishNewOutput("daeomn error", data.toString());
DaemonLog.log(this.logger, data.toString());
});
} }
if (daemon.stderr) { if (daemon.stderr) {
daemon.on('exit', (code, signal) => DaemonLog.log(this.logger, `Daemon exited with code: ${code}. Signal was: ${signal}.`)); daemon.on('exit', (code, signal) => DaemonLog.log(this.logger, `Daemon exited with code: ${code}. Signal was: ${signal}.`));

View File

@ -66,10 +66,10 @@ export class BoardsServiceImpl implements BoardsService {
const [ platform, boardName ] = board.id.split(":"); const [ platform, boardName ] = board.id.split(":");
const req = new PlatformInstallReq(); const req = new PlatformInstallReq();
req.setInstance(instance);
req.setArchitecture(boardName); req.setArchitecture(boardName);
req.setPlatformPackage(platform); req.setPlatformPackage(platform);
req.setVersion(board.availableVersions[0]); req.setVersion(board.availableVersions[0]);
req.setInstance(instance);
console.info("Starting board installation", board); console.info("Starting board installation", board);
const resp = client.platformInstall(req); const resp = client.platformInstall(req);

View File

@ -84,6 +84,24 @@ export class CoreClientProviderImpl implements CoreClientProvider {
updateResp.on('end', resolve); updateResp.on('end', resolve);
}); });
} }
// {
// const installBuiltinPkgReq = new PlatformInstallReq();
// installBuiltinPkgReq.setInstance(instance);
// installBuiltinPkgReq.setPlatformPackage("builtin");
// const resp = client.platformInstall(installBuiltinPkgReq);
// resp.on('data', (r: PlatformInstallResp) => {
// const prog = r.getProgress();
// if (prog) {
// console.info(`downloading ${prog.getFile()}: ${prog.getCompleted()}%`)
// }
// });
// await new Promise<void>((resolve, reject) => {
// resp.on('end', resolve);
// resp.on('error', reject);
// });
// }
// TODO: revisit this!!! // TODO: revisit this!!!
// `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly. // `updateResp.on('data'` is called only when running, for instance, `compile`. It does not run eagerly.
// await new Promise<void>((resolve, reject) => { // await new Promise<void>((resolve, reject) => {

View File

@ -1,11 +1,11 @@
import { inject, injectable } from 'inversify'; import { inject, injectable } from 'inversify';
import { FileSystem } from '@theia/filesystem/lib/common/filesystem'; import { FileSystem } from '@theia/filesystem/lib/common/filesystem';
import { CoreService } from '../common/protocol/core-service'; import { CoreService } from '../common/protocol/core-service';
import { CompileReq } from './cli-protocol/compile_pb'; import { CompileReq, CompileResp } from './cli-protocol/compile_pb';
import { BoardsService } from '../common/protocol/boards-service'; import { BoardsService } from '../common/protocol/boards-service';
import { CoreClientProvider } from './core-client-provider'; import { CoreClientProvider } from './core-client-provider';
import { PlatformInstallReq } from './cli-protocol/core_pb'; import * as path from 'path';
import { LibraryInstallReq } from './cli-protocol/lib_pb'; import { ToolOutputServiceServer } from '../common/protocol/tool-output-service';
@injectable() @injectable()
export class CoreServiceImpl implements CoreService { export class CoreServiceImpl implements CoreService {
@ -19,13 +19,17 @@ export class CoreServiceImpl implements CoreService {
@inject(BoardsService) @inject(BoardsService)
protected readonly boardsService: BoardsService; protected readonly boardsService: BoardsService;
async compile(options: CoreService.Compile.Options): Promise<string> { @inject(ToolOutputServiceServer)
protected readonly toolOutputService: ToolOutputServiceServer;
async compile(options: CoreService.Compile.Options): Promise<void> {
console.log('compile', options); console.log('compile', options);
const { uri } = options; const { uri } = options;
const sketchpath = await this.fileSystem.getFsPath(options.uri); const sketchFilePath = await this.fileSystem.getFsPath(options.uri);
if (!sketchpath) { if (!sketchFilePath) {
throw new Error(`Cannot resolve filesystem path for URI: ${uri}.`); throw new Error(`Cannot resolve filesystem path for URI: ${uri}.`);
} }
const sketchpath = path.dirname(sketchFilePath);
const { client, instance } = await this.coreClientProvider.getClient(uri); const { client, instance } = await this.coreClientProvider.getClient(uri);
// const boards = await this.boardsService.connectedBoards(); // const boards = await this.boardsService.connectedBoards();
@ -34,38 +38,30 @@ export class CoreServiceImpl implements CoreService {
// } // }
// https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88 // https://github.com/cmaglie/arduino-cli/blob/bd5e78701e7546787649d3cca6b21c5d22d0e438/cli/compile/compile.go#L78-L88
const installLibReq = new LibraryInstallReq();
installLibReq.setInstance(instance);
installLibReq.setName('arduino:samd');
const installResp = client.libraryInstall(installLibReq);
const xxx = await new Promise<string>((resolve, reject) => {
const chunks: Buffer[] = [];
installResp.on('data', (chunk: Buffer) => chunks.push(chunk));
installResp.on('error', error => reject(error));
installResp.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim()))
});
console.log('xxx', xxx);
const compilerReq = new CompileReq(); const compilerReq = new CompileReq();
compilerReq.setInstance(instance); compilerReq.setInstance(instance);
compilerReq.setSketchpath(sketchpath); compilerReq.setSketchpath(sketchpath);
compilerReq.setFqbn('arduino:samd'/*boards.current.name*/); compilerReq.setFqbn('arduino:avr:uno'/*boards.current.name*/);
// request.setShowproperties(false); // request.setShowproperties(false);
// request.setPreprocess(false); compilerReq.setPreprocess(false);
// request.setBuildcachepath(''); // request.setBuildcachepath('');
// request.setBuildpath(''); // compilerReq.setBuildpath('/tmp/build');
// compilerReq.setShowproperties(true);
// request.setBuildpropertiesList([]); // request.setBuildpropertiesList([]);
// request.setWarnings('none'); // request.setWarnings('none');
// request.setVerbose(true); compilerReq.setVerbose(true);
// request.setQuiet(false); compilerReq.setQuiet(false);
// request.setVidpid(''); // request.setVidpid('');
// request.setExportfile(''); // request.setExportfile('');
const result = client.compile(compilerReq); const result = client.compile(compilerReq);
return new Promise<string>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const chunks: Buffer[] = []; result.on('data', (cr: CompileResp) => {
result.on('data', (chunk: Buffer) => chunks.push(chunk)); this.toolOutputService.publishNewOutput("compile", new Buffer(cr.getOutStream_asU8()).toString());
console.error(cr.getErrStream().toString());
});
result.on('error', error => reject(error)); result.on('error', error => reject(error));
result.on('end', () => resolve(Buffer.concat(chunks).toString('utf8').trim())) result.on('end', () => resolve());
}); });
} }

View File

@ -0,0 +1,28 @@
import { injectable } from "inversify";
import { ToolOutputServiceServer, ToolOutputServiceClient } from "../common/protocol/tool-output-service";
@injectable()
export class ToolOutputServiceServerImpl implements ToolOutputServiceServer {
protected clients: ToolOutputServiceClient[] = [];
publishNewOutput(tool: string, chunk: string): void {
this.clients.forEach(c => c.onNewOutput(tool, chunk));
}
setClient(client: ToolOutputServiceClient | undefined): void {
if (!client) {
return;
}
this.clients.push(client);
}
disposeClient(client: ToolOutputServiceClient): void {
}
dispose(): void {
this.clients = [];
}
}