Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions cli/src/commands/board/build-runtime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Command } from "commander";
import chalk from "chalk";
import { logger } from "../../core/logger";
import { DEFAULT_DEVICE_NAME } from "../../config/project-config";
import { getFlashRuntimeHandler } from "./flash-runtime";


export async function handleBuildRuntimeCommand(board: string, options: { deviceName?: string }) {
try {
const handler = getFlashRuntimeHandler(board);

if (!handler.isSetup()) {
logger.warn(`The environment for ${board} is not set up. Run 'bscript board setup ${board}' and try again.`);
return;
}

const buildDir = await handler.build(options.deviceName);

logger.br();
logger.success(`Success to build the BlueScript runtime for ${board}`);
logger.info(`Build artifacts: ${chalk.yellow(buildDir)}`);
logger.info('To flash from another host, copy the build directory there and run:');
logger.info(` ${chalk.yellow(`esptool.py --chip ${board} -p <port> write_flash @flash_args`)} (in the copied directory)`);
logger.info(`or connect the board to this host and run ${chalk.yellow(`bscript board flash-runtime ${board}`)}`);
} catch (error) {
logger.error(`Failed to build the runtime for ${board}`);
logger.showError(error);
process.exit(1);
}
}

export function registerBuildRuntimeCommand(program: Command) {
program
.command('build-runtime')
.description('build the BlueScript runtime for the board without flashing it.')
.argument('<board-name>', 'the name of the board to build for (e.g., esp32, esp32s3)')
.option('-d, --device-name <device-name>', `BLE device name embedded in the runtime, the default is '${DEFAULT_DEVICE_NAME}'`)
.action(handleBuildRuntimeCommand);
}
61 changes: 48 additions & 13 deletions cli/src/commands/board/flash-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import inquirer from 'inquirer';
import * as path from 'path';
import * as os from 'os';
import { SerialPort } from 'serialport'
import { BoardName } from "../../config/board-utils";
import { Esp32FamilyBoardName, isEsp32FamilyBoard } from "../../config/board-utils";
import { ESP32_TARGET_BUILD_DIRS } from "@bscript/lang";
import { logger, runStep } from "../../core/logger";
import { execShell } from '../../core/command-exec';
import chalk from "chalk";
Expand All @@ -13,35 +14,69 @@ import { DEFAULT_DEVICE_NAME } from "../../config/project-config";

const RUNTIME_ESP_PORT_DIR = (runtimeDir: string) => path.join(runtimeDir, 'ports/esp32');

