Skip to content

Commit

Permalink
Auto container running. Side bar icons.
Browse files Browse the repository at this point in the history
  • Loading branch information
agrojean-ledger committed Jul 24, 2023
1 parent ca958f2 commit 23e93ba
Show file tree
Hide file tree
Showing 16 changed files with 183 additions and 79 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.1.1]

### Added

- Add icons to side bar root items.
- Add auto run of docker containers.
## [0.1.0]

### Changed
Expand Down
10 changes: 7 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ledger-dev-tools",
"displayName": "ledger-dev-tools",
"description": "Tools to accelerate development of apps for Ledger devices.",
"version": "0.1.0",
"version": "0.1.1",
"publisher": "LedgerHQ",
"license": "Apache",
"icon": "resources/ledger-square.png",
Expand Down Expand Up @@ -60,11 +60,15 @@
},
{
"command": "selectTarget",
"title": "Select the device you want to build your app for."
"title": "Select device",
"category": "Ledger",
"tooltip": "Select the device you want to build your app for."
},
{
"command": "showAppList",
"title": "Select the app you want to build."
"title": "Select app",
"category": "Ledger",
"tooltip": "Select the app you want to build."
}
],

Expand Down
Binary file added resources/device-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/device-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/docker-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/docker-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/test-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/test-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/tool-dark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/tool-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 4 additions & 3 deletions src/appSelector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as fg from "fast-glob";
import { StatusBarManager } from "./statusBar";
import { TreeDataProvider } from "./treeView";
import { TaskProvider } from "./taskProvider";
import { ContainerManager } from "./containerManager";
const APP_DETECTION_FILE: string = "Makefile";
const APP_DETECTION_STRING: string = "include $(BOLOS_SDK)/Makefile.defines";

Expand Down Expand Up @@ -44,9 +45,9 @@ export function findAppsInWorkspace(): App[] | undefined {
}

export async function showAppSelectorMenu(
statusManager: StatusBarManager,
treeDataProvider: TreeDataProvider,
taskProvider: TaskProvider
taskProvider: TaskProvider,
containerManager: ContainerManager
) {
const appNames = appList.map((app) => app.appName);
const result = await vscode.window.showQuickPick(appNames, {
Expand All @@ -56,7 +57,7 @@ export async function showAppSelectorMenu(
},
});
taskProvider.generateTasks();
statusManager.autoUpdateDevImageItem();
containerManager.manageContainer();
treeDataProvider.updateTargetLabel();
return result;
}
Expand Down
57 changes: 57 additions & 0 deletions src/containerManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"use strict";
import { execSync } from "child_process";
import { getSelectedApp } from "./appSelector";
import { TaskProvider } from "./taskProvider";
import { StatusBarManager } from "./statusBar";
import { TreeDataProvider } from "./treeView";

export enum DevImageStatus {
running = "sync",
syncing = "sync~spin",
stopped = "notebook-stop",
}

export class ContainerManager {
private statusBarManager: StatusBarManager;
private taskProvider: TaskProvider;
private treeProvider: TreeDataProvider;

constructor(taskProvider: TaskProvider, statusBarManager: StatusBarManager, treeProvider: TreeDataProvider) {
this.statusBarManager = statusBarManager;
this.taskProvider = taskProvider;
this.treeProvider = treeProvider;
this.manageContainer();
}

manageContainer(): void {
const currentApp = getSelectedApp();
try {
if (currentApp) {
const containerName = currentApp.containerName;
const command = `docker inspect -f '{{ .State.Status }}' ${containerName}`;
const containerStatus = execSync(command).toString().trim();
if (containerStatus === "running") {
this.statusBarManager.updateDevImageItem(DevImageStatus.running);
this.treeProvider.updateContainerLabel(DevImageStatus.running);
} else if (containerStatus === "starting" || containerStatus === "restarting") {
this.statusBarManager.updateDevImageItem(DevImageStatus.syncing);
this.treeProvider.updateContainerLabel(DevImageStatus.syncing);
} else {
this.statusBarManager.updateDevImageItem(DevImageStatus.stopped);
this.treeProvider.updateContainerLabel(DevImageStatus.stopped);
this.taskProvider.executeTaskByName("Update Container");
}
} else {
this.statusBarManager.updateDevImageItem(DevImageStatus.stopped);
this.treeProvider.updateContainerLabel(DevImageStatus.stopped);
}
} catch (error: any) {
this.statusBarManager.updateDevImageItem(DevImageStatus.stopped);
this.treeProvider.updateContainerLabel(DevImageStatus.stopped);
if (currentApp) {
console.log(`Ledger: Container ${currentApp.containerName} not found, respawning it.`);
this.taskProvider.executeTaskByName("Update Container");
}
}
}
}
20 changes: 12 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as vscode from "vscode";
import { TaskProvider, taskType } from "./taskProvider";
import { TreeDataProvider } from "./treeView";
import { showTargetSelectorMenu } from "./targetSelector";
import { StatusBarManager, DevImageStatus } from "./statusBar";
import { StatusBarManager } from "./statusBar";
import { ContainerManager, DevImageStatus } from "./containerManager";
import { findAppsInWorkspace, getSelectedApp, setSelectedApp, showAppSelectorMenu } from "./appSelector";

