Skip to content

Commit

Permalink
ATL-983: Propose installing the required libs.
Browse files Browse the repository at this point in the history
Signed-off-by: Akos Kitta <kittaakos@typefox.io>
  • Loading branch information
Akos Kitta authored and kittaakos committed Feb 25, 2021
1 parent 0aef4b3 commit 057904d
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 103 deletions.
6 changes: 5 additions & 1 deletion arduino-ide-extension/package.json
Expand Up @@ -124,7 +124,11 @@
],
"arduino": {
"cli": {
"version": "0.16.0"
"version": {
"owner": "arduino",
"repo": "arduino-cli",
"commitish": "scerza/lib-install-deps"
}
}
}
}
110 changes: 110 additions & 0 deletions arduino-ide-extension/src/browser/library/library-list-widget.ts
@@ -1,6 +1,10 @@
import { injectable, postConstruct, inject } from 'inversify';
import { Message } from '@phosphor/messaging';
import { addEventListener } from '@theia/core/lib/browser/widgets/widget';
import { AbstractDialog, DialogProps } from '@theia/core/lib/browser/dialogs';
import { LibraryPackage, LibraryService } from '../../common/protocol/library-service';
import { ListWidget } from '../widgets/component-list/list-widget';
import { Installable } from '../../common/protocol';
import { ListItemRenderer } from '../widgets/component-list/list-item-renderer';

@injectable()
Expand Down Expand Up @@ -33,4 +37,110 @@ export class LibraryListWidget extends ListWidget<LibraryPackage> {
]);
}

protected async install({ item, version }: { item: LibraryPackage, version: Installable.Version }): Promise<void> {
const dependencies = await this.service.listDependencies({ item, version, filterSelf: true });
let installDependencies: boolean | undefined = undefined;
if (dependencies.length) {
const message = document.createElement('div');
message.innerHTML = `The library <b>${item.name}:${version}</b> needs ${dependencies.length === 1 ? 'another dependency' : 'some other dependencies'} currently not installed:`;
const listContainer = document.createElement('div');
listContainer.style.maxHeight = '300px';
listContainer.style.overflowY = 'auto';
const list = document.createElement('ul');
list.style.listStyleType = 'none';
for (const { name } of dependencies) {
const listItem = document.createElement('li');
listItem.textContent = ` - ${name}`;
listItem.style.fontWeight = 'bold';
list.appendChild(listItem);
}
listContainer.appendChild(list);
message.appendChild(listContainer);
const question = document.createElement('div');
question.textContent = `Would you like to install ${dependencies.length === 1 ? 'the missing dependency' : 'all the missing dependencies'}?`;
message.appendChild(question);
const result = await new MessageBoxDialog({
title: `Dependencies for library ${item.name}:${version}`,
message,
buttons: [
'Install all',
`Install ${item.name} only`,
'Cancel'
],
maxWidth: 740 // Aligned with `settings-dialog.css`.
}).open();

if (result) {
const { response } = result;
if (response === 0) { // All
installDependencies = true;
} else if (response === 1) { // Current only
installDependencies = false;
}
}
}

if (typeof installDependencies === 'boolean') {
return this.service.install({ item, version, installDependencies });
}
}

}

class MessageBoxDialog extends AbstractDialog<MessageBoxDialog.Result> {

protected response: number;

constructor(protected readonly options: MessageBoxDialog.Options) {
super(options);
this.contentNode.appendChild(this.createMessageNode(this.options.message));
(options.buttons || ['OK']).forEach((text, index) => {
const button = this.createButton(text);
button.classList.add(index === 0 ? 'main' : 'secondary');
this.controlPanel.appendChild(button);
this.toDisposeOnDetach.push(addEventListener(button, 'click', () => {
this.response = index;
this.accept();
}));
});
}

protected onCloseRequest(message: Message): void {
super.onCloseRequest(message);
this.accept();
}

get value(): MessageBoxDialog.Result {
return { response: this.response };
}

protected createMessageNode(message: string | HTMLElement): HTMLElement {
if (typeof message === 'string') {
const messageNode = document.createElement('div');
messageNode.textContent = message;
return messageNode;
}
return message;
}

protected handleEnter(event: KeyboardEvent): boolean | void {
this.response = 0;
super.handleEnter(event);
}

}
export namespace MessageBoxDialog {
export interface Options extends DialogProps {
/**
* When empty, `['OK']` will be inferred.
*/
buttons?: string[];
message: string | HTMLElement;
}
export interface Result {
/**
* The index of `buttons` that was clicked.
*/
readonly response: number;
}
}
Expand Up @@ -81,11 +81,11 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
}