abstract class FlashRuntimeHandler extends CommandHandlerWithUpdateCheck {
export abstract class FlashRuntimeHandler extends CommandHandlerWithUpdateCheck {
abstract isSetup(): boolean;
abstract eraseFlash(port: string): Promise<void>;
abstract flashRuntime(port: string, deviceName?: string): Promise<void>;
// Build the runtime without flashing it. Returns the directory that holds the build artifacts.
abstract buildRuntime(deviceName?: string): Promise<string>;

async flash(port: string, deviceName?: string) {
await runStep('Erasing flash...', () => this.eraseFlash(port));
await runStep('Flashing BlueScript runtime...', () => this.flashRuntime(port, deviceName));
}

async build(deviceName?: string): Promise<string> {
let buildDir = '';
await runStep('Building BlueScript runtime...', async () => {
buildDir = await this.buildRuntime(deviceName);
});
return buildDir;
}
}

class ESP32FlashRuntimeHandler extends FlashRuntimeHandler {
readonly boardName: BoardName = 'esp32';
export class ESP32FlashRuntimeHandler extends FlashRuntimeHandler {
readonly boardName: Esp32FamilyBoardName;

constructor(boardName: Esp32FamilyBoardName = 'esp32') {
super();
this.boardName = boardName;
}

private get targetArgs(): string[] {
if (this.boardName === 'esp32') {
return [];
}
return [
'-B', ESP32_TARGET_BUILD_DIRS[this.boardName],
'-D', `IDF_TARGET=${this.boardName}`,
'-D', `SDKCONFIG=sdkconfig.${this.boardName}`,
];
}

isSetup(): boolean {
return this.globalConfigHandler.isBoardSetup(this.boardName);
}

async eraseFlash(port: string) {
await this.runIdfPy(['erase-flash', '-p', port]);
await this.runIdfPy([...this.targetArgs, 'erase-flash', '-p', port]);
}

async flashRuntime(port: string, deviceName?: string) {
deviceName = deviceName ?? DEFAULT_DEVICE_NAME;
await this.runIdfPy(
['-D', `DEVICE_NAME=${deviceName}`, 'build', 'flash', '-p', port],
[...this.targetArgs, '-D', `DEVICE_NAME=${deviceName}`, 'build', 'flash', '-p', port],
);
}

async buildRuntime(deviceName?: string): Promise<string> {
deviceName = deviceName ?? DEFAULT_DEVICE_NAME;
await this.runIdfPy(
[...this.targetArgs, '-D', `DEVICE_NAME=${deviceName}`, 'build'],
);
return path.join(this.getEspPortDir(), ESP32_TARGET_BUILD_DIRS[this.boardName]);
}

private async runIdfPy(args: string[]) {
const osType = os.platform();
const exportFile = this.getExportFile();
Expand All @@ -59,20 +94,20 @@ class ESP32FlashRuntimeHandler extends FlashRuntimeHandler {
}

private getExportFile() {
const boardConfig = this.globalConfigHandler.getBoardConfig('esp32');
const boardConfig = this.globalConfigHandler.getBoardConfig(this.boardName);
if (!boardConfig) {
throw new Error('An unexpected error occurred: cannot find board config.');
}
return boardConfig.exportFile;
}
}

function getFlashRuntimeHandler(board: string) {
export function getFlashRuntimeHandler(board: string) {
if (board === 'host') {
throw new Error('flash-runtime is not supported for the host board');
}
if (board === 'esp32') {
return new ESP32FlashRuntimeHandler();
if (isEsp32FamilyBoard(board)) {
return new ESP32FlashRuntimeHandler(board);
}
throw new Error(`Unsupported board name: ${board}`);
}
Expand Down Expand Up @@ -147,8 +182,8 @@ export function registerFlashRuntimeCommand(program: Command) {
program
.command('flash-runtime')
.description('flash the BlueScript runtime to the board.')
.argument('<board-name>', 'the name of the board to flash (e.g., esp32)')
.argument('<board-name>', 'the name of the board to flash (e.g., esp32, esp32s3)')
.option('-p, --port <port>', 'serial port to flash to')
.option('-d, --device-name <device-name>', `device name to flash to, the default is '${DEFAULT_DEVICE_NAME}'`)
.action(handleFlashRuntimeCommand);
}
}
26 changes: 22 additions & 4 deletions cli/src/commands/board/remove.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Command } from "commander";
import inquirer from 'inquirer';
import { BoardName, isValidBoard } from "../../config/board-utils";
import { logger, runStep } from "../../core/logger";
import { BoardName, isValidBoard, isEsp32FamilyBoard, ESP32_FAMILY_BOARD_NAMES } from "../../config/board-utils";
import { logger, runStep, skip } from "../../core/logger";
import { CommandHandlerWithUpdateCheck } from "../command";
import { BoardEnv, createBoardEnv } from "../../platforms/board-env";

Expand All @@ -17,10 +17,28 @@ class RemoveHandler extends CommandHandlerWithUpdateCheck {
}

async remove() {
await runStep('Removing...', async () => this.boardEnv.removeBoardRoot());
await runStep('Removing...', async () => {
if (this.isBoardRootShared()) {
return skip(`the ESP-IDF installation is still used by ${this.otherBoardsSharingRoot().join(', ')}.`);
}
this.boardEnv.removeBoardRoot();
});
this.globalConfigHandler.removeBoardConfig(this.boardName);
this.globalConfigHandler.save();
}

// Boards of the ESP32 family share one ESP-IDF installation.
private otherBoardsSharingRoot(): BoardName[] {
if (!isEsp32FamilyBoard(this.boardName)) {
return [];
}
return ESP32_FAMILY_BOARD_NAMES.filter(
b => b !== this.boardName && this.globalConfigHandler.isBoardSetup(b));
}

private isBoardRootShared(): boolean {
return this.otherBoardsSharingRoot().length > 0;
}

isSetup(): boolean {
return this.globalConfigHandler.isBoardSetup(this.boardName);
Expand Down Expand Up @@ -76,7 +94,7 @@ export function registerRemoveCommand(program: Command) {
program
.command('remove')
.description('remove the environment for the specified board')
.argument('<board-name>', 'name of the board to remove (e.g., esp32)')
.argument('<board-name>', 'name of the board to remove (e.g., esp32, esp32s3)')
.option('-f, --force', 'skip confirmation prompt')
.action(handleRemoveCommand);
}
7 changes: 6 additions & 1 deletion cli/src/commands/board/setup/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export abstract class SetupHandler extends CommandHandlerWithUpdateCheck {

async setup() {
this.boardEnv.ensureBlueScriptDir();
this.boardEnv.refreshBoardRoot();
this.prepareBoardRoot();
for (const step of this.setupSteps) {
await runStep(step.actionMessage, step.action);
}
Expand All @@ -48,6 +48,11 @@ export abstract class SetupHandler extends CommandHandlerWithUpdateCheck {
return this.setupSteps.map(step => step.description);
};

// Prepare the directory for the board environment. By default the directory is recreated.
protected prepareBoardRoot() {
this.boardEnv.refreshBoardRoot();
}

abstract loadBoardSetupSteps(): void;
abstract setBoardConfig(): Promise<void>;

Expand Down
55 changes: 41 additions & 14 deletions cli/src/commands/board/setup/esp32.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,52 @@ import { skip } from "../../../core/logger";
import * as path from 'path';
import * as os from 'os';
import * as fs from '../../../core/fs';
import { BoardName } from "../../../config/board-utils";
import { BoardName, Esp32FamilyBoardName, ESP32_FAMILY_BOARD_NAMES } from "../../../config/board-utils";
import { Esp32UnixEnv, Esp32WindowsEnv } from "../../../platforms/board-env/esp32-env";
import { GLOBAL_SETTINGS } from "../../../config/constants";


export abstract class Esp32SetupHandler extends SetupHandler {
boardName: BoardName = "esp32";
boardName: BoardName;
abstract boardEnv: Esp32UnixEnv | Esp32WindowsEnv;
protected espIdfPath?: string;
protected pythonCommand?: string;
protected makeCommand?: string;

constructor(espIdfPath?: string) {
constructor(target: Esp32FamilyBoardName, espIdfPath?: string) {
super();
this.boardName = target;
this.espIdfPath = espIdfPath;
}

// Other boards of the ESP32 family that have already been set up.
// They share the ESP-IDF installation with this board.
protected otherEsp32FamilyBoardsSetup(): Esp32FamilyBoardName[] {
return ESP32_FAMILY_BOARD_NAMES.filter(
b => b !== this.boardName && this.globalConfigHandler.isBoardSetup(b));
}

protected reuseExistingEspIdf(): boolean {
return this.otherEsp32FamilyBoardsSetup().length > 0 && this.boardEnv.isEspIdfInstalled();
}

protected prepareBoardRoot() {
if (this.reuseExistingEspIdf()) {
this.boardEnv.ensureBoardRoot();
} else {
this.boardEnv.refreshBoardRoot();
}
}

loadEspIdfSetupSteps(): void {
if (this.espIdfPath) {
if (this.reuseExistingEspIdf()) {
const others = this.otherEsp32FamilyBoardsSetup().join(', ');
this.setupSteps.push({
description: `Reuse ESP-IDF ${this.boardEnv.idfVersion} already installed for ${others}.`,
actionMessage: `Reusing existing ESP-IDF...`,
action: async () => skip('already installed.'),
});
} else if (this.espIdfPath) {
this.setupSteps.push({
description: `Copy ESP-IDF from ${this.espIdfPath}.`,
actionMessage: `Copying ESP-IDF from ${this.espIdfPath}...`,
Expand All @@ -37,7 +64,7 @@ export abstract class Esp32SetupHandler extends SetupHandler {
}

this.setupSteps.push({
description: "Run ESP-IDF install script.",
description: `Run ESP-IDF install script for ${this.boardName}.`,
actionMessage: "Running ESP-IDF install script...",
action: this.runEspIdfInstallScriptStep.bind(this),
});
Expand Down Expand Up @@ -87,9 +114,9 @@ export abstract class Esp32SetupHandler extends SetupHandler {
export class Esp32DarwinSetupHandler extends Esp32SetupHandler {
boardEnv: Esp32UnixEnv;

constructor(espIdfPath?: string) {
super(espIdfPath);
this.boardEnv = new Esp32UnixEnv();
constructor(target: Esp32FamilyBoardName = 'esp32', espIdfPath?: string) {
super(target, espIdfPath);
this.boardEnv = new Esp32UnixEnv(target);
}

loadBoardSetupSteps(): void {
Expand Down Expand Up @@ -147,9 +174,9 @@ export class Esp32LinuxSetupHandler extends Esp32SetupHandler {
}
}

constructor(espIdfPath?: string) {
super(espIdfPath);
this.boardEnv = new Esp32UnixEnv();
constructor(target: Esp32FamilyBoardName = 'esp32', espIdfPath?: string) {
super(target, espIdfPath);
this.boardEnv = new Esp32UnixEnv(target);
this.distType = this.getDistribution();
}

Expand Down Expand Up @@ -300,9 +327,9 @@ KERNEL=="ttyUSB[0-9]*", MODE="0666"
export class Esp32WindowsSetupHandler extends Esp32SetupHandler {
boardEnv: Esp32WindowsEnv;

constructor(espIdfPath?: string) {
super(espIdfPath);
this.boardEnv = new Esp32WindowsEnv();
constructor(target: Esp32FamilyBoardName = 'esp32', espIdfPath?: string) {
super(target, espIdfPath);
this.boardEnv = new Esp32WindowsEnv(target);
}

loadBoardSetupSteps(): void {
Expand Down
11 changes: 6 additions & 5 deletions cli/src/commands/board/setup/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,18 @@ import chalk from "chalk";
import { SetupHandler } from "./base";
import { Esp32DarwinSetupHandler, Esp32WindowsSetupHandler, Esp32LinuxSetupHandler } from "./esp32";
import { HostUnixSetupHandler, HostWindowsSetupHandler } from "./host";
import { isEsp32FamilyBoard } from "../../../config/board-utils";


function getSetupHandler(board: string, espIdfPath?: string): SetupHandler {
const osType = os.platform();
if (board === 'esp32') {
if (isEsp32FamilyBoard(board)) {
if (osType === 'darwin')
return new Esp32DarwinSetupHandler(espIdfPath);
return new Esp32DarwinSetupHandler(board, espIdfPath);
if (osType === 'linux')
return new Esp32LinuxSetupHandler(espIdfPath);
return new Esp32LinuxSetupHandler(board, espIdfPath);
if (osType === 'win32')
return new Esp32WindowsSetupHandler(espIdfPath);
return new Esp32WindowsSetupHandler(board, espIdfPath);
throw new Error(`Unsupported OS type: ${osType}.`);
}
if (board === 'host') {
Expand Down Expand Up @@ -82,7 +83,7 @@ export function registerSetupCommand(program: Command) {
program
.command('setup')
.description('set up the environment for the specified board')
.argument('<board-name>', 'name of the board to setup (e.g., esp32)')
.argument('<board-name>', 'name of the board to setup (e.g., esp32, esp32s3)')
.option('--esp-idf <path>', 'path to an existing ESP-IDF directory to copy')
.action(handleSetupCommand);
}
Expand Down
Loading