console.log("Ledger: Loading extension");
Expand All @@ -20,12 +21,13 @@ export function activate(context: vscode.ExtensionContext) {
let taskProvider = new TaskProvider();
context.subscriptions.push(vscode.tasks.registerTaskProvider(taskType, taskProvider));

let statusBarManager = new StatusBarManager();
statusBarManager.autoUpdateDevImageItem();

let treeProvider = new TreeDataProvider(taskProvider.getTaskSpecs());
vscode.window.registerTreeDataProvider("exampleView", treeProvider);

let statusBarManager = new StatusBarManager();
let containerManager = new ContainerManager(taskProvider, statusBarManager, treeProvider);
containerManager.manageContainer();

context.subscriptions.push(
vscode.commands.registerCommand("selectTarget", () => {
showTargetSelectorMenu(statusBarManager, taskProvider, treeProvider);
Expand All @@ -40,14 +42,15 @@ export function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(
vscode.commands.registerCommand("showAppList", () => {
showAppSelectorMenu(statusBarManager, treeProvider, taskProvider);
showAppSelectorMenu(treeProvider, taskProvider, containerManager);
})
);

vscode.tasks.onDidStartTask((event) => {
const taskName = event.execution.task.name;
if (taskName.startsWith("Run dev-tools image")) {
if (taskName.startsWith("Update Container")) {
statusBarManager.updateDevImageItem(DevImageStatus.syncing);
treeProvider.updateContainerLabel(DevImageStatus.syncing);
}
if (taskName.startsWith("Quick device onboarding")) {
const conf = vscode.workspace.getConfiguration("ledgerDevTools");
Expand All @@ -61,8 +64,8 @@ export function activate(context: vscode.ExtensionContext) {

vscode.tasks.onDidEndTask((event) => {
const taskName = event.execution.task.name;
if (taskName.startsWith("Run dev-tools image")) {
statusBarManager.autoUpdateDevImageItem();
if (taskName.startsWith("Update Container")) {
containerManager.manageContainer();
}
});

Expand All @@ -72,6 +75,7 @@ export function activate(context: vscode.ExtensionContext) {
if (!getSelectedApp()) {
setSelectedApp(appList[0]);
}
containerManager.manageContainer();
}
});

Expand Down
36 changes: 2 additions & 34 deletions src/statusBar.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
"use strict";

import * as vscode from "vscode";
import { execSync } from "child_process";
import { getSelectedTarget } from "./targetSelector";
import { getSelectedApp } from "./appSelector";

export enum DevImageStatus {
running = "sync",
syncing = "sync~spin",
stopped = "notebook-stop",
}
import { ContainerManager, DevImageStatus } from "./containerManager";

const imageToolTip = "Click to update image and respawn container.";

Expand All @@ -29,7 +23,7 @@ export class StatusBarManager {
const runDevImageCommand: vscode.Command = {
command: "executeTask",
title: "Execute Task",
arguments: ["Run dev-tools image"],
arguments: ["Update Container"],
};
this.devImageItem.command = runDevImageCommand;
this.updateDevImageItem(DevImageStatus.stopped);
Expand Down Expand Up @@ -69,30 +63,4 @@ export class StatusBarManager {
this.devImageItem.hide();
}
}

public autoUpdateDevImageItem(): void {
this.updateDevImageItem(this.getContainerStatus());
}

private getContainerStatus(): DevImageStatus {
try {
const currentApp = getSelectedApp();
if (currentApp) {
const containerName = currentApp.containerName;
const command = `docker inspect -f '{{ .State.Status }}' ${containerName}`;
const containerStatus = execSync(command).toString().trim();
if (containerStatus === "running") {
return DevImageStatus.running;
} else if (containerStatus === "starting" || containerStatus === "restarting") {
return DevImageStatus.syncing;
} else {
return DevImageStatus.stopped;
}
} else {
return DevImageStatus.stopped;
}
} catch (error: any) {
return DevImageStatus.stopped;
}
}
}
56 changes: 33 additions & 23 deletions src/taskProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,30 +32,30 @@ export class TaskProvider implements vscode.TaskProvider {
private tasks: vscode.Task[] = [];
private currentApp: App | undefined;
private taskSpecs: TaskSpec[] = [
{ group: "Docker Operations", name: "Run dev-tools image", builder: this.runDevToolsImageExec },
{ group: "Docker Operations", name: "Open dev-tools container terminal", builder: this.openTerminalExec },
{ group: "Docker Container", name: "Update Container", builder: this.runDevToolsImageExec },
{ group: "Docker Container", name: "Open terminal", builder: this.openTerminalExec },

{ group: "Build", name: "Build app", builder: this.buildExec },
{ group: "Build", name: "Build app [debug]", builder: this.buildDebugExec },
{ group: "Build", name: "Build", builder: this.buildExec },
{ group: "Build", name: "Build [debug]", builder: this.buildDebugExec },
{ group: "Build", name: "Clean build files", builder: this.cleanExec },

{ group: "Tests", name: "Run app with Speculos", builder: this.runInSpeculosExec },
{ group: "Tests", name: "Kill Speculos", builder: this.killSpeculosExec },
{ group: "Functional Tests", name: "Run with Speculos", builder: this.runInSpeculosExec },
{ group: "Functional Tests", name: "Kill Speculos", builder: this.killSpeculosExec },
{
group: "Tests",
name: "Run functional tests",
group: "Functional Tests",
name: "Run tests",
builder: this.functionalTestsExec,
dependsOn: this.functionalTestsRequirementsExec,
},
{
group: "Tests",
name: "Run functional tests (with display)",
group: "Functional Tests",
name: "Run tests (with display)",
builder: this.functionalTestsDisplayExec,
dependsOn: this.functionalTestsRequirementsExec,
},
{
group: "Tests",
name: "Run functional tests (with display) - on device",
group: "Functional Tests",
name: "Run tests (with display) - on device",
builder: this.functionalTestsDisplayOnDeviceExec,
dependsOn: this.functionalTestsRequirementsExec,
},
Expand Down Expand Up @@ -111,19 +111,29 @@ export class TaskProvider implements vscode.TaskProvider {
return this.tasks.find((task) => task.name === taskName);
}

public executeTaskByName(taskName: string) {
const task = this.getTaskByName(taskName);
if (task) {
vscode.tasks.executeTask(task);
}
}

private runDevToolsImageExec(): string {
let exec = "";
// Checks if a container with the name ${this.containerName} exists, and if it does, it is stopped and removed before a new container is created using the same name and other specified configuration parameters
if (platform === "linux") {
// Linux
exec = `docker ps -a --format '{{.Names}}' | grep -q ${this.containerName} && (docker container stop ${this.containerName} && docker container rm ${this.containerName}) ; docker pull ${image} && docker run --user $(id -u):$(id -g) --privileged -e DISPLAY=$DISPLAY -v '/dev/bus/usb:/dev/bus/usb' -v '/tmp/.X11-unix:/tmp/.X11-unix' -v '${this.workspacePath}:/app' -t -d --name ${this.containerName} ${image}`;
} else if (platform === "darwin") {
// macOS
exec = `xhost + ; docker ps -a --format '{{.Names}}' | grep -q ${this.containerName} && (docker container stop ${this.containerName} && docker container rm ${this.containerName}) ; docker pull ${image} && docker run --user $(id -u):$(id -g) --privileged -e DISPLAY='host.docker.internal:0' -v '/tmp/.X11-unix:/tmp/.X11-unix' -v '${this.workspacePath}:/app' -t -d --name ${this.containerName} ${image}`;
} else {
// Assume windows
const winWorkspacePath = this.workspacePath.substring(1); // Remove first '/' from windows workspace path URI. Otherwise it is not valid.
exec = `if (docker ps -a --format '{{.Names}}' | Select-String -Quiet ${this.containerName}) { docker container stop ${this.containerName}; docker container rm ${this.containerName} }; docker pull ${image}; docker run --privileged -e DISPLAY='host.docker.internal:0' -v '${winWorkspacePath}:/app' -t -d --name ${this.containerName} ${image}`;

if (this.currentApp) {
// Checks if a container with the name ${this.containerName} exists, and if it does, it is stopped and removed before a new container is created using the same name and other specified configuration parameters
if (platform === "linux") {
// Linux
exec = `docker ps -a --format '{{.Names}}' | grep -q ${this.containerName} && (docker container stop ${this.containerName} && docker container rm ${this.containerName}) ; docker pull ${image} && docker run --user $(id -u):$(id -g) --privileged -e DISPLAY=$DISPLAY -v '/dev/bus/usb:/dev/bus/usb' -v '/tmp/.X11-unix:/tmp/.X11-unix' -v '${this.workspacePath}:/app' -t -d --name ${this.containerName} ${image}`;
} else if (platform === "darwin") {
// macOS
exec = `xhost + ; docker ps -a --format '{{.Names}}' | grep -q ${this.containerName} && (docker container stop ${this.containerName} && docker container rm ${this.containerName}) ; docker pull ${image} && docker run --user $(id -u):$(id -g) --privileged -e DISPLAY='host.docker.internal:0' -v '/tmp/.X11-unix:/tmp/.X11-unix' -v '${this.workspacePath}:/app' -t -d --name ${this.containerName} ${image}`;
} else {
// Assume windows
const winWorkspacePath = this.workspacePath.substring(1); // Remove first '/' from windows workspace path URI. Otherwise it is not valid.
exec = `if (docker ps -a --format '{{.Names}}' | Select-String -Quiet ${this.containerName}) { docker container stop ${this.containerName}; docker container rm ${this.containerName} }; docker pull ${image}; docker run --privileged -e DISPLAY='host.docker.internal:0' -v '${winWorkspacePath}:/app' -t -d --name ${this.containerName} ${image}`;
}
}

return exec;
Expand Down
Loading

0 comments on commit 23e93ba

Please sign in to comment.