protected async install(item: T, version: Installable.Version): Promise<void> {
const { installable, searchable, itemLabel } = this.props;
const { install, searchable, itemLabel } = this.props;
const dialog = new InstallationProgressDialog(itemLabel(item), version);
dialog.open();
try {
await installable.install({ item, version });
dialog.open();
await install({ item, version });
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} finally {
Expand All @@ -94,20 +94,20 @@ export class FilterableListContainer<T extends ArduinoComponent> extends React.C
}

protected async uninstall(item: T): Promise<void> {
const uninstall = await new ConfirmDialog({
const ok = await new ConfirmDialog({
title: 'Uninstall',
msg: `Do you want to uninstall ${item.name}?`,
ok: 'Yes',
cancel: 'No'
}).open();
if (!uninstall) {
if (!ok) {
return;
}
const { installable, searchable, itemLabel } = this.props;
const { uninstall, searchable, itemLabel } = this.props;
const dialog = new UninstallationProgressDialog(itemLabel(item));
dialog.open();
try {
await installable.uninstall({ item });
dialog.open();
await uninstall({ item });
const items = await searchable.search({ query: this.state.filterText });
this.setState({ items: this.sort(items) });
} finally {
Expand All @@ -121,13 +121,14 @@ export namespace FilterableListContainer {

export interface Props<T extends ArduinoComponent> {
readonly container: ListWidget<T>;
readonly installable: Installable<T>;
readonly searchable: Searchable<T>;
readonly itemLabel: (item: T) => string;
readonly itemRenderer: ListItemRenderer<T>;
readonly resolveContainer: (element: HTMLElement) => void;
readonly resolveFocus: (element: HTMLElement | undefined) => void;
readonly filterTextChangeEvent: Event<string | undefined>;
readonly install: ({ item, version }: { item: T, version: Installable.Version }) => Promise<void>;
readonly uninstall: ({ item }: { item: T }) => Promise<void>;
}

export interface State<T> {
Expand Down
Expand Up @@ -67,6 +67,14 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget

protected onFocusResolved = (element: HTMLElement | undefined) => {
this.focusNode = element;
};

protected async install({ item, version }: { item: T, version: Installable.Version }): Promise<void> {
return this.options.installable.install({ item, version });
}

protected async uninstall({ item }: { item: T }): Promise<void> {
return this.options.installable.uninstall({ item });
}

render(): React.ReactNode {
Expand All @@ -75,7 +83,8 @@ export abstract class ListWidget<T extends ArduinoComponent> extends ReactWidget
resolveContainer={this.deferredContainer.resolve}
resolveFocus={this.onFocusResolved}
searchable={this.options.searchable}
installable={this.options.installable}
install={this.install.bind(this)}
uninstall={this.uninstall.bind(this)}
itemLabel={this.options.itemLabel}
itemRenderer={this.options.itemRenderer}
filterTextChangeEvent={this.filterTextChangeEmitter.event} />;
Expand Down
15 changes: 15 additions & 0 deletions arduino-ide-extension/src/common/protocol/library-service.ts
Expand Up @@ -6,6 +6,15 @@ export const LibraryServicePath = '/services/library-service';
export const LibraryService = Symbol('LibraryService');
export interface LibraryService extends Installable<LibraryPackage>, Searchable<LibraryPackage> {
list(options: LibraryService.List.Options): Promise<LibraryPackage[]>;
/**
* When `installDependencies` is not set, it is `true` by default. If you want to skip the installation of required dependencies, set it to `false`.
*/
install(options: { item: LibraryPackage, version?: Installable.Version, installDependencies?: boolean }): Promise<void>;
/**
* Set `filterSelf` to `true` if you want to avoid having `item` in the result set.
* Note: as of today (22.02.2021), the CLI works like this: `./arduino-cli lib deps Adaino@0.1.0 ✕ Adaino 0.1.0 must be installed.`.
*/
listDependencies({ item, version, filterSelf }: { item: LibraryPackage, version: Installable.Version, filterSelf?: boolean }): Promise<LibraryDependency[]>;
}

export namespace LibraryService {
Expand Down Expand Up @@ -83,3 +92,9 @@ export namespace LibraryPackage {
}

}

export interface LibraryDependency {
readonly name: string;
readonly requiredVersion: Installable.Version;
readonly installedVersion: Installable.Version;
}
26 changes: 2 additions & 24 deletions arduino-ide-extension/src/node/boards-service-impl.ts
Expand Up @@ -11,12 +11,12 @@ import {
PlatformListResp, Platform, PlatformUninstallResp, PlatformUninstallReq
} from './cli-protocol/commands/core_pb';
import { BoardDiscovery } from './board-discovery';
import { CoreClientProvider } from './core-client-provider';
import { CoreClientAware } from './core-client-provider';
import { BoardDetailsReq, BoardDetailsResp } from './cli-protocol/commands/board_pb';
import { ListProgrammersAvailableForUploadReq, ListProgrammersAvailableForUploadResp } from './cli-protocol/commands/upload_pb';

@injectable()
export class BoardsServiceImpl implements BoardsService {
export class BoardsServiceImpl extends CoreClientAware implements BoardsService {

@inject(ILogger)
protected logger: ILogger;
Expand All @@ -25,9 +25,6 @@ export class BoardsServiceImpl implements BoardsService {
@named('discovery')
protected discoveryLogger: ILogger;

@inject(CoreClientProvider)
protected readonly coreClientProvider: CoreClientProvider;

@inject(OutputService)
protected readonly outputService: OutputService;

Expand All @@ -49,25 +46,6 @@ export class BoardsServiceImpl implements BoardsService {
return this.boardDiscovery.getAvailablePorts();
}

private async coreClient(): Promise<CoreClientProvider.Client> {
const coreClient = await new Promise<CoreClientProvider.Client>(async resolve => {
const client = await this.coreClientProvider.client();
if (client) {
resolve(client);
return;
}
const toDispose = this.coreClientProvider.onClientReady(async () => {
const client = await this.coreClientProvider.client();
if (client) {
toDispose.dispose();
resolve(client);
return;
}
});
});
return coreClient;
}

async getBoardDetails(options: { fqbn: string }): Promise<BoardDetails | undefined> {
const coreClient = await this.coreClient();
const { client, instance } = coreClient;
Expand Down
Expand Up @@ -76,6 +76,9 @@ export class LibraryInstallReq extends jspb.Message {
getVersion(): string;
setVersion(value: string): LibraryInstallReq;

getNodeps(): boolean;
setNodeps(value: boolean): LibraryInstallReq;


serializeBinary(): Uint8Array;
toObject(includeInstance?: boolean): LibraryInstallReq.AsObject;
Expand All @@ -92,6 +95,7 @@ export namespace LibraryInstallReq {
instance?: commands_common_pb.Instance.AsObject,
name: string,
version: string,
nodeps: boolean,
}
}

Expand Down
32 changes: 31 additions & 1 deletion arduino-ide-extension/src/node/cli-protocol/commands/lib_pb.js
Expand Up @@ -965,7 +965,8 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.toObject = function(includeInsta
var f, obj = {
instance: (f = msg.getInstance()) && commands_common_pb.Instance.toObject(includeInstance, f),
name: jspb.Message.getFieldWithDefault(msg, 2, ""),
version: jspb.Message.getFieldWithDefault(msg, 3, "")
version: jspb.Message.getFieldWithDefault(msg, 3, ""),
nodeps: jspb.Message.getBooleanFieldWithDefault(msg, 4, false)
};

if (includeInstance) {
Expand Down Expand Up @@ -1015,6 +1016,10 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.deserializeBinaryFromReader = fu
var value = /** @type {string} */ (reader.readString());
msg.setVersion(value);
break;
case 4:
var value = /** @type {boolean} */ (reader.readBool());
msg.setNodeps(value);
break;
default:
reader.skipField();
break;
Expand Down Expand Up @@ -1066,6 +1071,13 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.serializeBinaryToWriter = functi
f
);
}
f = message.getNodeps();
if (f) {
writer.writeBool(
4,
f
);
}
};


Expand Down Expand Up @@ -1142,6 +1154,24 @@ proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.setVersion = function(
};


/**
* optional bool noDeps = 4;
* @return {boolean}
*/
proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.getNodeps = function() {
return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 4, false));
};


/**
* @param {boolean} value
* @return {!proto.cc.arduino.cli.commands.LibraryInstallReq} returns this
*/
proto.cc.arduino.cli.commands.LibraryInstallReq.prototype.setNodeps = function(value) {
return jspb.Message.setProto3BooleanField(this, 4, value);
};





Expand Down

0 comments on commit 057904d

Please sign in to comment.