diff --git a/cli/README.md b/cli/README.md index 191c36ea..5f6efa83 100644 --- a/cli/README.md +++ b/cli/README.md @@ -18,7 +18,7 @@ Build and test the CLI package: cd cli npm run build npm test # unit tests only -npm run test:integration # host integration tests (macOS + cc) +npm run test:integration # host integration tests (macOS or Windows) npm run test:all # unit + integration ``` @@ -27,10 +27,12 @@ npm run test:all # unit + integration | Script | Jest project | Location | Notes | | :--- | :--- | :--- | :--- | | `npm test` | `unit` | `tests/**/*.test.ts` (excludes `integration/`) | Mocks fs, shell, logger, devices | -| `npm run test:integration` | `integration` | `tests/integration/**/*.test.ts` | Real host `shell` process; macOS only | +| `npm run test:integration` | `integration` | `tests/integration/**/*.test.ts` | Real host `shell` process; macOS or Windows (MinGW-w64) | | `npm run test:all` | both | — | Run before merging CLI changes | -**Integration test requirements:** macOS, `cc`, and the `microcontroller/` tree at the repository root. On first run, tests build `microcontroller/ports/host/build/shell` and `c-runtime.so` if missing. Tests are skipped automatically on non-macOS platforms. +**Integration test requirements:** macOS (`cc`) or Windows (MinGW-w64: `gcc`, `mingw32-make`), and the `microcontroller/` tree at the repository root. On first run, tests build `microcontroller/ports/host/build/shell` (or `shell.exe`) and `c-runtime.so` (or `c-runtime.dll`) if missing. Tests are skipped automatically on Linux and other unsupported platforms. + +**Supported platforms:** macOS and Windows for `host` and `esp32` board setup. Linux is not supported. On Windows, install the Visual C++ Build Environment before `npm install` (node-gyp), and MinGW-w64 for the host runtime. See [Windows prerequisites](https://csg-tokyo.github.io/bluescript/docs/tutorial/get-started/setup-environment-windows) on the website. **Integration coverage (14 tests):** @@ -61,7 +63,7 @@ bscript -v Before merging CLI changes or cutting a release: -1. Run `npm run test:all` (or at least `npm test`; on macOS also `npm run test:integration`). +1. Run `npm run test:all` (or at least `npm test`; on macOS or Windows also `npm run test:integration`). 2. Follow the manual QA checklist: **[docs/manual-test.md](./docs/manual-test.md)** - **Daily PRs:** run automated tests plus **Quick smoke (host)** (~15 minutes). diff --git a/cli/docs/manual-test.md b/cli/docs/manual-test.md index b11ed505..332a40f2 100644 --- a/cli/docs/manual-test.md +++ b/cli/docs/manual-test.md @@ -39,9 +39,11 @@ Use a clean working directory for project commands (no existing `bsconfig.json` | Profile | OS | Node.js | Additional requirements | | :--- | :--- | :--- | :--- | | **host** | macOS | v18+ (v20+ recommended) | `cc`, `make` | -| **esp32** | macOS | v18+ (v20+ recommended) | ESP32 board, USB cable, Bluetooth enabled | +| **host** | Windows | v18+ (v20+ recommended) | Visual C++ Build Environment (for npm install), MinGW-w64 (`gcc`, `mingw32-make`) | +| **esp32** | macOS | v18+ (v20+ recommended) | Homebrew, Git, Python 3, ESP32 board, USB cable, Bluetooth enabled | +| **esp32** | Windows | v18+ (v20+ recommended) | Visual C++ Build Environment (for npm install), Git, Python 3, `make` or `mingw32-make`, ESP32 board, USB cable, Bluetooth enabled | -> **Note:** The host runtime currently requires **macOS**. ESP32 setup is also macOS-only in the current CLI implementation. +> ESP32 on Windows (setup, flash, BLE `project run`) has been manually verified. Linux is not supported by the CLI. ### Automated integration tests (host) @@ -49,8 +51,8 @@ Host integration tests live in `cli/tests/integration/`. They spawn the real hos | Requirement | Detail | | :--- | :--- | -| OS | macOS only (tests are skipped on other platforms) | -| Toolchain | `cc` (builds `microcontroller/ports/host/build/` on first run if missing) | +| OS | macOS or Windows (tests are skipped on Linux and other platforms) | +| Toolchain | macOS: `cc`; Windows: MinGW-w64 (`gcc`, `mingw32-make`). Builds `microcontroller/ports/host/build/` on first run if missing. | | Repo layout | Run from `cli/` with the `microcontroller/` tree at the repository root | ```bash @@ -65,7 +67,7 @@ CLI log output is suppressed during integration runs (`tests/integration-setup.t ## Quick smoke (host only, ~15 min) -Run this before merging most CLI PRs. No hardware required. +Run this before merging most CLI PRs. No hardware required. Run on **macOS** or **Windows** (MinGW-w64 for host). The same steps apply on both platforms. 1. **MT-SMOKE-01** — `bscript -v` prints the expected version 2. **MT-SMOKE-02** — `bscript board list` shows `esp32` and `host` @@ -734,11 +736,11 @@ For failures, include the item ID (e.g. `MT-PROJ-RUN-03`) in Notes or link to an ## Coverage map: automated vs manual -Jest **unit** tests in `cli/tests/` mock filesystem, network, and device I/O. **Integration** tests in `cli/tests/integration/` use real host runtime processes on macOS. Use this table to avoid re-testing automated behavior manually while ensuring gaps are covered. +Jest **unit** tests in `cli/tests/` mock filesystem, network, and device I/O. **Integration** tests in `cli/tests/integration/` use real host runtime processes on macOS or Windows. Use this table to avoid re-testing automated behavior manually while ensuring gaps are covered. -| Area | Unit tests | Integration tests (host, macOS) | Manual testing still needed | +| Area | Unit tests | Integration tests (host, macOS/Windows) | Manual testing still needed | | :--- | :--- | :--- | :--- | -| `board setup` | Handler logic, macOS paths, skip-if-done | — | Real download, ESP-IDF install, host runtime build | +| `board setup` | Handler logic, macOS/Windows paths, skip-if-done | — | Real download, ESP-IDF install, host runtime build | | `board flash-runtime` | ESP32 handler, host rejection, port prompt mocked, `deviceName` passed to build | — | Actual USB flash on hardware; BLE advertised name after flash | | `board remove` / `fullclean` | File removal, prompts mocked | — | Confirm disk state after real removal | | `board update` | Update steps, rollback logic | — | End-to-end after real version bump | diff --git a/cli/src/commands/board/flash-runtime.ts b/cli/src/commands/board/flash-runtime.ts index 1b04281f..3fc04fb3 100644 --- a/cli/src/commands/board/flash-runtime.ts +++ b/cli/src/commands/board/flash-runtime.ts @@ -1,18 +1,19 @@ import { Command } from "commander"; 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 { logger, runStep } from "../../core/logger"; -import { exec } from '../../core/shell'; +import { execShell } from '../../core/command-exec'; import chalk from "chalk"; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; import { DEFAULT_DEVICE_NAME } from "../../config/project-config"; const RUNTIME_ESP_PORT_DIR = (runtimeDir: string) => path.join(runtimeDir, 'ports/esp32'); -abstract class FlashRuntimeHandler extends CommandHandler { +abstract class FlashRuntimeHandler extends CommandHandlerWithUpdateCheck { abstract isSetup(): boolean; abstract flashRuntime(port: string, deviceName?: string): Promise; @@ -41,11 +42,19 @@ class ESP32FlashRuntimeHandler extends FlashRuntimeHandler { deviceName = deviceName ?? DEFAULT_DEVICE_NAME; - await exec( - `source ${boardConfig.exportFile} && idf.py -D DEVICE_NAME=${deviceName} build flash -p ${port}`, - { cwd: RUNTIME_ESP_PORT_DIR(runtimeDir) } + await this.runIdfPy( + boardConfig.exportFile, + ['-D', `DEVICE_NAME=${deviceName}`, 'build', 'flash', '-p', port], + RUNTIME_ESP_PORT_DIR(runtimeDir) ) } + + private async runIdfPy(exportFile: string, args: string[], cwd: string) { + const osType = os.platform(); + const preCommand = osType === 'win32' ? `call ${exportFile}` : `source ${exportFile}`; + + await execShell(`${preCommand} && idf.py ${args.join(' ')}`, { cwd }); + } } function getFlashRuntimeHandler(board: string) { diff --git a/cli/src/commands/board/full-clean.ts b/cli/src/commands/board/full-clean.ts index 83660fa4..4cb2f3c4 100644 --- a/cli/src/commands/board/full-clean.ts +++ b/cli/src/commands/board/full-clean.ts @@ -1,20 +1,18 @@ import { Command } from "commander"; import inquirer from 'inquirer'; import { logger } from "../../core/logger"; -import * as fs from '../../core/fs'; import { CommandHandler } from "../command"; -import { GLOBAL_SETTINGS } from "../../config/constants"; +import { CommonBoardEnv } from "../../platforms/board-env"; class FullcleanHandler extends CommandHandler { constructor() { - super(false); + super(); } fullclean() { - if (fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_DIR)) { - fs.removeDir(GLOBAL_SETTINGS.BLUESCRIPT_DIR); - } + const env = new CommonBoardEnv(); + env.removeBlueScriptDir(); } } diff --git a/cli/src/commands/board/list.ts b/cli/src/commands/board/list.ts index dc384d68..943256e8 100644 --- a/cli/src/commands/board/list.ts +++ b/cli/src/commands/board/list.ts @@ -2,10 +2,10 @@ import { Command } from "commander"; import chalk from 'chalk'; import { BOARD_NAMES } from "../../config/board-utils"; import { logger } from "../../core/logger"; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; -class ListHandler extends CommandHandler { +class ListHandler extends CommandHandlerWithUpdateCheck { list() { const supportedBoards = BOARD_NAMES; logger.log('Available boards:'); diff --git a/cli/src/commands/board/remove.ts b/cli/src/commands/board/remove.ts index e9aafb75..44ff3b82 100644 --- a/cli/src/commands/board/remove.ts +++ b/cli/src/commands/board/remove.ts @@ -1,73 +1,38 @@ import { Command } from "commander"; import inquirer from 'inquirer'; -import { BoardName } from "../../config/board-utils"; +import { BoardName, isValidBoard } from "../../config/board-utils"; import { logger, runStep } from "../../core/logger"; -import * as fs from '../../core/fs'; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; +import { BoardEnv, createBoardEnv } from "../../platforms/board-env"; -abstract class RemoveHandler extends CommandHandler { - async remove() { - await runStep('Removing...', () => this.removeBoard()); - this.globalConfigHandler.save(); - } - abstract isSetup(): boolean; - abstract removeBoard(): Promise; -} +class RemoveHandler extends CommandHandlerWithUpdateCheck { + boardName: BoardName; + boardEnv: BoardEnv; -class HostRemoveHandler extends RemoveHandler { - readonly boardName: BoardName = 'host'; - - isSetup(): boolean { - return this.globalConfigHandler.isBoardSetup(this.boardName); + constructor(boardName: BoardName) { + super(); + this.boardName = boardName; + this.boardEnv = createBoardEnv(boardName); } - async removeBoard() { - const boardConfig = this.globalConfigHandler.getBoardConfig('host'); - if (boardConfig === undefined) { - throw new Error(`Cannot find config for ${this.boardName}.`); - } - if (fs.exists(boardConfig.buildDir)) { - fs.removeDir(boardConfig.buildDir); - } - + async remove() { + await runStep('Removing...', async () => this.boardEnv.removeBoardRoot()); this.globalConfigHandler.removeBoardConfig(this.boardName); + this.globalConfigHandler.save(); } -} - -class ESP32RemoveHandler extends RemoveHandler { - readonly boardName: BoardName = 'esp32'; - + isSetup(): boolean { return this.globalConfigHandler.isBoardSetup(this.boardName); } - - async removeBoard() { - const boardConfig = this.globalConfigHandler.getBoardConfig('esp32'); - if (boardConfig === undefined) { - throw new Error(`Cannot find config for ${this.boardName}.`); - } - if (fs.exists(boardConfig.rootDir)) { - fs.removeDir(boardConfig.rootDir); - } - - this.globalConfigHandler.removeBoardConfig(this.boardName); - } -} - -function getRemoveHandler(board: string) { - if (board === 'esp32') { - return new ESP32RemoveHandler(); - } - if (board === 'host') { - return new HostRemoveHandler(); - } - throw new Error(`Unsupported board name: ${board}`); } export async function handleRemoveCommand(board: string, options: { force?: boolean }) { try { - const removeHandler = getRemoveHandler(board); + if (!isValidBoard(board)) { + throw new Error(`Unsupported board name: ${board}`); + } + const removeHandler = new RemoveHandler(board); // Check if setup has already been completed. if (!removeHandler.isSetup()) { diff --git a/cli/src/commands/board/setup.ts b/cli/src/commands/board/setup.ts deleted file mode 100644 index 6b3ff2ef..00000000 --- a/cli/src/commands/board/setup.ts +++ /dev/null @@ -1,342 +0,0 @@ -import { Command } from "commander"; -import * as path from 'path'; -import * as os from 'os'; -import inquirer from 'inquirer'; -import { logger, runStep, skip } from "../../core/logger"; -import { BoardName } from "../../config/board-utils"; -import { exec } from '../../core/shell'; -import * as fs from '../../core/fs'; -import chalk from "chalk"; -import { CommandHandler } from "../command"; -import { GLOBAL_SETTINGS } from "../../config/constants"; -import { buildHostRuntime } from "../../platforms/runtime/host-board-runtime"; - - -abstract class SetupHandler extends CommandHandler { - - getSetupPlan(): string[] { - const plan: string[] = []; - plan.push(`Download BlueScript runtime from ${GLOBAL_SETTINGS.RUNTIME_ZIP_URL}`); - plan.push(...this.getBoardSetupPlan()); - return plan; - } - - async setup(): Promise { - this.ensureBlueScriptDir(); - await this.downloadBlueScriptRuntimeStep(); - await this.setupBoard(); - this.globalConfigHandler.save(); - } - - private ensureBlueScriptDir() { - if (!fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_DIR)) { - fs.makeDir(GLOBAL_SETTINGS.BLUESCRIPT_DIR); - } - } - - private async downloadBlueScriptRuntimeStep() { - return runStep('Downloading BlueScript runtime...', async () => { - if (this.globalConfigHandler.isRuntimeSetup()) { - return skip('already downloaded.'); - } - await this.downloadBlueScriptRuntime(); - }); - } - - private async downloadBlueScriptRuntime() { - if (fs.exists(GLOBAL_SETTINGS.RUNTIME_DIR)) { - fs.removeDir(GLOBAL_SETTINGS.RUNTIME_DIR); - } - await fs.downloadAndUnzip(GLOBAL_SETTINGS.RUNTIME_ZIP_URL, GLOBAL_SETTINGS.BLUESCRIPT_DIR); - this.globalConfigHandler.setRuntimeDir(GLOBAL_SETTINGS.RUNTIME_DIR); - } - - abstract needSetup(): boolean; - abstract getBoardSetupPlan(): string[]; - abstract setupBoard(): Promise; -} - - -type ESP32SupportedOS = 'macos'; - -export class ESP32SetupHandler extends SetupHandler { - readonly boardName: BoardName = 'esp32'; - private os: ESP32SupportedOS; - - constructor() { - super(); - this.os = this.checkAndGetOS(); - } - - private checkAndGetOS(): ESP32SupportedOS { - if (os.platform() === 'darwin') { - return 'macos'; - } else { - throw new Error('Unsupported OS.'); - } - } - - needSetup(): boolean { - return !this.globalConfigHandler.isBoardSetup(this.boardName); - } - - getBoardSetupPlan(): string[] { - let plan: string[] = []; - if (this.os === 'macos') { - plan.push('Install required packages via Homebrew or MacPorts if they are not installed (cmake, ninja, dfu-util, and ccache).'); - plan.push('Install Python3 via Homebrew or MacPorts if python version is not greater than 3 or python3 is not installed.'); - } else { - throw new Error('Unknown OS.'); - } - plan.push(`Clone ESP-IDF ${GLOBAL_SETTINGS.ESP_IDF_VERSION} from ${GLOBAL_SETTINGS.ESP_IDF_GIT_REPO}.`); - plan.push('Run ESP-IDF install script.'); - return plan; - } - - async setupBoard(): Promise { - await this.installRequiredPackagesStep(); - await this.installPython3Step(); - await this.cloneEspIdfStep(); - await this.runEspIdfInstallScriptStep(); - - this.globalConfigHandler.updateBoardConfig(this.boardName, { - idfVersion: GLOBAL_SETTINGS.ESP_IDF_VERSION, - rootDir: GLOBAL_SETTINGS.ESP_ROOT_DIR, - exportFile: GLOBAL_SETTINGS.ESP_IDF_EXPORT_FILE, - xtensaGccDir: await this.getXtensaGccDir(), - }); - } - - private async installRequiredPackagesStep() { - return runStep('Installing required packages...', async () => { - let packages: string[] = []; - if (!(await this.isPackageInstalled('cmake'))) { packages.push('cmake'); } - if (!(await this.isPackageInstalled('ninja'))) { packages.push('ninja'); } - if (!(await this.isPackageInstalled('dfu-util'))) { packages.push('dfu-util'); } - if (!(await this.isPackageInstalled('ccache'))) { packages.push('ccache'); } - if (packages.length === 0) { - return skip('already installed.'); - } - await this.installEspidfRequiredPackages(packages); - }); - } - - private async installPython3Step() { - return runStep('Installing Python3...', async () => { - if ((await this.isPythonVersionGreaterThan3()) || (await this.isPackageInstalled('python3'))) { - return skip('already installed.'); - } - await this.installPython3(); - }); - } - - private cloneEspIdfStep() { - return runStep( - `Cloning ESP-IDF ${GLOBAL_SETTINGS.ESP_IDF_VERSION} from ${GLOBAL_SETTINGS.ESP_IDF_GIT_REPO}... It may take a while.`, - () => this.cloneEspIdf(), - ); - } - - private runEspIdfInstallScriptStep() { - return runStep( - 'Running ESP-IDF install script...', - () => this.runEspIdfInstallScript() - ); - } - - private async installEspidfRequiredPackages(packages: string[]) { - let installer: string; - if (await this.isPackageInstalled('brew')) { - installer = 'brew'; - } else if (await this.isPackageInstalled('port')) { - installer = 'port'; - } else { - throw new Error('Cannot find package installer. Please install Homebrew or MacPorts and try again.'); - } - - await exec(`${installer} install ${packages.join(' ')}`); - } - - private async installPython3() { - if (await this.isPackageInstalled('brew')) { - await exec('brew install python3'); - } else if (await this.isPackageInstalled('port')) { - await exec('sudo port install python38'); - } else { - throw new Error('Cannot find package installer. Please install Homebrew or MacPorts and try again.'); - } - } - - private async isPackageInstalled(name: string) { - try { - await exec(`which ${name}`, { silent: true }); - return true; - } catch (error) { - return false; - } - } - - private async isPythonVersionGreaterThan3() { - try { - const result = await exec(`python --version`, { silent: true }); - return result.startsWith('Python 3.'); - } catch (error) { - return false; - } - } - - private async cloneEspIdf() { - if (fs.exists(GLOBAL_SETTINGS.ESP_ROOT_DIR)) { - fs.removeDir(GLOBAL_SETTINGS.ESP_ROOT_DIR); - } - if (!(await this.isPackageInstalled('git'))) { - throw new Error('Cannot find git command. Please install git and try again.'); - } - - fs.makeDir(GLOBAL_SETTINGS.ESP_ROOT_DIR); - await exec(`git clone --depth 1 -b ${GLOBAL_SETTINGS.ESP_IDF_VERSION} --recursive ${GLOBAL_SETTINGS.ESP_IDF_GIT_REPO}`, - { cwd: GLOBAL_SETTINGS.ESP_ROOT_DIR }); - } - - private async runEspIdfInstallScript() { - await exec(GLOBAL_SETTINGS.ESP_IDF_INSTALL_FILE); - } - - private async getXtensaGccDir() { - try { - const gccPath = await exec(`source ${GLOBAL_SETTINGS.ESP_IDF_EXPORT_FILE} > /dev/null 2>&1 && which xtensa-esp32-elf-gcc`, { silent:true }); - return path.dirname(gccPath); - } catch (error) { - throw new Error('Failed to get xtensa gcc path.', {cause: error}); - } - - } -} - -export class HostSetupHandler extends SetupHandler { - readonly boardName: BoardName = 'host'; - - constructor() { - super(); - if (os.platform() !== 'darwin') { - throw new Error('Unsupported OS.'); - } - } - - needSetup(): boolean { - return !this.globalConfigHandler.isBoardSetup(this.boardName); - } - - getBoardSetupPlan(): string[] { - return [ - 'Verify that cc and make are installed (Xcode Command Line Tools).', - 'Build host runtime process.', - ]; - } - - async setupBoard(): Promise { - await this.verifyBuildToolsStep(); - const buildDir = await this.buildHostRuntimeStep(); - this.globalConfigHandler.updateBoardConfig(this.boardName, { buildDir: buildDir! }); - } - - private async verifyBuildToolsStep() { - return runStep('Verifying that cc and make are installed...', async () => { - const missing: string[] = []; - if (!(await this.isCommandInstalled('cc'))) { missing.push('cc'); } - if (!(await this.isCommandInstalled('make'))) { missing.push('make'); } - if (missing.length === 0) { - return; - } - throw new Error( - `Missing required tools: ${missing.join(', ')}. Install Xcode Command Line Tools and try again.`, - ); - }); - } - - private buildHostRuntimeStep() { - return runStep('Building host runtime...', async () => { - const runtimeDir = this.globalConfigHandler.getConfig().runtimeDir; - if (!runtimeDir) { - throw new Error('An unexpected error occurred: cannot find runtime directory path.'); - } - return await buildHostRuntime(runtimeDir); - }); - } - - private async isCommandInstalled(name: string) { - try { - await exec(`which ${name}`, { silent: true }); - return true; - } catch { - return false; - } - } -} - - -function getSetupHandler(board: string): SetupHandler { - if (board === 'esp32') { - return new ESP32SetupHandler(); - } - if (board === 'host') { - return new HostSetupHandler(); - } - throw new Error(`Unsupported board name: ${board}`); -} - -export async function handleSetupCommand(board: string) { - try { - const setupHandler = getSetupHandler(board); - - // Check if setup has already been completed. - if (!setupHandler.needSetup()) { - logger.warn(`The setup for ${board} has already been completed.`); - return; - } - - // Ask user if it's ok to proceed with setup. - const setupPlan = setupHandler.getSetupPlan(); - logger.log('The following setup process will be executed:'); - setupPlan.forEach(step => logger.log(` - ${step}`)); - const { proceed } = await inquirer.prompt([ - { - type: 'confirm', - name: 'proceed', - message: 'Do you want to continue?', - default: true, - }, - ]); - if (!proceed) { - logger.warn('Setup cancelled by user.'); - return; - } - - // Setup - await setupHandler.setup(); - - logger.br(); - logger.success(`Success to set up ${board}`); - if (board === 'host') { - logger.info(`Next step: run ${chalk.yellow('bscript project create -b host')}`); - } else { - logger.info(`Next step: run ${chalk.yellow(`bscript board flash-runtime ${board}`)}`); - } - - } catch (error) { - logger.error(`Failed to set up ${board}`); - logger.showError(error); - process.exit(1); - } -} - - -export function registerSetupCommand(program: Command) { - program - .command('setup') - .description('set up the environment for the specified board') - .argument('', 'name of the board to setup (e.g., esp32)') - .action(handleSetupCommand); -} - - diff --git a/cli/src/commands/board/setup/base.ts b/cli/src/commands/board/setup/base.ts new file mode 100644 index 00000000..c4f5dbe7 --- /dev/null +++ b/cli/src/commands/board/setup/base.ts @@ -0,0 +1,62 @@ +import { runStep, skip } from "../../../core/logger"; +import { StepSkip } from "../../../core/logger/step-runner"; +import { CommandHandlerWithUpdateCheck } from "../../command"; +import { BoardName } from "../../../config/board-utils"; +import { CommonBoardEnv } from "../../../platforms/board-env/common-env"; + + +export interface Step { + description: string; + actionMessage: string; + action: () => Promise; +} + + +export abstract class SetupHandler extends CommandHandlerWithUpdateCheck { + abstract boardName: BoardName; + abstract boardEnv: CommonBoardEnv; + protected setupSteps: Step[] = []; + + constructor() { + super(); + } + + loadSetupSteps() { + this.setupSteps.push({ + description: `Download BlueScript runtime from ${this.boardEnv.runtimeZipUrl}.`, + actionMessage: `Downloading BlueScript runtime...`, + action: this.downloadBlueScriptRuntimeStep.bind(this) + }); + this.loadBoardSetupSteps(); + } + + needSetup() { + return !this.globalConfigHandler.isBoardSetup(this.boardName); + } + + async setup() { + this.boardEnv.ensureBlueScriptDir(); + this.boardEnv.refreshBoardRoot(); + for (const step of this.setupSteps) { + await runStep(step.actionMessage, step.action); + } + await this.setBoardConfig(); + this.globalConfigHandler.save(); + }; + + getSetupPlan(): string[] { + return this.setupSteps.map(step => step.description); + }; + + abstract loadBoardSetupSteps(): void; + abstract setBoardConfig(): Promise; + + protected async downloadBlueScriptRuntimeStep() { + if (this.globalConfigHandler.isRuntimeSetup()) { + return skip('already downloaded.'); + } + + await this.boardEnv.downloadBlueScriptRuntime(); + this.globalConfigHandler.setRuntimeDir(this.boardEnv.runtimeDir); + } +} diff --git a/cli/src/commands/board/setup/esp32.ts b/cli/src/commands/board/setup/esp32.ts new file mode 100644 index 00000000..03f846ae --- /dev/null +++ b/cli/src/commands/board/setup/esp32.ts @@ -0,0 +1,152 @@ +import { SetupHandler } from "./base"; +import { execWithLog } from '../../../core/command-exec'; +import { skip } from "../../../core/logger"; +import * as path from 'path'; +import { BoardName } from "../../../config/board-utils"; +import { Esp32DarwinEnv, Esp32WindowsEnv } from "../../../platforms/board-env/esp32-env"; + + +export class Esp32DarwinSetupHandler extends SetupHandler { + boardName: BoardName = "esp32"; + boardEnv: Esp32DarwinEnv; + pythonCommand?: string; + makeCommand?: string; + + constructor() { + super(); + this.boardEnv = new Esp32DarwinEnv(); + } + + loadBoardSetupSteps(): void { + this.setupSteps.push({ + description: "Verify that git, python3, brew and make are installed.", + actionMessage: "Verifying that git, python3 and brew are installed...", + action: this.verifyPrerequisitsInstalledStep.bind(this), + }); + this.setupSteps.push({ + description: "Install required packages via brew if they are not installed (cmake, ninja, dfu-util, and ccache).", + actionMessage: "Installing required packages...", + action: this.installRequiredPackagesStep.bind(this), + }); + this.setupSteps.push({ + description: `Clone ESP-IDF ${this.boardEnv.idfVersion} from ${this.boardEnv.idfGitRepo}.`, + actionMessage: `Cloning ESP-IDF ${this.boardEnv.idfVersion}... It may take a while.`, + action: this.cloneEspIdfStep.bind(this), + }); + this.setupSteps.push({ + description: "Run ESP-IDF install script.", + actionMessage: "Running ESP-IDF install script...", + action: this.runEspIdfInstallScriptStep.bind(this), + }); + } + + async setBoardConfig() { + const xtensaGccDir = await this.boardEnv.getXtensaGccDir(this.pythonCommand!); + this.globalConfigHandler.updateBoardConfig(this.boardName, { + idfVersion: this.boardEnv.idfVersion, + rootDir: this.boardEnv.espRootDir, + exportFile: this.boardEnv.idfExportFile, + toolchain: { + gcc: path.join(xtensaGccDir, this.boardEnv.xtensaGccFileName), + ar: path.join(xtensaGccDir, this.boardEnv.xtensaArFileName), + ld: path.join(xtensaGccDir, this.boardEnv.xtensaLdFileName), + make: this.makeCommand!, + python: this.pythonCommand! + }, + }); + } + + private async verifyPrerequisitsInstalledStep() { + if (!await this.boardEnv.isPackageInstalled("git")) { + throw new Error("Cannot find git command. Please install git and try again."); + } + if (!await this.boardEnv.isPackageInstalled("brew")) { + throw new Error("Cannot find brew command. Please install Homebrew and try again."); + } + this.pythonCommand = await this.boardEnv.getPythonCommand(); + this.makeCommand = await this.boardEnv.getMakeCommand(); + } + + private async installRequiredPackagesStep() { + let packages: string[] = []; + if (!(await this.boardEnv.isPackageInstalled('cmake'))) { packages.push('cmake'); } + if (!(await this.boardEnv.isPackageInstalled('ninja'))) { packages.push('ninja'); } + if (!(await this.boardEnv.isPackageInstalled('dfu-util'))) { packages.push('dfu-util'); } + if (!(await this.boardEnv.isPackageInstalled('ccache'))) { packages.push('ccache'); } + if (packages.length === 0) { + return skip('already installed.'); + } + await execWithLog('brew', ['install', ...packages]); + } + + private async cloneEspIdfStep() { + await this.boardEnv.cloneEspIdf(); + } + + private async runEspIdfInstallScriptStep() { + await this.boardEnv.runEspIdfInstallScript(); + } +} + +export class Esp32WindowsSetupHandler extends SetupHandler { + boardName: BoardName = "esp32"; + boardEnv: Esp32WindowsEnv; + makeCommand?: string; + pythonCommand?: string; + + constructor() { + super(); + this.boardEnv = new Esp32WindowsEnv(); + } + + loadBoardSetupSteps(): void { + this.setupSteps.push({ + description: "Verify that git, python3 and make are installed.", + actionMessage: "Verifying that git and python3 are installed...", + action: this.verifyPrerequisitsInstalledStep.bind(this), + }); + this.setupSteps.push({ + description: `Clone ESP-IDF ${this.boardEnv.idfVersion} from ${this.boardEnv.idfGitRepo}.`, + actionMessage: `Cloning ESP-IDF ${this.boardEnv.idfVersion}... It may take a while.`, + action: this.cloneEspIdfStep.bind(this), + }); + this.setupSteps.push({ + description: "Run ESP-IDF install script.", + actionMessage: "Running ESP-IDF install script...", + action: this.runEspIdfInstallScriptStep.bind(this), + }); + } + + async setBoardConfig() { + const xtensaGccDir = await this.boardEnv.getXtensaGccDir(this.pythonCommand!); + this.globalConfigHandler.updateBoardConfig(this.boardName, { + idfVersion: this.boardEnv.idfVersion, + rootDir: this.boardEnv.espRootDir, + exportFile: this.boardEnv.idfExportFile, + toolchain: { + gcc: path.join(xtensaGccDir, this.boardEnv.xtensaGccFileName), + ar: path.join(xtensaGccDir, this.boardEnv.xtensaArFileName), + ld: path.join(xtensaGccDir, this.boardEnv.xtensaLdFileName), + make: this.makeCommand!, + python: this.pythonCommand! + }, + }); + } + + private async verifyPrerequisitsInstalledStep() { + // git command + if (!await this.boardEnv.isPackageInstalled("git")) { + throw new Error("Cannot find git command. Please install git and try again."); + } + this.pythonCommand = await this.boardEnv.getPythonCommand(); + this.makeCommand = await this.boardEnv.getMakeCommand(); + } + + private async cloneEspIdfStep() { + await this.boardEnv.cloneEspIdf(); + } + + private async runEspIdfInstallScriptStep() { + await this.boardEnv.runEspIdfInstallScript(); + } +} \ No newline at end of file diff --git a/cli/src/commands/board/setup/host.ts b/cli/src/commands/board/setup/host.ts new file mode 100644 index 00000000..1b930b81 --- /dev/null +++ b/cli/src/commands/board/setup/host.ts @@ -0,0 +1,100 @@ +import { SetupHandler } from "./base"; +import { BoardName } from "../../../config/board-utils"; +import { HostDarwinEnv, HostWindowsEnv } from "../../../platforms/board-env/host-env"; + + +export class HostDarwinSetupHandler extends SetupHandler { + boardName: BoardName = 'host'; + boardEnv: HostDarwinEnv; + gccCommand?: string; + arCommand?: string; + makeCommand?: string; + + constructor() { + super(); + this.boardEnv = new HostDarwinEnv(); + } + + loadBoardSetupSteps(): void { + this.setupSteps.push({ + description: "Verify that cc and make are installed.", + actionMessage: "Verifying that cc and make are installed...", + action: this.verifyPrerequisitsInstalledStep.bind(this), + }); + this.setupSteps.push({ + description: "Build host runtime.", + actionMessage: "Building host runtime...", + action: this.buildHostRuntimeStep.bind(this), + }); + } + + async setBoardConfig() { + this.globalConfigHandler.setBoardConfig('host', { + rootDir: this.boardEnv.hostRootDir, + shellFile: this.boardEnv.shellFile, + toolchain: { + gcc: this.gccCommand!, + ar: this.arCommand!, + make: this.makeCommand! + }, + }) + } + + private async verifyPrerequisitsInstalledStep() { + this.gccCommand = await this.boardEnv.getGccCommand(); + this.arCommand = await this.boardEnv.getArCommand(); + this.makeCommand = await this.boardEnv.getMakeCommand(); + } + + private async buildHostRuntimeStep() { + await this.boardEnv.buildHostRuntime(); + } +} + +export class HostWindowsSetupHandler extends SetupHandler { + boardName: BoardName = 'host'; + boardEnv: HostWindowsEnv; + gccCommand?: string; + arCommand?: string; + makeCommand?: string; + + constructor() { + super(); + this.boardEnv = new HostWindowsEnv(); + } + + loadBoardSetupSteps(): void { + this.setupSteps.push({ + description: "Verify that MinGW is installed.", + actionMessage: "Verifying that MinGW is installed...", + action: this.verifyMingwIsInstalledStep.bind(this), + }); + this.setupSteps.push({ + description: "Build host runtime.", + actionMessage: "Building host runtime...", + action: this.buildHostRuntimeStep.bind(this), + }); + } + + async setBoardConfig() { + this.globalConfigHandler.setBoardConfig('host', { + rootDir: this.boardEnv.hostRootDir, + shellFile: this.boardEnv.shellFile, + toolchain: { + gcc: this.gccCommand!, + ar: this.arCommand!, + make: this.makeCommand! + }, + }); + } + + private async verifyMingwIsInstalledStep() { + this.gccCommand = await this.boardEnv.getGccCommand(); + this.arCommand = await this.boardEnv.getArCommand(); + this.makeCommand = await this.boardEnv.getMakeCommand(); + } + + private async buildHostRuntimeStep() { + await this.boardEnv.buildHostRuntime(); + } +} \ No newline at end of file diff --git a/cli/src/commands/board/setup/index.ts b/cli/src/commands/board/setup/index.ts new file mode 100644 index 00000000..9570c1df --- /dev/null +++ b/cli/src/commands/board/setup/index.ts @@ -0,0 +1,85 @@ +import { Command } from "commander"; +import * as os from 'os'; +import inquirer from 'inquirer'; +import { logger } from "../../../core/logger"; +import chalk from "chalk"; +import { SetupHandler } from "./base"; +import { Esp32DarwinSetupHandler, Esp32WindowsSetupHandler } from "./esp32"; +import { HostDarwinSetupHandler, HostWindowsSetupHandler } from "./host"; + + +function getSetupHandler(board: string): SetupHandler { + const osType = os.platform(); + if (board === 'esp32') { + if (osType === 'darwin') + return new Esp32DarwinSetupHandler(); + if (osType === 'win32') + return new Esp32WindowsSetupHandler(); + throw new Error(`Unsupported OS type: ${osType}.`); + } + if (board === 'host') { + if (osType === 'darwin') + return new HostDarwinSetupHandler(); + if (osType === 'win32') + return new HostWindowsSetupHandler(); + throw new Error(`Unsupported OS type: ${osType}.`); + } + throw new Error(`Unsupported board name: ${board}`); +} + +export async function handleSetupCommand(board: string) { + try { + const setupHandler = getSetupHandler(board); + + // Check if setup has already been completed. + if (!setupHandler.needSetup()) { + logger.warn(`The setup for ${board} has already been completed.`); + return; + } + setupHandler.loadSetupSteps(); + + // Ask user if it's ok to proceed with setup. + const setupPlan = setupHandler.getSetupPlan(); + logger.log('The following setup process will be executed:'); + setupPlan.forEach(step => logger.log(` - ${step}`)); + const { proceed } = await inquirer.prompt([ + { + type: 'confirm', + name: 'proceed', + message: 'Do you want to continue?', + default: true, + }, + ]); + if (!proceed) { + logger.warn('Setup cancelled by user.'); + return; + } + + // Setup + await setupHandler.setup(); + + logger.br(); + logger.success(`Success to set up ${board}`); + if (board === 'host') { + logger.info(`Next step: run ${chalk.yellow('bscript project create -b host')}`); + } else { + logger.info(`Next step: run ${chalk.yellow(`bscript board flash-runtime ${board}`)}`); + } + + } catch (error) { + logger.error(`Failed to set up ${board}`); + logger.showError(error); + process.exit(1); + } +} + + +export function registerSetupCommand(program: Command) { + program + .command('setup') + .description('set up the environment for the specified board') + .argument('', 'name of the board to setup (e.g., esp32)') + .action(handleSetupCommand); +} + + diff --git a/cli/src/commands/board/update.ts b/cli/src/commands/board/update.ts index a3d3d52b..85634deb 100644 --- a/cli/src/commands/board/update.ts +++ b/cli/src/commands/board/update.ts @@ -3,37 +3,60 @@ import { logger, runStep, skip } from "../../core/logger"; import { CommandHandler } from "../command"; import { GLOBAL_SETTINGS } from "../../config/constants"; import * as fs from '../../core/fs'; -import { exec } from "../../core/shell"; import * as path from 'path'; -import { buildHostRuntime } from "../../platforms/runtime/host-board-runtime"; +import { CommonBoardEnv, createBoardEnv, Esp32Env } from "../../platforms/board-env"; +import { Esp32BoardConfig, GlobalConfig, GlobalConfigHandler } from "../../config/global-config"; +import chalk from "chalk"; class UpdateHandler extends CommandHandler { + private oldGlobalConfig?: GlobalConfig; + private globalConfigHandler: GlobalConfigHandler; private existingRuntimeDir: string | undefined; private existingEspDir: string | undefined; private tmpRuntimeDir = path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'tmp-runtime'); private tmpEspDir = path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'tmp-esp'); constructor() { - super(false); + super(); + const { handler, oldConfig } = this.getOldGlobalConfigAndHandler(); + this.oldGlobalConfig = oldConfig; + this.globalConfigHandler = handler; + } + + private getOldGlobalConfigAndHandler() { + if (!GlobalConfigHandler.isGlobalConfigFileExists()) { + logger.warn("The BlueScript environment is not setup."); + process.exit(1); + } + try { + const handler = GlobalConfigHandler.load(); + const oldConfig = structuredClone(handler.getConfig()); + if (oldConfig.version === GLOBAL_SETTINGS.VM_VERSION) { + logger.warn("Update is not needed."); + process.exit(0); + } + return { handler, oldConfig }; + } catch (error) { + const handler = GlobalConfigHandler.loadEmpty(); + const oldConfig = GlobalConfigHandler.getConfigWithoutCheck(); + return { handler, oldConfig }; + } } async update() { try { await this.updateRuntimeStep(); - if (this.globalConfigHandler.isBoardSetup('esp32')) { - await this.updateEsp32Step(); - } - if (this.globalConfigHandler.isBoardSetup('host')) { - await this.updateHostStep(); - } + await this.updateEsp32Step(); + await this.updateHostStep(); this.globalConfigHandler.setVersion(GLOBAL_SETTINGS.VM_VERSION); + this.globalConfigHandler.save(); } catch (error) { // Restore - if (this.existingRuntimeDir) { + if (this.existingRuntimeDir && fs.exists(this.tmpRuntimeDir)) { fs.moveDir(this.tmpRuntimeDir, this.existingRuntimeDir); } - if (this.existingEspDir) { + if (this.existingEspDir && fs.exists(this.tmpEspDir)) { fs.moveDir(this.tmpEspDir, this.existingEspDir); } throw error; @@ -45,81 +68,88 @@ class UpdateHandler extends CommandHandler { fs.removeDir(this.tmpEspDir); } } - this.globalConfigHandler.save(); } private updateRuntimeStep() { return runStep('Updating Runtime...', async () => { - const globalConfig = this.globalConfigHandler.getConfig(); - if (globalConfig.runtimeDir === undefined || globalConfig.version === GLOBAL_SETTINGS.VM_VERSION) { + if (this.oldGlobalConfig?.runtimeDir === undefined) { return skip('not needed'); } - this.existingRuntimeDir = globalConfig.runtimeDir; - await this.updateRuntime(this.existingRuntimeDir); + await this.updateRuntime(); }); } private updateEsp32Step() { return runStep('Updating the environment for esp32...', async () => { - const esp32Config = this.globalConfigHandler.getBoardConfig('esp32')!; - if (esp32Config.idfVersion === GLOBAL_SETTINGS.ESP_IDF_VERSION) { - return skip('not needed'); + if (!("esp32" in (this.oldGlobalConfig?.boards ?? {}))) { + return skip('not setup'); } - this.existingEspDir = esp32Config.rootDir; - await this.updateEsp32(this.existingEspDir); + const esp32Config = this.oldGlobalConfig?.boards.esp32; + const esp32Env = createBoardEnv('esp32'); + await this.updateEsp32(esp32Env, esp32Config); }); } private updateHostStep() { return runStep('Updating the environment for host...', async () => { - const globalConfig = this.globalConfigHandler.getConfig(); - if (globalConfig.runtimeDir === undefined || globalConfig.version === GLOBAL_SETTINGS.VM_VERSION) { - return skip('not needed'); + if (!("host" in (this.oldGlobalConfig?.boards ?? {}))) { + return skip('not setup'); } - await this.updateHost(globalConfig.runtimeDir); - }); - } - - private async updateRuntime(existingRuntimeDir: string) { - fs.moveDir(existingRuntimeDir, this.tmpRuntimeDir); - await fs.downloadAndUnzip(GLOBAL_SETTINGS.RUNTIME_ZIP_URL, GLOBAL_SETTINGS.BLUESCRIPT_DIR); - this.globalConfigHandler.setRuntimeDir(GLOBAL_SETTINGS.RUNTIME_DIR); + await this.updateHost(); + }); } - private async updateHost(runtimeDir: string) { - const hostConfig = this.globalConfigHandler.getBoardConfig('host')!; - await buildHostRuntime(runtimeDir, hostConfig.buildDir); + private async updateRuntime() { + const env = new CommonBoardEnv(); + this.existingRuntimeDir = env.runtimeDir; + fs.moveDir(env.runtimeDir, this.tmpRuntimeDir); + + await env.downloadBlueScriptRuntime(); + this.globalConfigHandler.setRuntimeDir(env.runtimeDir); } - private async updateEsp32(existingEspDir: string) { - fs.moveDir(existingEspDir, this.tmpEspDir); - fs.makeDir(GLOBAL_SETTINGS.ESP_ROOT_DIR); - await this.cloneEspIdf(); - await this.runEspIdfInstallScript(); - this.globalConfigHandler.updateBoardConfig('esp32', { - idfVersion: GLOBAL_SETTINGS.ESP_IDF_VERSION, - rootDir: GLOBAL_SETTINGS.ESP_ROOT_DIR, - exportFile: GLOBAL_SETTINGS.ESP_IDF_EXPORT_FILE, - xtensaGccDir: await this.getXtensaGccDir(), + private async updateHost() { + const hostEnv = createBoardEnv('host'); + await hostEnv.buildHostRuntime(); + const boardConfig = this.oldGlobalConfig?.boards.host; + let gccCommand = boardConfig?.toolchain.gcc ?? await hostEnv.getGccCommand(); + let arCommand = boardConfig?.toolchain.ar ?? await hostEnv.getArCommand(); + let makeCommand = boardConfig?.toolchain.make ??await hostEnv.getMakeCommand(); + this.globalConfigHandler.setBoardConfig('host', { + rootDir: hostEnv.hostRootDir, + shellFile: hostEnv.shellFile, + toolchain: { + gcc: gccCommand, + ar: arCommand, + make: makeCommand, + }, }); } - private async cloneEspIdf() { - await exec(`git clone --depth 1 -b ${GLOBAL_SETTINGS.ESP_IDF_VERSION} --recursive ${GLOBAL_SETTINGS.ESP_IDF_GIT_REPO}`, - { cwd: GLOBAL_SETTINGS.ESP_ROOT_DIR }); - } - - private async runEspIdfInstallScript() { - await exec(GLOBAL_SETTINGS.ESP_IDF_INSTALL_FILE); - } + private async updateEsp32(esp32Env: Esp32Env, boardConfig?: Esp32BoardConfig) { + this.existingEspDir = esp32Env.espRootDir; - private async getXtensaGccDir() { - try { - const gccPath = await exec(`source ${GLOBAL_SETTINGS.ESP_IDF_EXPORT_FILE} > /dev/null 2>&1 && which xtensa-esp32-elf-gcc`, { silent:true }); - return path.dirname(gccPath); - } catch (error) { - throw new Error('Failed to get xtensa gcc path.', {cause: error}); + if (boardConfig?.idfVersion !== esp32Env.idfVersion) { + fs.moveDir(esp32Env.espRootDir, this.tmpEspDir); + esp32Env.refreshBoardRoot(); + await esp32Env.cloneEspIdf(); + await esp32Env.runEspIdfInstallScript(); } + let pythonCommand = boardConfig?.toolchain.python ?? await esp32Env.getPythonCommand(); + let makeCommand = boardConfig?.toolchain.make ?? await esp32Env.getMakeCommand(); + const xtensaGccDir = await esp32Env.getXtensaGccDir(pythonCommand); + this.globalConfigHandler.setBoardConfig('esp32', { + idfVersion: esp32Env.idfVersion, + rootDir: esp32Env.espRootDir, + exportFile: esp32Env.idfExportFile, + toolchain: { + gcc: path.join(xtensaGccDir, esp32Env.xtensaGccFileName), + ar: path.join(xtensaGccDir, esp32Env.xtensaArFileName), + ld: path.join(xtensaGccDir, esp32Env.xtensaLdFileName), + make: makeCommand, + python: pythonCommand + }, + }); } } @@ -134,6 +164,8 @@ export async function handleUpdateCommand() { } catch (error) { logger.error(`Failed to update board environments.`); logger.showError(error); + + logger.info(`If you cannot update, run ${chalk.yellow('bscript board fullclean')} and setup boards from the beginning.`); process.exit(1); } } diff --git a/cli/src/commands/command.ts b/cli/src/commands/command.ts index 925d6efe..97b5331f 100644 --- a/cli/src/commands/command.ts +++ b/cli/src/commands/command.ts @@ -4,13 +4,14 @@ import chalk from "chalk"; import { GLOBAL_SETTINGS } from "../config/constants"; import * as fs from '../core/fs'; -export abstract class CommandHandler { +export abstract class CommandHandler {} + +export abstract class CommandHandlerWithUpdateCheck extends CommandHandler { protected globalConfigHandler: GlobalConfigHandler; - constructor(checkUpdate = true) { - if (checkUpdate) { - this.checkUpdate(); - } + constructor() { + super(); + this.checkUpdate(); this.globalConfigHandler = GlobalConfigHandler.load(); } diff --git a/cli/src/commands/project/check.ts b/cli/src/commands/project/check.ts index 191bc20b..0049e428 100644 --- a/cli/src/commands/project/check.ts +++ b/cli/src/commands/project/check.ts @@ -1,11 +1,11 @@ import { Command } from "commander"; import { logger, runStep } from "../../core/logger"; import { ProjectConfigHandler } from "../../config/project-config"; -import { cwd } from "../../core/shell"; -import { CommandHandler } from "../command"; +import { cwd } from "../../core/command-exec"; +import { CommandHandlerWithUpdateCheck } from "../command"; import { CompilerAdapter, getCompilerAdapter } from "../../platforms"; -class CheckHandler extends CommandHandler { +class CheckHandler extends CommandHandlerWithUpdateCheck { private compilerAdapter: CompilerAdapter; constructor(private projectConfigHandler: ProjectConfigHandler) { diff --git a/cli/src/commands/project/create.ts b/cli/src/commands/project/create.ts index 07ab6428..20c43c72 100644 --- a/cli/src/commands/project/create.ts +++ b/cli/src/commands/project/create.ts @@ -4,10 +4,10 @@ import chalk from "chalk"; import * as path from 'path'; import { logger } from "../../core/logger"; import { ProjectConfigHandler } from "../../config/project-config"; -import { cwd } from "../../core/shell"; +import { cwd } from "../../core/command-exec"; import { BOARD_NAMES, BoardName, isValidBoard } from "../../config/board-utils"; import * as fs from '../../core/fs'; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; const ENTRY_FILE_CONTENTS = `console.log("Hello world!");\n`; @@ -16,7 +16,7 @@ const GIT_IGNORE_CONTENTS = `\ **/packages/ `; -class CreateHandler extends CommandHandler { +class CreateHandler extends CommandHandlerWithUpdateCheck { private board: BoardName; private projectRoot: string; private projectConfigHandler: ProjectConfigHandler; diff --git a/cli/src/commands/project/install.ts b/cli/src/commands/project/install.ts index 3b977d06..60b10fd0 100644 --- a/cli/src/commands/project/install.ts +++ b/cli/src/commands/project/install.ts @@ -1,14 +1,14 @@ import { Command } from "commander"; import { logger } from "../../core/logger"; import { ProjectConfigHandler, PackageSource ,PROJECT_DEFAULT_PATHS } from "../../config/project-config"; -import { cwd, exec } from "../../core/shell"; +import { cwd, simpleExec } from "../../core/command-exec"; import * as fs from '../../core/fs'; import * as path from 'path'; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; import { GLOBAL_SETTINGS } from "../../config/constants"; -class InstallationHandler extends CommandHandler { +class InstallationHandler extends CommandHandlerWithUpdateCheck { private projectConfigHandler: ProjectConfigHandler; private projectRootDir: string; private packagesDir: string; @@ -65,10 +65,9 @@ class InstallationHandler extends CommandHandler { private async downloadPackage(url: string, version?: string): Promise { logger.log(`Downloading from ${url}...`); const tmpDir = path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'tmp-package'); - const branchCmd = version ? `--branch ${version}` : ''; - const cmd = `git clone --depth 1 ${branchCmd} ${url} ${tmpDir}`; + const branchArgs = version ? ['--branch', version] : []; try { - await exec(cmd, {silent: true}); + await simpleExec('git', ['clone', '--depth', '1', ...branchArgs, url, tmpDir]); const gitDir = path.join(tmpDir, '.git'); if (fs.exists(gitDir)) { fs.removeDir(gitDir); diff --git a/cli/src/commands/project/run.ts b/cli/src/commands/project/run.ts index 47bc29e6..19288f14 100644 --- a/cli/src/commands/project/run.ts +++ b/cli/src/commands/project/run.ts @@ -7,14 +7,14 @@ import path from 'path'; import { logger, ProgramOutput, createBoxedOutput, createConsoleOutput, createWebSocketOutput, runStep, LoadStepLogger } from "../../core/logger"; import { ProjectConfigHandler } from "../../config/project-config"; -import { cwd, exec } from "../../core/shell"; -import { CommandHandler } from "../command"; +import { cwd, ExecOptions, simpleExec } from "../../core/command-exec"; +import { CommandHandlerWithUpdateCheck } from "../command"; import { BoardRuntime, CompilerAdapter, createPlatformSession } from "../../platforms"; import { CompileError, CompileOutput } from "@bscript/lang"; import { WebSocketConnection } from "../../services/websocket"; import { SerialTaskQueue } from "../../core/serial-task-queue"; -class RunHandler extends CommandHandler { +class RunHandler extends CommandHandlerWithUpdateCheck { protected compiler: CompilerAdapter; protected runtime: BoardRuntime; protected programOutput: ProgramOutput; @@ -128,18 +128,9 @@ class RunHandler extends CommandHandler { } class RunWithReplHandler extends RunHandler { - private rl: readline.Interface; + private rl?: readline.Interface; private readonly taskQueue = new SerialTaskQueue(); - constructor(projectConfigHandler: ProjectConfigHandler) { - super(projectConfigHandler); - this.rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - prompt: chalk.blue.bold('> ') - }); - } - async run() { const interrupted = await super.run(); if (interrupted) { @@ -151,12 +142,23 @@ class RunWithReplHandler extends RunHandler { return false; } + async close() { + this.rl?.close(); + await super.close(); + } + private runRepl() { logger.info("Start REPL. Type 'Ctrl-D' to exit."); - this.rl.prompt(); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + prompt: chalk.blue.bold('> '), + }); + this.rl = rl; + rl.prompt(); return new Promise((resolve, reject) => { - this.rl.on('line', (line) => { - this.rl.pause(); + rl.on('line', (line) => { + rl.pause(); this.taskQueue.enqueue(async () => { try { const output = await this.compiler.compileFragment(line); @@ -170,12 +172,12 @@ class RunWithReplHandler extends RunHandler { return; } } finally { - this.rl.resume(); - this.rl.prompt(); + rl.resume(); + rl.prompt(); } }); }); - this.rl.on('close', () => { + rl.on('close', () => { resolve(); }); }); @@ -237,13 +239,15 @@ class RunWithNotebookHandler extends RunHandler { } - private openBrowser(port: number|string) { + private async openBrowser(port: number | string) { const url = `http://localhost:${port}`; - const startCommand = - process.platform === 'win32' ? 'start' : - process.platform === 'darwin' ? 'open' : 'xdg-open'; - - exec(`${startCommand} ${url}`, {silent: true}); + if (process.platform === 'win32') { + await simpleExec('cmd.exe', ['/c', 'start', '', url], { detached: true, stdio: 'ignore' }); + } else if (process.platform === 'darwin') { + await simpleExec('open', [url], { detached: true, stdio: 'ignore' }); + } else { + await simpleExec('xdg-open', [url], { detached: true, stdio: 'ignore' }); + } } private startWebsocket() { diff --git a/cli/src/commands/project/uninstall.ts b/cli/src/commands/project/uninstall.ts index 5aa920aa..16483a1b 100644 --- a/cli/src/commands/project/uninstall.ts +++ b/cli/src/commands/project/uninstall.ts @@ -1,13 +1,13 @@ import { Command } from "commander"; import { logger } from "../../core/logger"; import { ProjectConfigHandler, PROJECT_DEFAULT_PATHS } from "../../config/project-config"; -import { cwd } from "../../core/shell"; +import { cwd } from "../../core/command-exec"; import * as fs from '../../core/fs'; import * as path from 'path'; -import { CommandHandler } from "../command"; +import { CommandHandlerWithUpdateCheck } from "../command"; -class UninstallHandler extends CommandHandler { +class UninstallHandler extends CommandHandlerWithUpdateCheck { private projectDir: string; private projectConfigHandler: ProjectConfigHandler; diff --git a/cli/src/commands/repl.ts b/cli/src/commands/repl.ts index 8dd4d58b..235f3cd9 100644 --- a/cli/src/commands/repl.ts +++ b/cli/src/commands/repl.ts @@ -6,7 +6,7 @@ import * as path from 'path'; import * as readline from 'readline'; import chalk from "chalk"; import * as fs from '../core/fs'; -import { CommandHandler } from "./command"; +import { CommandHandlerWithUpdateCheck } from "./command"; import { GLOBAL_SETTINGS } from "../config/constants"; import { CompileContext, createPlatformSession } from "../platforms"; import { BoardName } from "../config/board-utils"; @@ -23,9 +23,11 @@ function defaultReplReadlineFactory(): readline.Interface { }); } -class ReplHandler extends CommandHandler { +class ReplHandler extends CommandHandlerWithUpdateCheck { static readonly TEMP_PROJECT_NAME = 'temp'; - static readonly tempProjectDir = path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, this.TEMP_PROJECT_NAME); + static get tempProjectDir(): string { + return path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, ReplHandler.TEMP_PROJECT_NAME); + } private projectConfigHandler: ProjectConfigHandler; private platform: ReturnType; @@ -68,10 +70,10 @@ class ReplHandler extends CommandHandler { this.createTempProject(); await this.runRepl(); - this.deleteTempProject(); this.rl.close(); await runStep('Disconnecting...', () => this.platform.runtime.disconnect()); + this.deleteTempProject(); process.exit(0); } diff --git a/cli/src/config/constants.ts b/cli/src/config/constants.ts index a1b36e7a..1b170049 100644 --- a/cli/src/config/constants.ts +++ b/cli/src/config/constants.ts @@ -15,32 +15,4 @@ export const GLOBAL_SETTINGS = { get BLUESCRIPT_CONFIG_FILE() { return path.join(this.BLUESCRIPT_DIR, 'config.json'); }, - - get RUNTIME_ZIP_URL() { - return `https://github.com/csg-tokyo/bluescript/releases/download/v${this.VM_VERSION}/release-microcontroller-v${this.VM_VERSION}.zip`; - }, - - get RUNTIME_DIR() { - return path.join(this.BLUESCRIPT_DIR, 'microcontroller'); - }, - - get ESP_ROOT_DIR() { - return path.join(this.BLUESCRIPT_DIR, 'esp'); - }, - - get ESP_IDF_VERSION() { - return 'v5.4'; - }, - - get ESP_IDF_GIT_REPO() { - return 'https://github.com/espressif/esp-idf.git'; - }, - - get ESP_IDF_EXPORT_FILE() { - return path.join(this.ESP_ROOT_DIR, 'esp-idf/export.sh'); - }, - - get ESP_IDF_INSTALL_FILE() { - return path.join(this.ESP_ROOT_DIR, 'esp-idf/install.sh'); - } } diff --git a/cli/src/config/global-config.ts b/cli/src/config/global-config.ts index 223ee6e3..9074a870 100644 --- a/cli/src/config/global-config.ts +++ b/cli/src/config/global-config.ts @@ -8,11 +8,23 @@ const esp32BoardSchema = z.object({ idfVersion: z.string(), rootDir: z.string(), exportFile: z.string(), - xtensaGccDir: z.string(), + toolchain: z.object({ + gcc: z.string(), + ar: z.string(), + ld: z.string(), + make: z.string(), + python: z.string(), + }), }); const hostBoardSchema = z.object({ - buildDir: z.string(), + rootDir: z.string(), + shellFile: z.string(), + toolchain: z.object({ + gcc: z.string(), + ar: z.string(), + make: z.string(), + }), }); const boardConfigSchema = z.object({ @@ -39,8 +51,8 @@ export class GlobalConfigHandler { } static load() { - if (!fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE)) { - return new GlobalConfigHandler(globalConfigSchema.parse({version: GLOBAL_SETTINGS.VM_VERSION})); + if (!this.isGlobalConfigFileExists()) { + return this.loadEmpty() } try { const fileContent = fs.readFile(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE); @@ -67,6 +79,19 @@ export class GlobalConfigHandler { } } + static loadEmpty() { + return new GlobalConfigHandler(globalConfigSchema.parse({ version: GLOBAL_SETTINGS.VM_VERSION })); + } + + static getConfigWithoutCheck() { + const fileContent = fs.readFile(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE); + return JSON.parse(fileContent) as GlobalConfig; + } + + static isGlobalConfigFileExists() { + return fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE); + } + update(config: Partial) { try { this.config = globalConfigSchema.parse({ @@ -117,6 +142,15 @@ export class GlobalConfigHandler { return this.config.boards?.[boardName]; } + setBoardConfig(boardName: K, boardConfig: BoardConfig[K]) { + this.update({ + boards: { + ...this.config.boards, + [boardName]: boardConfig + } + }); + } + updateBoardConfig(boardName: K, boardConfig: Partial) { const existingBoardConfig = this.config.boards[boardName] ?? {}; const mergedBoardConfig = { diff --git a/cli/src/core/command-exec.ts b/cli/src/core/command-exec.ts new file mode 100644 index 00000000..d5235138 --- /dev/null +++ b/cli/src/core/command-exec.ts @@ -0,0 +1,148 @@ +import { spawn, SpawnOptions } from "child_process"; +import { logger } from "./logger"; +import { exists } from "./fs"; + +export function cwd() { + return process.cwd(); +} + +export type ExecOptions = { + cwd?: string; + detached?: boolean; + stdio?: 'ignore' | 'pipe'; +}; + +type ProcessOutput = { + stdout: string; + stderr: string; +}; + +type ProcessStreamHooks = { + onStdoutData?: (chunk: string) => void; + onStderrData?: (chunk: string) => void; +}; + +type FailureMode = 'stderr' | 'detailed'; + +function validateCwd(cwd?: string): void { + if (cwd && !exists(cwd)) { + throw new Error(`${cwd} does not exist.`); + } +} + +function formatCommand(command: string, args: string[]): string { + return [command, ...args].join(' '); +} + +function rejectProcessFailure( + formattedCommand: string, + code: number | null, + stdout: string, + stderr: string, + failureMode: FailureMode, + spawnError?: Error, +): Error { + if (spawnError) { + return new Error(spawnError.message); + } + if (failureMode === 'stderr') { + return new Error(stderr || `Command failed: ${formattedCommand}`); + } + return new Error(getErrorMessage(formattedCommand, code, stdout, stderr)); +} + +function runProcess( + command: string, + args: string[], + options: ExecOptions = {}, + hooks?: ProcessStreamHooks, + failureMode: FailureMode = 'detailed', +): Promise { + const { cwd, detached = false, stdio = 'pipe' } = options; + const formattedCommand = formatCommand(command, args); + + return new Promise((resolve, reject) => { + const spawnOptions: SpawnOptions = { shell: false, cwd, detached, stdio }; + const child = spawn(command, args, spawnOptions); + let stdout = ''; + let stderr = ''; + + if (stdio !== 'ignore' && child.stdout) { + child.stdout.on('data', (data) => { + const chunk = data.toString(); + hooks?.onStdoutData?.(chunk); + stdout += chunk; + }); + } + + if (stdio !== 'ignore' && child.stderr) { + child.stderr.on('data', (data) => { + const chunk = data.toString(); + hooks?.onStderrData?.(chunk); + stderr += chunk; + }); + } + + child.on('error', (err) => { + reject(rejectProcessFailure(formattedCommand, null, stdout, stderr, failureMode, err)); + }); + + child.on('close', (code) => { + if (code === 0) { + resolve({ stdout, stderr }); + } else { + reject(rejectProcessFailure(formattedCommand, code, stdout, stderr, failureMode)); + } + }); + + if (detached) { + child.unref(); + } + }); +} + +export async function simpleExec( + command: string, + args: string[], + options?: ExecOptions, +): Promise { + validateCwd(options?.cwd); + const { stdout } = await runProcess(command, args, options, undefined, 'stderr'); + return stdout; +} + +export async function execWithLog( + command: string, + args: string[], + options?: ExecOptions, +): Promise { + validateCwd(options?.cwd); + + const formattedCommand = formatCommand(command, args); + logger.log(`Executing ${formattedCommand}`); + + const { stdout } = await runProcess(command, args, options, { + onStdoutData: (chunk) => process.stdout.write(chunk), + onStderrData: (chunk) => process.stderr.write(chunk), + }, 'stderr'); + return stdout; +} + +export async function execShell(command: string, options?: { cwd?: string }): Promise { + validateCwd(options?.cwd); + + if (process.platform === 'win32') { + await execWithLog('cmd.exe', ['/c', command], { cwd: options?.cwd }); + } else { + await execWithLog('/bin/sh', ['-c', command], { cwd: options?.cwd }); + } +} + +function getErrorMessage(command: string, code: number|null, stdout: string, stderr: string) { + let message = `Command failed: ${command}\n`; + if (code) + message += `> Exit code: ${code}\n`; + message += `> Stdout: ${stdout === '' ? 'N/A' : stdout}\n`; + message += `> Stderr: ${stderr === '' ? 'N/A' : stderr}\n`; + return message; +} diff --git a/cli/src/core/fs.ts b/cli/src/core/fs.ts index d79c2a1b..49b9a9c7 100644 --- a/cli/src/core/fs.ts +++ b/cli/src/core/fs.ts @@ -8,7 +8,7 @@ export function makeDir(path: string, recursive: boolean = true) { } export function removeDir(path: string) { - fs.rmSync(path, { recursive: true }); + fs.rmSync(path, { recursive: true, force: true }); } export function moveDir(from: string, to: string) { diff --git a/cli/src/core/shell.ts b/cli/src/core/shell.ts deleted file mode 100644 index a3490381..00000000 --- a/cli/src/core/shell.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { spawn } from "child_process"; -import { logger } from "./logger"; -import { exists } from "./fs"; - -export function cwd() { - return process.cwd(); -} - -export function exec(command: string, options?: {cwd?: string, silent?: boolean}): Promise { - const {cwd, silent = false} = options ?? {}; - - if (cwd && !exists(cwd)) { - throw new Error(`${cwd} does not exist.`); - } - if (!silent) { - logger.log(`Executing ${command}`); - } - - return new Promise((resolve, reject) => { - const executeProcess = spawn(command, {shell: true, cwd}); - let stdout = ''; - let stderr = ''; - - executeProcess.stdout.on('data', (data) => { - const chunk = data.toString(); - if (!silent) { - process.stdout.write(chunk); - } - stdout += chunk; - }); - - executeProcess.stderr.on('data', (data) => { - const chunk = data.toString(); - if (!silent) { - process.stderr.write(chunk); - } - stderr += chunk; - }); - - executeProcess.on('error', (err) => { - const message = getErrorMessage(command, null, stdout, stderr); - reject(new Error(message)); - }); - - executeProcess.on('close', (code) => { - if (code === 0) { - resolve(stdout); - } else { - const message = getErrorMessage(command, code, stdout, stderr); - reject(new Error(message)); - } - }); - }); -} - -function getErrorMessage(command: string, code: number|null, stdout: string, stderr: string) { - let message = `Command failed: ${command}\n`; - if (code) - message += `> Exit code: ${code}\n`; - message += `> Stdout: ${stdout === '' ? 'N/A' : stdout}\n`; - message += `> Stderr: ${stderr === '' ? 'N/A' : stderr}\n`; - return message; -} \ No newline at end of file diff --git a/cli/src/index.ts b/cli/src/index.ts index cfde62d8..de0ca217 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -4,7 +4,7 @@ import { Command } from 'commander'; import { logger } from './core/logger'; import packageJson from '../package.json'; -import { registerSetupCommand } from './commands/board/setup'; +import { registerSetupCommand } from './commands/board/setup/index'; import { registerRemoveCommand } from './commands/board/remove'; import { registerFlashRuntimeCommand } from './commands/board/flash-runtime'; import { registerListCommand } from './commands/board/list'; diff --git a/cli/src/platforms/board-env/common-env.ts b/cli/src/platforms/board-env/common-env.ts new file mode 100644 index 00000000..3890ce81 --- /dev/null +++ b/cli/src/platforms/board-env/common-env.ts @@ -0,0 +1,95 @@ +import * as path from 'path'; +import * as fs from '../../core/fs'; +import { GLOBAL_SETTINGS } from '../../config/constants'; +import { simpleExec } from '../../core/command-exec'; + + +export abstract class BoardEnv { + get runtimeDir() { + return path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'microcontroller'); + } + + get runtimeZipUrl() { + const v = GLOBAL_SETTINGS.VM_VERSION; + return `https://github.com/csg-tokyo/bluescript/releases/download/v${v}/release-microcontroller-v${v}.zip`; + } + + ensureBlueScriptDir() { + if (!fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_DIR)) { + fs.makeDir(GLOBAL_SETTINGS.BLUESCRIPT_DIR); + } + } + + removeBlueScriptDir() { + if (fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_DIR)) { + fs.removeDir(GLOBAL_SETTINGS.BLUESCRIPT_DIR); + } + } + + abstract removeBoardRoot(): void; + abstract refreshBoardRoot(): void; + abstract isPackageInstalled(name: string): Promise; + + isPythonVersionGreaterThan3(): Promise { + return isPythonVersionGreaterThan3(); + }; + + async downloadBlueScriptRuntime() { + if (fs.exists(this.runtimeDir)) { + fs.removeDir(this.runtimeDir); + } + await fs.downloadAndUnzip(this.runtimeZipUrl, GLOBAL_SETTINGS.BLUESCRIPT_DIR); + } + + needUpdate(): boolean { + if (!fs.exists(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE)) { + return false; + } + const currentVersion = GLOBAL_SETTINGS.VM_VERSION; + const configFile = JSON.parse(fs.readFile(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE)); + const existingVersion = configFile.version; + return currentVersion !== existingVersion; + } +} + +export class CommonBoardEnv extends BoardEnv { + removeBoardRoot(): void {} + refreshBoardRoot(): void {} + async isPackageInstalled(name: string): Promise { + return false; + } + async isPythonVersionGreaterThan3(): Promise { + return false; + } +} + +export async function isPackageInstalledOnUnix(name: string) { + try { + await simpleExec('which', [name]); + return true; + } catch { + return false; + } +} + +export async function isPackageInstalledOnWindows(name: string) { + try { + await simpleExec('where.exe', [name]); + return true; + } catch { + return false; + } +} + +export async function isPythonVersionGreaterThan3() { + try { + const result = await simpleExec( + 'python', + ['-c', 'import sys; print(sys.version_info.major)'], + ); + return result.trim() === '3'; + } catch { + return false; + } +} + diff --git a/cli/src/platforms/board-env/esp32-env.ts b/cli/src/platforms/board-env/esp32-env.ts new file mode 100644 index 00000000..702446cd --- /dev/null +++ b/cli/src/platforms/board-env/esp32-env.ts @@ -0,0 +1,174 @@ +import * as path from 'path'; +import * as fs from '../../core/fs'; +import { GLOBAL_SETTINGS } from "../../config/constants"; +import { simpleExec, execShell, execWithLog } from '../../core/command-exec'; +import { BoardEnv, isPackageInstalledOnUnix, isPackageInstalledOnWindows } from './common-env'; + +const XTENSA_TOOLCHAIN_DIR = 'xtensa-esp-elf'; +const XTENSA_GCC_NAME = 'xtensa-esp32-elf-gcc'; +const XTENSA_AR_NAME = 'xtensa-esp32-elf-ar'; +const XTENSA_LD_NAME = 'xtensa-esp32-elf-ld'; + +export abstract class Esp32Env extends BoardEnv { + get espRootDir() { return path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'esp'); } + get idfDir() { return path.join(this.espRootDir, 'esp-idf'); } + get idfToolsPyFile() { return path.join(this.idfDir, 'tools/idf_tools.py'); } + get idfVersion() { return 'v5.4'; } + get idfGitRepo() { return 'https://github.com/espressif/esp-idf.git'; } + abstract get idfExportFile(): string; + abstract get xtensaGccFileName(): string; + abstract get xtensaArFileName(): string; + abstract get xtensaLdFileName(): string; + + abstract runEspIdfInstallScript(): Promise; + abstract getXtensaGccDir(pythonCommand: string): Promise; + abstract getMakeCommand(): Promise; + + async cloneEspIdf() { + await execWithLog( + 'git', + ['clone', '--depth', '1', '-b', this.idfVersion, '--recursive', this.idfGitRepo], + { cwd: this.espRootDir }, + ); + } + + removeBoardRoot() { + fs.removeDir(this.espRootDir); + } + + refreshBoardRoot() { + this.removeBoardRoot(); + fs.makeDir(this.espRootDir); + } + + protected parseKeyValueExport(stdout: string): Map { + const env = new Map(); + + for (const line of stdout.trim().split('\n')) { + if (!line || line.startsWith('ERROR:') || line.startsWith('WARNING:')) { + continue; + } + const eq = line.indexOf('='); + if (eq === -1) continue; + + const key = line.slice(0, eq); + const value = line.slice(eq + 1); + env.set(key, value); + } + + return env; + } + + protected splitPathValue(pathValue: string, separator: string): string[] { + return pathValue + .split(separator) + .map((p) => p.trim()) + .filter((p) => p.length > 0); + } + + protected resolveXtensaGccDirFromExport(stdout: string, pathLabel: string, pathSeparator: string): string { + const env = this.parseKeyValueExport(stdout); + + const pathValue = env.get(pathLabel); + if (!pathValue) { + throw new Error('PATH not found in idf_tools.py export output'); + } + + return this.findXtensaGccDirFromPathEntries(this.splitPathValue(pathValue, pathSeparator)); + } + + protected findXtensaGccDirFromPathEntries(entries: string[]): string { + for (const entry of entries) { + if (entry.includes(XTENSA_TOOLCHAIN_DIR) && fs.exists(path.join(entry, this.xtensaGccFileName))) { + return entry; + } + } + + throw new Error(`${XTENSA_TOOLCHAIN_DIR} not found in exported PATH`); + } + + async getPythonCommand(): Promise { + if (await this.isPythonVersionGreaterThan3()) { + return 'python'; + } else if (await this.isPackageInstalled('python3')) { + return 'python3'; + } else { + throw new Error("Cannot find Python3. Please install Python3 and try again."); + } + } +} + +export class Esp32DarwinEnv extends Esp32Env { + get idfInstallShFile() { return path.join(this.idfDir, 'install.sh'); } + get idfExportFile() { return path.join(this.idfDir, 'export.sh'); } + get xtensaGccFileName() { return XTENSA_GCC_NAME; } + get xtensaArFileName() { return XTENSA_AR_NAME; } + get xtensaLdFileName() { return XTENSA_LD_NAME; } + + async runEspIdfInstallScript() { + await execShell(`bash ${JSON.stringify(this.idfInstallShFile)}`); + } + + async getXtensaGccDir(pythonCommand: string) { + try { + const stdout = await simpleExec( + pythonCommand, + [this.idfToolsPyFile, 'export', '--format', 'key-value'], + ); + return super.resolveXtensaGccDirFromExport(stdout, 'PATH', ':'); + } catch (error) { + throw new Error(`Failed to find ${XTENSA_TOOLCHAIN_DIR}.`, { cause: error }); + } + } + + isPackageInstalled(name: string): Promise { + return isPackageInstalledOnUnix(name); + } + + async getMakeCommand(): Promise { + if (await this.isPackageInstalled('make')) { + return 'make'; + } else { + throw new Error('Cannot find make command. Please install make.'); + } + } +} + +export class Esp32WindowsEnv extends Esp32Env { + get idfExportBatFile() { return path.join(this.idfDir, 'export.bat'); } + get idfInstallBatFile() { return path.join(this.idfDir, 'install.bat'); } + get idfExportFile() { return this.idfExportBatFile; } + get xtensaGccFileName(): string { return `${XTENSA_GCC_NAME}.exe`; } + get xtensaArFileName(): string { return `${XTENSA_AR_NAME}.exe`; } + get xtensaLdFileName(): string { return `${XTENSA_LD_NAME}.exe`; } + + async runEspIdfInstallScript() { + await execShell(this.idfInstallBatFile); + } + + async getXtensaGccDir(pythonCommand: string) { + try { + const stdout = await simpleExec( + pythonCommand, + [this.idfToolsPyFile, 'export', '--format', 'key-value'], + ); + return super.resolveXtensaGccDirFromExport(stdout, 'PATH', ';'); + } catch (error) { + throw new Error(`Failed to find ${XTENSA_TOOLCHAIN_DIR}.`, { cause: error }); + } + } + + isPackageInstalled(name: string): Promise { + return isPackageInstalledOnWindows(name); + } + + async getMakeCommand(): Promise { + if (await this.isPackageInstalled('make')) { + return 'make'; + } else if (await this.isPackageInstalled('mingw32-make')) { + return 'mingw32-make'; + } else { + throw new Error("Cannot find make or mingw32-make command. Please install make or mingw32-make and try again."); + } + } +} diff --git a/cli/src/platforms/board-env/host-env.ts b/cli/src/platforms/board-env/host-env.ts new file mode 100644 index 00000000..bbedf514 --- /dev/null +++ b/cli/src/platforms/board-env/host-env.ts @@ -0,0 +1,148 @@ +import * as path from 'path'; +import * as fs from '../../core/fs'; +import { GLOBAL_SETTINGS } from '../../config/constants'; +import { simpleExec } from '../../core/command-exec'; +import { BoardEnv, isPackageInstalledOnUnix, isPackageInstalledOnWindows } from './common-env'; + +export abstract class HostEnv extends BoardEnv { + get hostRootDir() { return path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'host'); } + get buildDir() { return path.join(this.runtimeDir, 'ports/host/build'); } + get builtinModuleCFile() { return path.join(this.runtimeDir, 'ports/host/std-module.c'); } + get shellCFile() { return path.join(this.runtimeDir, 'ports/host/shell.c'); } + get runtimeCFile() { return path.join(this.runtimeDir, 'core/src/c-runtime.c'); } + get commCFile() { return path.join(this.runtimeDir, 'ports/host/comm.c'); } + + abstract get shellFile(): string; + abstract buildHostRuntime(): Promise; + + removeBoardRoot() { + fs.removeDir(this.hostRootDir); + } + + refreshBoardRoot() { + this.removeBoardRoot(); + fs.makeDir(this.hostRootDir); + } + + removeBuildDir() { + fs.removeDir(this.buildDir); + } + + abstract getGccCommand(): Promise; + abstract getArCommand(): Promise; + abstract getMakeCommand(): Promise; +} + +export class HostDarwinEnv extends HostEnv { + get runtimeSoFile() { return path.join(this.buildDir, 'c-runtime.so'); } + get shellFile() { return path.join(this.buildDir, 'shell'); } + + async buildHostRuntime() { + fs.makeDir(this.buildDir); + try { + await simpleExec('cc', [ + '-DLINUX64', '-O2', '-shared', '-fPIC', + '-o', this.runtimeSoFile, + this.runtimeCFile, this.builtinModuleCFile, this.commCFile, + ]); + await simpleExec('cc', [ + '-DLINUX64', '-O2', + '-o', this.shellFile, + this.shellCFile, this.runtimeSoFile, + '-lm', '-ldl', + ]); + } catch(error) { + throw new Error('Failed to compile host runtime.', { cause: error }); + } + + } + + isPackageInstalled(name: string): Promise { + return isPackageInstalledOnUnix(name); + } + + async getGccCommand(): Promise { + if (await this.isPackageInstalled('cc')) { + return 'cc'; + } else { + throw new Error('Cannot find cc command. Please install cc.'); + } + } + + async getArCommand(): Promise { + if (await this.isPackageInstalled('ar')) { + return 'ar'; + } else { + throw new Error('Cannot find ar command. Please install ar.'); + } + } + + async getMakeCommand(): Promise { + if (await this.isPackageInstalled('make')) { + return 'make'; + } else { + throw new Error('Cannot find make command. Please install make.'); + } + } +} + +export class HostWindowsEnv extends HostEnv { + get runtimeDllFile() { return path.join(this.buildDir, 'c-runtime.dll'); } + get shellFile() { return path.join(this.buildDir, 'shell.exe'); } + + async buildHostRuntime() { + fs.makeDir(this.buildDir); + try { + await simpleExec('gcc', [ + '-DLINUX64', '-O2', '-shared', + '-o', this.runtimeDllFile, + this.runtimeCFile, this.builtinModuleCFile, this.commCFile, + ]); + await simpleExec('gcc', [ + '-DLINUX64', '-O2', + '-o', this.shellFile, + this.shellCFile, this.runtimeDllFile, + '-lm', + ]); + } catch (error) { + throw new Error('Failed to compile host runtime.', { cause: error }); + } + } + + isPackageInstalled(name: string): Promise { + return isPackageInstalledOnWindows(name); + } + + async getGccCommand(): Promise { + if (await this.isPackageInstalled('gcc') && await this.isMingwGccAvailable()) { + return 'gcc'; + } else { + throw new Error('Cannot find gcc command. Please install MinGW-w64 and add it to PATH.'); + } + } + + private async isMingwGccAvailable(): Promise { + try { + const machine = (await simpleExec('gcc', ['-dumpmachine'])).trim(); + return machine.includes('mingw'); + } catch { + return false; + } + } + + async getArCommand(): Promise { + if (await this.isPackageInstalled('ar')) { + return 'ar'; + } else { + throw new Error('Cannot find ar command. Please install MinGW-w64 and add it to PATH.'); + } + } + + async getMakeCommand(): Promise { + if (await this.isPackageInstalled('mingw32-make')) { + return 'mingw32-make'; + } else { + throw new Error('Cannot find mingw32-make command. Please install MinGW-w64 and add it to PATH.'); + } + } +} diff --git a/cli/src/platforms/board-env/index.ts b/cli/src/platforms/board-env/index.ts new file mode 100644 index 00000000..c0fad756 --- /dev/null +++ b/cli/src/platforms/board-env/index.ts @@ -0,0 +1,42 @@ +import * as os from 'os'; +import { Esp32Env, Esp32DarwinEnv, Esp32WindowsEnv } from './esp32-env'; +import { CommonBoardEnv, BoardEnv } from './common-env'; +import { BoardName } from '../../config/board-utils'; +import { HostEnv, HostDarwinEnv, HostWindowsEnv } from './host-env'; + + +type BoardEnvMap = { + esp32: Esp32Env; + host: HostEnv; +}; + +export function createBoardEnv(board: B): BoardEnvMap[B]; +export function createBoardEnv(board: BoardName): BoardEnvMap[BoardName] { + const osType = os.platform(); + if (board === 'esp32') { + if (osType === 'darwin') + return new Esp32DarwinEnv(); + if (osType === 'win32') + return new Esp32WindowsEnv(); + throw new Error(`Unsupported OS type: ${osType}.`); + } + if (board === 'host') { + if (osType === 'darwin') + return new HostDarwinEnv(); + if (osType === 'win32') + return new HostWindowsEnv(); + throw new Error(`Unsupported OS type: ${osType}.`); + } + throw new Error(`Unsupported board name: ${board}`); +} + +export { + CommonBoardEnv, + BoardEnv, + Esp32Env, + Esp32DarwinEnv, + Esp32WindowsEnv, + HostEnv, + HostDarwinEnv, + HostWindowsEnv, +}; diff --git a/cli/src/platforms/compiler/esp32-compiler-adapter.ts b/cli/src/platforms/compiler/esp32-compiler-adapter.ts index 3d4d3613..5dce8205 100644 --- a/cli/src/platforms/compiler/esp32-compiler-adapter.ts +++ b/cli/src/platforms/compiler/esp32-compiler-adapter.ts @@ -3,7 +3,7 @@ import { ProjectConfigHandler, PROJECT_DEFAULT_PATHS } from "../../config/projec import { BoardName } from "../../config/board-utils"; import { CompilerSession, MemoryImage, MemoryLayout, - Esp32Toolchain, Esp32ToolchainConfig, ProjectForEsp32, PackageForEsp32 + Esp32Toolchain, Esp32ToolchainConfig, Project, PackageForEsp32 } from "@bscript/lang"; import { CompilerAdapter, CompileContext } from "./compiler-adapter"; import * as path from 'path'; @@ -19,7 +19,7 @@ const DUMMY_MEMORY_LAYOUT: MemoryLayout = { export class Esp32CompilerAdapter implements CompilerAdapter { readonly boardName: BoardName = 'esp32'; private boardConfig: Esp32BoardConfig; - private compiler?: CompilerSession; + private compiler?: CompilerSession; constructor( private globalConfigHandler: GlobalConfigHandler, @@ -41,7 +41,7 @@ export class Esp32CompilerAdapter implements CompilerAdapter { if (!memoryLayout) { throw new Error('Memory layout is required to build an ESP32 project.'); } - const project = ProjectForEsp32.load( + const project = Project.load( this.projectConfigHandler.getConfig().projectName, createEsp32PackageReader(this.boardName, this.projectConfigHandler), ); @@ -65,13 +65,14 @@ export class Esp32CompilerAdapter implements CompilerAdapter { } return { runtimeDir, - compilerToolchainDir: this.boardConfig.xtensaGccDir, + compilerToolchain: this.boardConfig.toolchain, espDir: this.boardConfig.rootDir, }; } } -export function createEsp32PackageReader( + +function createEsp32PackageReader( _boardName: BoardName, projectConfigHandler: ProjectConfigHandler, ): (name: string) => PackageForEsp32 { diff --git a/cli/src/platforms/compiler/host-compiler-adapter.ts b/cli/src/platforms/compiler/host-compiler-adapter.ts index 75a886b4..7ebc9361 100644 --- a/cli/src/platforms/compiler/host-compiler-adapter.ts +++ b/cli/src/platforms/compiler/host-compiler-adapter.ts @@ -1,42 +1,68 @@ -import { GlobalConfigHandler } from "../../config/global-config"; +import { GlobalConfigHandler, HostBoardConfig } from "../../config/global-config"; import { ProjectConfigHandler, PROJECT_DEFAULT_PATHS } from "../../config/project-config"; import { BoardName } from "../../config/board-utils"; import { - CompilerSession, SharedObject, - HostToolchain, ProjectForHost, Package + CompilerSession, SharedLibrary, + HostUnixToolchain, HostToolchainConfig, HostWindowsToolchain, Project, PackageForHostUnix, PackageForHostWindows } from "@bscript/lang"; import { CompilerAdapter, CompileContext } from "./compiler-adapter"; import * as path from 'path'; +import * as os from 'os'; +type HostPackageClass = typeof PackageForHostUnix | typeof PackageForHostWindows; + export class HostCompilerAdapter implements CompilerAdapter { readonly boardName: BoardName = 'host'; - private compiler?: CompilerSession; + private boardConfig: HostBoardConfig; + private compiler?: CompilerSession; constructor( private globalConfigHandler: GlobalConfigHandler, private projectConfigHandler: ProjectConfigHandler, ) { - if (!this.globalConfigHandler.isBoardSetup(this.boardName)) { + const boardConfig = this.globalConfigHandler.getBoardConfig('host'); + if (boardConfig === undefined) { throw new Error(`The environment for ${this.boardName} is not set up.`); } + this.boardConfig = boardConfig; } - async buildForCheck(): Promise { + async buildForCheck(): Promise { return this.buildProject(); } - async buildProject(_context?: CompileContext): Promise { - const project = ProjectForHost.load( - this.projectConfigHandler.getConfig().projectName, - createHostPackageReader(this.boardName, this.projectConfigHandler), - ); - const toolchain = new HostToolchain(this.getRuntimeDir()); - this.compiler = new CompilerSession(toolchain); - return this.compiler.buildProject(project); + async buildProject(_context?: CompileContext): Promise { + const runtimeDir = this.getRuntimeDir(); + const compilerConfig: HostToolchainConfig = { + runtimeDir, + compilerToolchain: this.boardConfig.toolchain, + }; + + if (os.platform() === 'darwin') { + const project = Project.load( + this.projectConfigHandler.getConfig().projectName, + createHostPackageReader(this.projectConfigHandler, PackageForHostUnix), + ); + const toolchain = new HostUnixToolchain(compilerConfig); + this.compiler = new CompilerSession(toolchain); + return this.compiler.buildProject(project); + } + + if (os.platform() === 'win32') { + const project = Project.load( + this.projectConfigHandler.getConfig().projectName, + createHostPackageReader(this.projectConfigHandler, PackageForHostWindows), + ); + const toolchain = new HostWindowsToolchain(compilerConfig); + this.compiler = new CompilerSession(toolchain); + return this.compiler.buildProject(project); + } + + throw new Error('Unsupported OS.'); } - async compileFragment(src: string): Promise { + async compileFragment(src: string): Promise { if (!this.compiler) { throw new Error("Cannot compile fragment before building the project."); } @@ -53,10 +79,10 @@ export class HostCompilerAdapter implements CompilerAdapter { } } -export function createHostPackageReader( - _boardName: BoardName, +function createHostPackageReader( projectConfigHandler: ProjectConfigHandler, -): (name: string) => Package { + PackageClass: T, +): (name: string) => InstanceType { return (name: string) => { const mainRoot = projectConfigHandler.root; const subPackageRoot = path.join(mainRoot, PROJECT_DEFAULT_PATHS.PACKAGES_DIR, name); @@ -66,7 +92,7 @@ export function createHostPackageReader( const configHandler = isMain ? projectConfigHandler.asBoard('host') : ProjectConfigHandler.load(root).asBoard('host'); - return new Package( + return new PackageClass( name, { rootDir: root, @@ -77,7 +103,7 @@ export function createHostPackageReader( packageDir: PROJECT_DEFAULT_PATHS.PACKAGES_DIR, }, Object.keys(configHandler.dependencies), - ); + ) as InstanceType; } catch (error) { throw new Error(`Failed to read ${name}.`, { cause: error }); } diff --git a/cli/src/platforms/runtime/host-board-runtime.ts b/cli/src/platforms/runtime/host-board-runtime.ts index 5382b7ba..683b4009 100644 --- a/cli/src/platforms/runtime/host-board-runtime.ts +++ b/cli/src/platforms/runtime/host-board-runtime.ts @@ -1,39 +1,13 @@ import * as path from 'path'; -import { exec } from '../../core/shell'; -import { SharedObject } from "@bscript/lang"; +import { SharedLibrary } from "@bscript/lang"; import { ProgramOutput } from "../../core/logger/program-output"; import { BoardRuntime } from "./board-runtime"; import { CompileContext } from "../compiler/compiler-adapter"; import { HostBoardConfig } from "../../config/global-config"; -import * as fs from '../../core/fs'; import { HostService, ProcessConnection } from '../../services/process'; -export async function buildHostRuntime(runtimeDir: string, buildDir?: string): Promise { - const resolvedBuildDir = buildDir ?? path.join(runtimeDir, 'ports/host/build'); - const builtinModuleC = path.join(runtimeDir, 'ports/host/std-module.c'); - const shellC = path.join(runtimeDir, 'ports/host/shell.c'); - const runtimeC = path.join(runtimeDir, 'core/src/c-runtime.c'); - const commC = path.join(runtimeDir, 'ports/host/comm.c'); - const runtimeSo = path.join(resolvedBuildDir, 'c-runtime.so'); - const shell = path.join(resolvedBuildDir, 'shell'); - - fs.makeDir(resolvedBuildDir); - - await exec( - `cc -DLINUX64 -O2 -shared -fPIC -o "${runtimeSo}" "${runtimeC}" "${builtinModuleC}" "${commC}"`, - { silent: true }, - ); - await exec( - `cc -DLINUX64 -O2 -o "${shell}" "${shellC}" "${runtimeSo}" -lm -ldl`, - { silent: true }, - ); - - return resolvedBuildDir; -} - - -export class HostBoardRuntime implements BoardRuntime { +export class HostBoardRuntime implements BoardRuntime { private programOutput: ProgramOutput; private shellProcess: ProcessConnection; private hostService: HostService; @@ -71,11 +45,11 @@ export class HostBoardRuntime implements BoardRuntime { return {}; } - async load(output: SharedObject): Promise { - return this.hostService.load(output.soFile); + async load(output: SharedLibrary): Promise { + return this.hostService.load(output.filePath); } - async execute(output: SharedObject): Promise { + async execute(output: SharedLibrary): Promise { let exectime = 0; for (const entry of output.entryNames) { exectime += await this.hostService.execute(entry.name); @@ -88,6 +62,6 @@ export class HostBoardRuntime implements BoardRuntime { } private getShellPath(): string { - return path.join(this.boardConfig.buildDir, 'shell'); + return path.join(this.boardConfig.shellFile); } } diff --git a/cli/src/services/ble.ts b/cli/src/services/ble.ts index 7bce82cd..6e1c2cd7 100644 --- a/cli/src/services/ble.ts +++ b/cli/src/services/ble.ts @@ -7,8 +7,8 @@ import { Protocol, ProtocolPacketBuilder, ProtocolParser } from './device-protoc const MTU = 495; -const SERVICE_UUID = '00ff'; -const CHARACTERISTIC_UUID = 'ff01'; +const SERVICE_UUID = 'b500'; +const CHARACTERISTIC_UUID = 'b501'; export type DeviceServiceEvents = { @@ -44,9 +44,8 @@ export class DeviceService extends Service { for (const entryPoint of bin.entryPoints) { builder.jump(entryPoint.isMain ? isMain : 0, entryPoint.address); } - await this.send('execute', builder.build()); let executionTime = 0; - return new Promise((resolve) => { + const p = new Promise((resolve) => { this.on('exectime', (id, time) => { executionTime += time; if (id === isMain) { @@ -55,16 +54,19 @@ export class DeviceService extends Service { } }); }); + await this.send('execute', builder.build()); + return p; } public async init(): Promise { const builder = new ProtocolPacketBuilder(MTU).reset(); - await this.send('init', builder.build()); - return new Promise((resolve) => { + const p = new Promise((resolve) => { this.once('memory', (layout) => { resolve(layout); }); }); + await this.send('init', builder.build()); + return p; } private handleReceivedData(data: Buffer) { @@ -168,8 +170,7 @@ export class BleConnection extends Connection { await this.waitForPoweredOn(); this.foundPeriferals = []; - await noble.startScanningAsync([SERVICE_UUID], false); - const peripheral = await new Promise((resolve) => { + const searchPeriferalPromise = new Promise((resolve) => { this.discoverHandler = (p: Peripheral) => { this.foundPeriferals.push(p); if (p.advertisement.localName === this.deviceName) { @@ -180,6 +181,8 @@ export class BleConnection extends Connection { }; noble.on('discover', this.discoverHandler); }); + await noble.startScanningAsync([SERVICE_UUID], false); + const peripheral = await searchPeriferalPromise; await noble.stopScanningAsync(); this.peripheral = peripheral; this.peripheral.on('disconnect', (event) => { @@ -193,7 +196,6 @@ export class BleConnection extends Connection { this.emit('connected'); }); await peripheral.connectAsync(); - const { characteristics } = await peripheral.discoverSomeServicesAndCharacteristicsAsync( [SERVICE_UUID], [CHARACTERISTIC_UUID] @@ -208,6 +210,7 @@ export class BleConnection extends Connection { } }) await this.characteristic.subscribeAsync(); + return; } private async waitForPoweredOn(): Promise { diff --git a/cli/src/services/host-protocol.ts b/cli/src/services/host-protocol.ts index d76da443..37553bf3 100644 --- a/cli/src/services/host-protocol.ts +++ b/cli/src/services/host-protocol.ts @@ -10,7 +10,15 @@ export enum HostProtocol { Max } +export const HOST_MAX_PAYLOAD_SIZE = 256; + export function hostProtocolBuilder(protocol: HostProtocol, payload: string) { + if (payload.length > HOST_MAX_PAYLOAD_SIZE) { + throw new Error( + `Host protocol payload exceeds ${HOST_MAX_PAYLOAD_SIZE} bytes ` + + `(got ${payload.length}): ${payload}`, + ); + } const protocolStr = String(protocol).padStart(2, '0'); const payloadLen = String(payload.length).padStart(4, '0'); return `${protocolStr} ${payloadLen} ${payload}\n`; diff --git a/cli/src/services/process.ts b/cli/src/services/process.ts index c6de8458..083fc544 100644 --- a/cli/src/services/process.ts +++ b/cli/src/services/process.ts @@ -138,7 +138,7 @@ export class ProcessConnection extends Connection { } public async disconnect(): Promise { - if (!this.checkProcessRunning(this.shellProcess)) { + if (!this.shellProcess) { return; } @@ -147,27 +147,44 @@ export class ProcessConnection extends Connection { this.disconnecting = true; await new Promise((resolve) => { - const timeout = setTimeout(() => { - proc.kill('SIGKILL'); + let settled = false; + let timeout: NodeJS.Timeout; + const cleanup = () => { + if (settled) { + return; + } + settled = true; + clearTimeout(timeout); + proc.stdout.removeAllListeners(); + proc.stderr.removeAllListeners(); + proc.stdin.removeAllListeners(); proc.stdout.destroy(); proc.stderr.destroy(); proc.stdin.destroy(); + proc.removeAllListeners(); resolve(); + }; + + if (proc.exitCode !== null || proc.signalCode !== null) { + cleanup(); + return; + } + + timeout = setTimeout(() => { + if (proc.exitCode === null && proc.signalCode === null) { + proc.kill('SIGKILL'); + } + cleanup(); }, 3_000); - timeout.unref(); - proc.once('exit', () => { - clearTimeout(timeout); - proc.stdout.destroy(); - proc.stderr.destroy(); - proc.stdin.destroy(); - resolve(); - }); + proc.once('close', cleanup); proc.stdout.removeAllListeners('data'); proc.stderr.removeAllListeners('data'); - proc.stdin.end(); - proc.kill(); + if (proc.stdin.writable) { + proc.stdin.end(); + } + proc.kill('SIGTERM'); }); this.emit('disconnected', 0); diff --git a/cli/tests/commands/board/flash-runtime.test.ts b/cli/tests/commands/board/flash-runtime.test.ts index 95ed0c2f..ebc693cb 100644 --- a/cli/tests/commands/board/flash-runtime.test.ts +++ b/cli/tests/commands/board/flash-runtime.test.ts @@ -5,7 +5,7 @@ import { mockedInquirer, mockedLogger, mockProcessExit, - mockedExec, + mockedExecShell, } from '../mock-helpers'; @@ -56,7 +56,7 @@ describe('board flash-runtime command', () => { // --- Assert --- expect(mockedInquirer.prompt).not.toHaveBeenCalled(); - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('build flash -p'), {cwd: expect.stringContaining('esp32')}); + expect(mockedExecShell).toHaveBeenCalledWith(expect.stringContaining('build flash -p'), { cwd: expect.stringContaining('esp32') }); }); it('should show an error and return if no serial ports are found', async () => { @@ -70,7 +70,7 @@ describe('board flash-runtime command', () => { // --- Assert --- expect(mockedLogger.error).toHaveBeenCalled(); expect(mockedInquirer.prompt).not.toHaveBeenCalled(); - expect(mockedExec).not.toHaveBeenCalled(); + expect(mockedExecShell).not.toHaveBeenCalled(); }); it('should exit with an error for an unknown board name', async () => { @@ -105,7 +105,7 @@ describe('board flash-runtime command', () => { await handleFlashRuntimeCommand('esp32', {}); // --- Assert --- - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('build flash'), {cwd: expect.stringContaining('esp32')}); + expect(mockedExecShell).toHaveBeenCalledWith(expect.stringContaining('build flash'), { cwd: expect.stringContaining('esp32') }); expect(mockedLogger.error).not.toHaveBeenCalled(); }); @@ -119,7 +119,7 @@ describe('board flash-runtime command', () => { await handleFlashRuntimeCommand('esp32', { deviceName: 'my-device' }); // --- Assert --- - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('my-device'), {cwd: expect.stringContaining('esp32')}); + expect(mockedExecShell).toHaveBeenCalledWith(expect.stringContaining('my-device'), { cwd: expect.stringContaining('esp32') }); expect(mockedLogger.error).not.toHaveBeenCalled(); }); @@ -133,7 +133,7 @@ describe('board flash-runtime command', () => { // --- Assert --- expect(mockedLogger.warn).toHaveBeenCalledWith(`The environment for esp32 is not set up. Run 'bscript board setup esp32' and try again.`); expect(mockedInquirer.prompt).not.toHaveBeenCalled(); - expect(mockedExec).not.toHaveBeenCalled(); + expect(mockedExecShell).not.toHaveBeenCalled(); }); }); @@ -152,4 +152,4 @@ describe('board flash-runtime command', () => { exitSpy.mockRestore(); }); }); -}); \ No newline at end of file +}); diff --git a/cli/tests/commands/board/setup.test.ts b/cli/tests/commands/board/setup.test.ts index a0f02a61..ef6637d5 100644 --- a/cli/tests/commands/board/setup.test.ts +++ b/cli/tests/commands/board/setup.test.ts @@ -2,20 +2,21 @@ import { handleSetupCommand } from '../../../src/commands/board/setup'; import os from 'os'; import { mockedDownloadAndUnzip, - mockedExec, + mockedSimpleExec, + mockedExecWithLog, + mockedExecShell, mockedInquirer, mockedLogger, mockProcessExit, } from '../mock-helpers'; -import { deleteGlobalEnv, getGlobalConfig, setupDefaultGlobalEnv, setupEmpyGlobalEnv, setupGlobalEnvWithEsp32, setupGlobalEnvWithHost, spyGlobalSettings } from '../global-env-helper'; +import { deleteGlobalEnv, getGlobalConfig, setupDefaultGlobalEnv, setupEmpyGlobalEnv, setupGlobalEnvWithEsp32, setupGlobalEnvWithHost, spyGlobalSettings, getTestRuntimeDir, isEsp32IdfToolsExportPythonCommand, mockXtensaGccFromIdfToolsExport } from '../global-env-helper'; +import { HostDarwinEnv } from '../../../src/platforms/board-env/host-env'; +import * as path from 'path'; -jest.mock('../../../src/platforms/runtime/host-board-runtime', () => ({ - buildHostRuntime: jest.fn().mockResolvedValue('/mock/host/build'), - getHostBuildDir: jest.fn(), -})); -import { buildHostRuntime } from '../../../src/platforms/runtime/host-board-runtime'; -const mockedBuildHostRuntime = buildHostRuntime as jest.Mock; +const mockedBuildHostRuntime = jest + .spyOn(HostDarwinEnv.prototype, 'buildHostRuntime') + .mockResolvedValue(); jest.mock('os', () => ({ ...jest.requireActual('os'), @@ -25,10 +26,55 @@ jest.mock('os', () => ({ export const mockedOs = os as jest.Mocked; mockedOs.platform.mockReturnValue('darwin'); +function mockEsp32ShellCommands(options: { + whichFound?: string[]; + pythonMajor?: string; + gitCloneFails?: boolean; +}) { + mockedSimpleExec.mockImplementation(async (cmd, args) => { + if (cmd === 'which') { + if (options.whichFound?.includes(args[0])) { + return ''; + } + throw new Error('not found'); + } + if (isEsp32IdfToolsExportPythonCommand(cmd) && args.some((arg: string) => arg.includes('export'))) { + return mockXtensaGccFromIdfToolsExport(); + } + if (cmd === 'python' && args[1]?.includes('import sys')) { + return options.pythonMajor ?? '3'; + } + return ''; + }); + mockedExecWithLog.mockImplementation(async (cmd, args) => { + if (cmd === 'git' && options.gitCloneFails) { + throw new Error('git command failed'); + } + if (cmd === 'git' || cmd === 'brew') { + return ''; + } + return ''; + }); + mockedExecShell.mockImplementation(async () => {}); +} + +function mockHostShellCommands(options: { ccMissing?: boolean }) { + mockedSimpleExec.mockImplementation(async (cmd, args) => { + if (cmd === 'which') { + if (options.ccMissing && args[0] === 'cc') { + throw new Error('not found'); + } + return ''; + } + return ''; + }); +} + describe('board setup command', () => { beforeAll(() => { spyGlobalSettings('setup'); + jest.spyOn(HostDarwinEnv.prototype, 'buildHostRuntime').mockResolvedValue(); }) afterEach(() => { @@ -62,7 +108,9 @@ describe('board setup command', () => { // --- Assert --- expect(mockedLogger.warn).toHaveBeenCalledWith('Setup cancelled by user.'); // No further actions taken - expect(mockedExec).not.toHaveBeenCalled(); + expect(mockedSimpleExec).not.toHaveBeenCalled(); + expect(mockedExecWithLog).not.toHaveBeenCalled(); + expect(mockedExecShell).not.toHaveBeenCalled(); }); it('should exit with an error for an unknown board name', async () => { @@ -83,12 +131,7 @@ describe('board setup command', () => { // --- Arrange --- const exitSpy = mockProcessExit(); mockedInquirer.prompt.mockResolvedValue({ proceed: true }); - mockedExec.mockImplementation(async (command) => { - if (command.startsWith('git clone')) { - throw new Error('git command failed');; - } - return ''; - }); + mockEsp32ShellCommands({ gitCloneFails: true }); // --- Act --- await handleSetupCommand('esp32'); @@ -104,21 +147,10 @@ describe('board setup command', () => { it('should perform a full setup if not already set up', async () => { // --- Arrange --- mockedInquirer.prompt.mockResolvedValue({ proceed: true }); - mockedExec.mockImplementation(async (command: string) => { - if (command.startsWith('which')) { - if (command.includes('brew') || command.includes('git')) { - return ''; - } - if (command.includes('xtensa-esp32-elf-gcc')) { - return '/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc'; - } - throw new Error('not found'); - } - if (command.includes('python --version')) { - return 'Python 2.7.18'; - } - return ''; + mockEsp32ShellCommands({ + whichFound: ['brew', 'git', 'make'], }); + setupEmpyGlobalEnv(); // --- Act --- await handleSetupCommand('esp32'); @@ -131,25 +163,33 @@ describe('board setup command', () => { expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); // 3. Install required packages via Homebrew - expect(mockedExec).toHaveBeenCalledWith('brew install cmake ninja dfu-util ccache'); - - // 4. Instaall Python 3 if not present - expect(mockedExec).toHaveBeenCalledWith('brew install python3'); - - // 5. Clone ESP-IDF and run install script - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('git clone'), expect.any(Object)); - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('install.sh')); - - // 6. Update and save config + expect(mockedExecWithLog).toHaveBeenCalledWith( + 'brew', + ['install', 'cmake', 'ninja', 'dfu-util', 'ccache'], + ); + + // 4. Clone ESP-IDF and run install script + expect(mockedExecWithLog).toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); + expect(mockedExecShell).toHaveBeenCalledWith(expect.stringContaining('install.sh')); + + // 5. Update and save config expect(Object.keys(getGlobalConfig().boards)).toContain('esp32'); - // 7. No errors logged + // 6. No errors logged expect(mockedLogger.error).not.toHaveBeenCalled(); }); it('should skip downloading runtime if it exist', async () => { // --- Arrange --- + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); setupDefaultGlobalEnv(); + mockEsp32ShellCommands({ + whichFound: ['brew', 'git', 'make'], + }); // --- Act --- await handleSetupCommand('esp32'); @@ -158,61 +198,51 @@ describe('board setup command', () => { // Confirm downloads are skipped expect(mockedDownloadAndUnzip).not.toHaveBeenCalled(); // Confirm device setup proceeds - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('git clone'), expect.any(Object)); + expect(mockedExecWithLog).toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); }); it('shold skip install required packages if all packages are installed', async () => { // --- Arrange --- setupEmpyGlobalEnv(); - mockedExec.mockImplementation(async (command: string) => { - if (command.startsWith('which')) { - if (command.includes('brew') || command.includes('git')) { - return ''; - } - if (command.includes('cmake') || command.includes('ninja') || command.includes('dfu-util') || command.includes('ccache')) { - return ''; - } - throw new Error('not found'); - } - if (command.includes('python --version')) { - return 'Python 2.7.18'; - } - return ''; + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); + mockEsp32ShellCommands({ + whichFound: ['brew', 'git', 'cmake', 'ninja', 'dfu-util', 'ccache', 'make'], }); // --- Act --- await handleSetupCommand('esp32'); // --- Assert --- - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('brew install cmake')); + expect(mockedExecWithLog).not.toHaveBeenCalledWith('brew', ['install', 'cmake']); }); - it('shold skip install python3 if python3 is already installed', async () => { + it('shold stop if python3 is not installed', async () => { // --- Arrange --- setupEmpyGlobalEnv(); - mockedExec.mockImplementation(async (command: string) => { - if (command.startsWith('which')) { - if (command.includes('brew') || command.includes('git')) { - return ''; - } - throw new Error('not found'); - } - if (command.includes('python --version')) { - return 'Python 3.7.18'; - } - return ''; + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); + const exitSpy = mockProcessExit(); + mockEsp32ShellCommands({ + whichFound: ['brew', 'git', 'make'], + pythonMajor: '2', }); // --- Act --- await handleSetupCommand('esp32'); // --- Assert --- - expect(mockedExec).not.toHaveBeenCalledWith('brew install python3'); + expect(mockedLogger.showError).toHaveBeenCalledWith(new Error('Cannot find Python3. Please install Python3 and try again.')); + expect(process.exit).toHaveBeenCalledWith(1); + exitSpy.mockRestore(); }) it('should warn and exit if setup is already completed', async () => { // --- Arrange --- setupGlobalEnvWithEsp32(); + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); // --- Act --- await handleSetupCommand('esp32'); @@ -221,12 +251,15 @@ describe('board setup command', () => { expect(mockedLogger.warn).toHaveBeenCalledWith('The setup for esp32 has already been completed.'); // No further actions taken expect(mockedInquirer.prompt).not.toHaveBeenCalled(); - expect(mockedExec).not.toHaveBeenCalled(); + expect(mockedSimpleExec).not.toHaveBeenCalled(); + expect(mockedExecWithLog).not.toHaveBeenCalled(); + expect(mockedExecShell).not.toHaveBeenCalled(); }); it('should exit with an error for an unsupported OS', async () => { // --- Arrange --- const exitSpy = mockProcessExit(); + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); mockedOs.platform.mockReturnValue('linux'); setupGlobalEnvWithEsp32() @@ -235,7 +268,7 @@ describe('board setup command', () => { // --- Assert --- expect(mockedLogger.error).toHaveBeenCalledWith('Failed to set up esp32'); - expect(mockedLogger.showError).toHaveBeenCalledWith(new Error('Unsupported OS.')); + expect(mockedLogger.showError).toHaveBeenCalledWith(new Error('Unsupported OS type: linux.')); expect(process.exit).toHaveBeenCalledWith(1); exitSpy.mockRestore(); }); @@ -249,19 +282,14 @@ describe('board setup command', () => { it('should perform a full setup if not already set up', async () => { setupEmpyGlobalEnv(); mockedInquirer.prompt.mockResolvedValue({ proceed: true }); - mockedExec.mockImplementation(async (command: string) => { - if (command.startsWith('which')) { - return ''; - } - return ''; - }); + mockHostShellCommands({}); await handleSetupCommand('host'); expect(mockedInquirer.prompt).toHaveBeenCalledTimes(1); expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); expect(mockedBuildHostRuntime).toHaveBeenCalledTimes(1); - expect(getGlobalConfig().boards.host).toEqual({ buildDir: '/mock/host/build' }); + expect(Object.keys(getGlobalConfig().boards)).toContain('host'); expect(mockedLogger.info).toHaveBeenCalledWith(expect.stringContaining('bscript project create')); expect(mockedLogger.info).not.toHaveBeenCalledWith(expect.stringContaining('flash-runtime')); expect(mockedLogger.error).not.toHaveBeenCalled(); @@ -271,15 +299,7 @@ describe('board setup command', () => { setupEmpyGlobalEnv(); const exitSpy = mockProcessExit(); mockedInquirer.prompt.mockResolvedValue({ proceed: true }); - mockedExec.mockImplementation(async (command: string) => { - if (command.includes('which cc')) { - throw new Error('not found'); - } - if (command.startsWith('which')) { - return ''; - } - return ''; - }); + mockHostShellCommands({ ccMissing: true }); await handleSetupCommand('host'); @@ -290,6 +310,7 @@ describe('board setup command', () => { it('should warn and exit if setup is already completed', async () => { setupGlobalEnvWithHost(); + mockedInquirer.prompt.mockResolvedValue({ proceed: true }); await handleSetupCommand('host'); diff --git a/cli/tests/commands/board/update.test.ts b/cli/tests/commands/board/update.test.ts index 62850978..2ba51408 100644 --- a/cli/tests/commands/board/update.test.ts +++ b/cli/tests/commands/board/update.test.ts @@ -1,50 +1,95 @@ import { handleUpdateCommand } from '../../../src/commands/board/update'; -import { deleteGlobalEnv, DUMMY_ESP_IDF_VERSION, getGlobalConfig, setupDefaultGlobalEnv, setupGlobalEnvWithEsp32, DUMMY_VM_VERSION, spyGlobalSettings, DUMMY_OLD_VM_VERSION, DUMMY_OLD_ESP_IDF_VERSION } from '../global-env-helper'; -import { mockedDownloadAndUnzip, mockedExec, mockProcessExit } from '../mock-helpers'; +import { + deleteGlobalEnv, + DUMMY_ESP_IDF_VERSION, + getGlobalConfig, + getExpectedHostToolchain, + getTestEspRootDir, + getTestHostShellFile, + getTestRuntimeDir, + setupDefaultGlobalEnv, + setupGlobalEnvWithEsp32, + setupGlobalEnvWithHost, + DUMMY_VM_VERSION, + spyGlobalSettings, + DUMMY_OLD_VM_VERSION, + DUMMY_OLD_ESP_IDF_VERSION, + isEsp32IdfToolsExportPythonCommand, + mockXtensaGccFromIdfToolsExport, +} from '../global-env-helper'; +import { mockedDownloadAndUnzip, mockedSimpleExec, mockedExecWithLog, mockedExecShell, mockProcessExit } from '../mock-helpers'; +import { HostDarwinEnv, HostWindowsEnv } from '../../../src/platforms/board-env/host-env'; import * as fs from '../../../src/core/fs'; -import { GLOBAL_SETTINGS } from '../../../src/config/constants'; +import * as os from 'os'; +const HostEnvClass = os.platform() === 'win32' ? HostWindowsEnv : HostDarwinEnv; +const mockedBuildHostRuntime = jest.spyOn(HostEnvClass.prototype, 'buildHostRuntime'); + +function mockUpdateShellCommands(options: { gitCloneFails?: boolean }) { + mockedSimpleExec.mockImplementation(async (cmd, args) => { + if (isEsp32IdfToolsExportPythonCommand(cmd) && args.some((arg: string) => arg.includes('export'))) { + return mockXtensaGccFromIdfToolsExport(); + } + return ''; + }); + mockedExecWithLog.mockImplementation(async (cmd, args) => { + if (cmd === 'git' && options.gitCloneFails) { + throw new Error('Failed to cloning ESP-IDF'); + } + if (cmd === 'git') { + return ''; + } + return ''; + }); + mockedExecShell.mockImplementation(async () => {}); +} describe('board update command', () => { beforeAll(() => { spyGlobalSettings('update'); + mockedBuildHostRuntime.mockResolvedValue(); }); afterEach(() => { jest.clearAllMocks(); + mockedDownloadAndUnzip.mockResolvedValue(undefined); + mockedBuildHostRuntime.mockResolvedValue(); deleteGlobalEnv(); }); it('should update all environments.', async () => { // --- Arrange --- setupGlobalEnvWithEsp32(true, true); - mockedExec.mockImplementation((command: string) => { - if (command.endsWith('which xtensa-esp32-elf-gcc')) { - return 'xtensa-esp-elf/bin'; - } - return ''; - }); + mockUpdateShellCommands({}); // --- Act --- await handleUpdateCommand(); // --- Assert --- expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('git clone'),expect.any(Object)); - expect(mockedExec).toHaveBeenCalledWith(expect.stringContaining('install')); + expect(mockedExecWithLog).toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); + expect(mockedExecShell).toHaveBeenCalledWith(expect.stringContaining('install')); expect(getGlobalConfig().version).toMatch(DUMMY_VM_VERSION); expect(getGlobalConfig().boards.esp32.idfVersion).toMatch(DUMMY_ESP_IDF_VERSION); }); it('should skip updating runtime if version mismatch does not exist.', async () => { // --- Arrange --- + const exitSpy = mockProcessExit(); setupDefaultGlobalEnv(); // --- Act --- await handleUpdateCommand(); // --- Assert --- - expect(mockedDownloadAndUnzip).not.toHaveBeenCalledTimes(1); + expect(process.exit).toHaveBeenCalledWith(0); + + // --- Clean up --- + exitSpy.mockRestore(); }); it('should skip updating ESP-IDF if version mismatch of ESP-IDF does not exist.', async () => { @@ -56,7 +101,11 @@ describe('board update command', () => { // --- Assert --- expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('git clone'),expect.any(Object)); + expect(mockedExecWithLog).not.toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); expect(getGlobalConfig().version).toMatch(DUMMY_VM_VERSION); }); @@ -64,12 +113,7 @@ describe('board update command', () => { // --- Arrange --- const exitSpy = mockProcessExit(); setupGlobalEnvWithEsp32(true, true); - mockedExec.mockImplementation((command: string) => { - if (command.endsWith('which xtensa-esp32-elf-gcc')) { - return 'xtensa-esp-elf/bin'; - } - return ''; - }); + mockUpdateShellCommands({}); mockedDownloadAndUnzip.mockImplementation(() => { throw new Error('Failed to download.'); }); @@ -79,9 +123,13 @@ describe('board update command', () => { // --- Assert --- expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('git clone'),expect.any(Object)); - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('install')); - expect(fs.exists(GLOBAL_SETTINGS.RUNTIME_DIR)).toBe(true); + expect(mockedExecWithLog).not.toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); + expect(mockedExecShell).not.toHaveBeenCalledWith(expect.stringContaining('install')); + expect(fs.exists(getTestRuntimeDir())).toBe(true); expect(getGlobalConfig().version).toMatch(DUMMY_OLD_VM_VERSION); // --- Clean up --- @@ -92,26 +140,75 @@ describe('board update command', () => { // --- Arrange --- const exitSpy = mockProcessExit(); setupGlobalEnvWithEsp32(true, true); - mockedExec.mockImplementation((command: string) => { - if (command.startsWith('git clone')) { - throw new Error('Failed to cloning ESP-IDF'); - } - return ''; - }); + mockUpdateShellCommands({ gitCloneFails: true }); // --- Act --- await handleUpdateCommand(); // --- Assert --- expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('git clone'),expect.any(Object)); - expect(mockedExec).not.toHaveBeenCalledWith(expect.stringContaining('install')); - expect(fs.exists(GLOBAL_SETTINGS.RUNTIME_DIR)).toBe(true); - expect(fs.exists(GLOBAL_SETTINGS.ESP_ROOT_DIR)).toBe(true); + expect(mockedExecWithLog).toHaveBeenCalledWith( + 'git', + expect.arrayContaining(['clone']), + expect.any(Object), + ); + expect(mockedExecShell).not.toHaveBeenCalledWith(expect.stringContaining('install')); + expect(fs.exists(getTestRuntimeDir())).toBe(true); + expect(fs.exists(getTestEspRootDir())).toBe(true); expect(getGlobalConfig().version).toMatch(DUMMY_OLD_VM_VERSION); expect(getGlobalConfig().boards.esp32.idfVersion).toMatch(DUMMY_OLD_ESP_IDF_VERSION); // --- Clean up --- exitSpy.mockRestore(); }); -}); \ No newline at end of file + + describe('for host board', () => { + it('should update host environment.', async () => { + // --- Arrange --- + setupGlobalEnvWithHost(true); + + // --- Act --- + await handleUpdateCommand(); + + // --- Assert --- + expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); + expect(mockedBuildHostRuntime).toHaveBeenCalledTimes(1); + expect(getGlobalConfig().version).toMatch(DUMMY_VM_VERSION); + expect(getGlobalConfig().boards.host.shellFile).toBe(getTestHostShellFile()); + expect(getGlobalConfig().boards.host.toolchain).toEqual(getExpectedHostToolchain()); + }); + + it('should skip updating host if host is not setup.', async () => { + // --- Arrange --- + setupDefaultGlobalEnv(true); + + // --- Act --- + await handleUpdateCommand(); + + // --- Assert --- + expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); + expect(mockedBuildHostRuntime).not.toHaveBeenCalled(); + }); + + it('should restore old runtime if error occures during updating host', async () => { + // --- Arrange --- + const exitSpy = mockProcessExit(); + setupGlobalEnvWithHost(true); + mockedBuildHostRuntime.mockRejectedValueOnce( + new Error('Failed to compile host runtime.'), + ); + + // --- Act --- + await handleUpdateCommand(); + + // --- Assert --- + expect(mockedDownloadAndUnzip).toHaveBeenCalledTimes(1); + expect(mockedBuildHostRuntime).toHaveBeenCalledTimes(1); + expect(fs.exists(getTestRuntimeDir())).toBe(true); + expect(getGlobalConfig().version).toMatch(DUMMY_OLD_VM_VERSION); + + // --- Clean up --- + exitSpy.mockRestore(); + }); + }); +}); diff --git a/cli/tests/commands/global-env-helper.ts b/cli/tests/commands/global-env-helper.ts index 4e4ef05c..2ce6eac5 100644 --- a/cli/tests/commands/global-env-helper.ts +++ b/cli/tests/commands/global-env-helper.ts @@ -1,11 +1,9 @@ import * as path from "path"; +import * as os from "os"; import * as fs from '../../src/core/fs'; import { GLOBAL_SETTINGS } from "../../src/config/constants"; - - -// export const DUMMY_BLUESCRIPT_DIR = path.join(TEMP_DIR, '.bluescript'); -// export const DUMMY_BLUESCRIPT_CONFIG_FILE = path.join(DUMMY_BLUESCRIPT_DIR, 'config.json'); -// const DUMMY_RUNTIME_DIR = path.join(DUMMY_BLUESCRIPT_DIR, 'microcontroller'); +import { CommonBoardEnv, Esp32DarwinEnv, Esp32WindowsEnv } from "../../src/platforms/board-env"; +import { HostDarwinEnv, HostWindowsEnv } from "../../src/platforms/board-env/host-env"; const TEMP_DIR = path.join(__dirname, '../../temp-files'); const DUMMY_BLUESCRIPT_DIR = (suffix: string) => path.join(TEMP_DIR, `.bluescript-${suffix}`); @@ -15,11 +13,29 @@ export const DUMMY_OLD_VM_VERSION = '0.0.0'; export const DUMMY_ESP_IDF_VERSION = 'v5.4'; export const DUMMY_OLD_ESP_IDF_VERSION = 'v5.3'; +function commonBoardEnv() { return new CommonBoardEnv(); } + +function esp32BoardEnv() { + return os.platform() === 'win32' ? new Esp32WindowsEnv() : new Esp32DarwinEnv(); +} + +function hostBoardEnv() { + return os.platform() === 'win32' ? new HostWindowsEnv() : new HostDarwinEnv(); +} +export function getTestRuntimeDir() { return commonBoardEnv().runtimeDir; } +export function getTestEspRootDir() { return esp32BoardEnv().espRootDir; } +export function getTestEspIdfExportFile() { return esp32BoardEnv().idfExportFile; } +export function getTestHostShellFile() { return hostBoardEnv().shellFile; } + +export function getExpectedHostToolchain() { + return os.platform() === 'win32' + ? { gcc: 'gcc', ar: 'ar', make: 'mingw32-make' } + : { gcc: 'cc', ar: 'ar', make: 'make' }; +} export function spyGlobalSettings(globalDirSuffix: string) { jest.spyOn(GLOBAL_SETTINGS, 'BLUESCRIPT_DIR', 'get').mockReturnValue(DUMMY_BLUESCRIPT_DIR(globalDirSuffix)); jest.spyOn(GLOBAL_SETTINGS, 'VM_VERSION', 'get').mockReturnValue(DUMMY_VM_VERSION); - jest.spyOn(GLOBAL_SETTINGS, 'ESP_IDF_VERSION', 'get').mockReturnValue(DUMMY_ESP_IDF_VERSION); } export function setupEmpyGlobalEnv() { @@ -47,34 +63,44 @@ export function deleteGlobalEnv() { export function setupDefaultGlobalEnv(isOldVersion = false) { setupGlobalEnv({ version: isOldVersion ? DUMMY_OLD_VM_VERSION : DUMMY_VM_VERSION, - runtimeDir: GLOBAL_SETTINGS.RUNTIME_DIR, + runtimeDir: getTestRuntimeDir(), boards: {} }); - fs.makeDir(GLOBAL_SETTINGS.RUNTIME_DIR); + fs.makeDir(getTestRuntimeDir()); } export function setupGlobalEnvWithHost(isOldVersion = false, buildDir?: string) { - const resolvedBuildDir = buildDir ?? path.join(GLOBAL_SETTINGS.RUNTIME_DIR, 'ports/host/build'); + const hostEnv = hostBoardEnv(); + const resolvedBuildDir = buildDir ?? path.join(getTestRuntimeDir(), 'ports/host/build'); setupGlobalEnv({ version: isOldVersion ? DUMMY_OLD_VM_VERSION : DUMMY_VM_VERSION, - runtimeDir: GLOBAL_SETTINGS.RUNTIME_DIR, + runtimeDir: getTestRuntimeDir(), boards: { host: { - buildDir: resolvedBuildDir, - }, + rootDir: hostEnv.hostRootDir, + shellFile: hostEnv.shellFile, + toolchain: getExpectedHostToolchain(), + } }, }); fs.makeDir(resolvedBuildDir); - fs.makeDir(GLOBAL_SETTINGS.RUNTIME_DIR); + fs.makeDir(getTestRuntimeDir()); } export function setupGlobalEnvWithHostIntegration(runtimeDir: string, buildDir: string) { + const osType = os.platform(); + const shellFileName = osType === 'win32' ? 'shell.exe' : 'shell'; + const toolchain = osType === 'win32' + ? { gcc: 'gcc', ar: 'ar', make: 'mingw32-make' } + : { gcc: 'cc', ar: 'ar', make: 'make' }; setupGlobalEnv({ version: DUMMY_VM_VERSION, runtimeDir, boards: { host: { - buildDir, + rootDir: path.join(GLOBAL_SETTINGS.BLUESCRIPT_DIR, 'host'), + shellFile: path.join(buildDir, shellFileName), + toolchain, }, }, }); @@ -83,18 +109,44 @@ export function setupGlobalEnvWithHostIntegration(runtimeDir: string, buildDir: export function setupGlobalEnvWithEsp32(isOldVersion = false, isEspIdfOldVersion = false) { setupGlobalEnv({ version: isOldVersion ? DUMMY_OLD_VM_VERSION : DUMMY_VM_VERSION, - runtimeDir: GLOBAL_SETTINGS.RUNTIME_DIR, + runtimeDir: getTestRuntimeDir(), boards: { esp32: { idfVersion: isEspIdfOldVersion ? DUMMY_OLD_ESP_IDF_VERSION : DUMMY_ESP_IDF_VERSION, - rootDir: GLOBAL_SETTINGS.ESP_ROOT_DIR, - exportFile: GLOBAL_SETTINGS.ESP_IDF_EXPORT_FILE, - xtensaGccDir: "/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin" + rootDir: getTestEspRootDir(), + exportFile: getTestEspIdfExportFile(), + toolchain: { + gcc: '/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-gcc', + ar: '/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-ar', + ld: '/.espressif/tools/xtensa-esp-elf/esp-14.2.0_20241119/xtensa-esp-elf/bin/xtensa-esp32-elf-ld', + make: 'make', + python: 'python' + }, } } }); - fs.makeDir(GLOBAL_SETTINGS.ESP_ROOT_DIR); - fs.makeDir(GLOBAL_SETTINGS.RUNTIME_DIR); + fs.makeDir(getTestEspRootDir()); + fs.makeDir(getTestRuntimeDir()); +} + +export function getEsp32IdfToolsExportPythonCommand(): string { + return os.platform() === 'win32' ? 'python' : 'python3'; +} + +export function isEsp32IdfToolsExportPythonCommand(cmd: string): boolean { + return cmd === 'python' || cmd === 'python3'; +} + +export function mockXtensaGccFromIdfToolsExport(): string { + const esp32Env = esp32BoardEnv(); + const pathSep = esp32Env instanceof Esp32WindowsEnv ? ';' : ':'; + const gccDir = path.join( + GLOBAL_SETTINGS.BLUESCRIPT_DIR, + '.espressif/tools/xtensa-esp-elf/bin', + ); + fs.makeDir(gccDir); + fs.writeFile(path.join(gccDir, esp32Env.xtensaGccFileName), ''); + return `PATH=${gccDir}${pathSep}/xtensa-esp-elf-gdb/bin`; } export function getGlobalConfig(): any { @@ -103,4 +155,3 @@ export function getGlobalConfig(): any { } return JSON.parse(fs.readFile(GLOBAL_SETTINGS.BLUESCRIPT_CONFIG_FILE)); } - diff --git a/cli/tests/commands/mock-helpers.ts b/cli/tests/commands/mock-helpers.ts index c7fa4163..c73a98f9 100644 --- a/cli/tests/commands/mock-helpers.ts +++ b/cli/tests/commands/mock-helpers.ts @@ -1,10 +1,12 @@ -import { exec, cwd } from '../../src/core/shell'; +import { simpleExec, execWithLog, execShell, cwd } from '../../src/core/command-exec'; import inquirer from 'inquirer'; import { logger } from '../../src/core/logger'; import { downloadAndUnzip } from '../../src/core/fs'; -export const mockedExec = exec as jest.Mock; +export const mockedSimpleExec = simpleExec as jest.Mock; +export const mockedExecWithLog = execWithLog as jest.Mock; +export const mockedExecShell = execShell as jest.Mock; export const mockedCwd = cwd as jest.Mock; export const mockedDownloadAndUnzip = downloadAndUnzip as jest.Mock; export const mockedInquirer = inquirer as jest.Mocked; diff --git a/cli/tests/commands/project/create.test.ts b/cli/tests/commands/project/create.test.ts index 7e15eeb6..c50a939c 100644 --- a/cli/tests/commands/project/create.test.ts +++ b/cli/tests/commands/project/create.test.ts @@ -10,7 +10,7 @@ import { import { deleteGlobalEnv, setupDefaultGlobalEnv, setupGlobalEnvWithEsp32, spyGlobalSettings } from '../global-env-helper'; describe('create project command', () => { - const DUMMY_CWD = path.join(__dirname, '../../../temp-files'); + const DUMMY_CWD = path.join(__dirname, '../../../temp-files/create'); const projectName = 'test-project'; const projectDir = path.join(DUMMY_CWD, projectName); const projectBsconfig = path.join(projectDir, 'bsconfig.json'); @@ -22,10 +22,11 @@ describe('create project command', () => { } beforeAll(() => { - spyGlobalSettings('create'); + fs.makeDir(DUMMY_CWD, true); }); beforeEach(() => { + spyGlobalSettings('create'); mockedCwd.mockReturnValue(DUMMY_CWD); deleteGlobalEnv(); deleteDummyProject(); @@ -33,13 +34,8 @@ describe('create project command', () => { afterEach(() => { jest.clearAllMocks(); - // deleteGlobalEnv(); - // deleteDummyProject(); - }); - - afterAll(() => { - // deleteGlobalEnv(); - // deleteDummyProject(); + deleteGlobalEnv(); + deleteDummyProject(); }); it('should show warning and exit if update is needed', async () => { diff --git a/cli/tests/commands/project/install.test.ts b/cli/tests/commands/project/install.test.ts index d9e604ed..ab8dd7cf 100644 --- a/cli/tests/commands/project/install.test.ts +++ b/cli/tests/commands/project/install.test.ts @@ -3,7 +3,7 @@ import * as fs from '../../../src/core/fs'; import * as path from 'path'; import { mockedCwd, - mockedExec, + mockedSimpleExec, mockedLogger, mockProcessExit, } from '../mock-helpers'; @@ -13,13 +13,14 @@ import { PROJECT_DEFAULT_PATHS } from '../../../src/config/project-config'; describe('install command', () => { - const projectRoot = path.join(__dirname, '../../../temp-files/test-project'); + const projectRoot = path.join(__dirname, '../../../temp-files/install/test-project'); beforeAll(() => { - spyGlobalSettings('install'); - }) + fs.makeDir(path.dirname(projectRoot), true); + }); beforeEach(() => { + spyGlobalSettings('install'); mockedCwd.mockReturnValue(projectRoot); }); @@ -49,10 +50,11 @@ describe('install command', () => { // --- Arrange --- setupGlobalEnvWithEsp32(); createDummyProject(projectRoot); - mockedExec.mockImplementation((command) => { - if (command.startsWith('git clone')) { - dummyGitClone(command, {}); + mockedSimpleExec.mockImplementation((cmd, args) => { + if (cmd === 'git' && args[0] === 'clone') { + dummyGitClone(args, {}); } + return Promise.resolve(''); }); // --- Act --- @@ -68,16 +70,17 @@ describe('install command', () => { // --- Arrange --- setupGlobalEnvWithEsp32(); createDummyProject(projectRoot); - mockedExec.mockImplementation((command) => { - if (command.startsWith('git clone')) { - if (command.includes('led')) { - dummyGitClone(command, { + mockedSimpleExec.mockImplementation((cmd, args) => { + if (cmd === 'git' && args[0] === 'clone') { + if (args.some((arg: string) => arg.includes('led'))) { + dummyGitClone(args, { 'pkg-gpio-esp32-project': 'https://github.com/bluescript-lang/pkg-gpio-esp32.git' }); - } else if (command.includes('gpio')) { - dummyGitClone(command, {}); + } else if (args.some((arg: string) => arg.includes('gpio'))) { + dummyGitClone(args, {}); } } + return Promise.resolve(''); }); // --- Act --- @@ -94,18 +97,19 @@ describe('install command', () => { // --- Arrange --- setupGlobalEnvWithEsp32(); createDummyProject(projectRoot); - mockedExec.mockImplementation((command) => { - if (command.startsWith('git clone')) { - if (command.includes('led')) { - dummyGitClone(command, { + mockedSimpleExec.mockImplementation((cmd, args) => { + if (cmd === 'git' && args[0] === 'clone') { + if (args.some((arg: string) => arg.includes('led'))) { + dummyGitClone(args, { 'pkg-gpio-esp32-project': 'https://github.com/bluescript-lang/pkg-gpio-esp32.git' }); - } else if (command.includes('gpio')) { - dummyGitClone(command, { + } else if (args.some((arg: string) => arg.includes('gpio'))) { + dummyGitClone(args, { 'pkg-led-esp32-project': 'https://github.com/bluescript-lang/pkg-led-esp32.git' }); } } + return Promise.resolve(''); }); // --- Act --- @@ -124,18 +128,19 @@ describe('install command', () => { 'pkg-led-esp32-project': 'https://github.com/bluescript-lang/pkg-led-esp32.git', 'pkg-pwm-esp32-project': 'https://github.com/bluescript-lang/pkg-pwm-esp32.git#v1.0.0', }); - mockedExec.mockImplementation((command) => { - if (command.startsWith('git clone')) { - if (command.includes('led')) { - dummyGitClone(command, { + mockedSimpleExec.mockImplementation((cmd, args) => { + if (cmd === 'git' && args[0] === 'clone') { + if (args.some((arg: string) => arg.includes('led'))) { + dummyGitClone(args, { 'pkg-gpio-esp32-project': 'https://github.com/bluescript-lang/pkg-gpio-esp32.git' }); - } else if (command.includes('gpio')) { - dummyGitClone(command, {}); - } else if (command.includes('pwm')) { - dummyGitClone(command, {}); + } else if (args.some((arg: string) => arg.includes('gpio'))) { + dummyGitClone(args, {}); + } else if (args.some((arg: string) => arg.includes('pwm'))) { + dummyGitClone(args, {}); } } + return Promise.resolve(''); }); // --- Act --- @@ -152,10 +157,11 @@ describe('install command', () => { const exitSpy = mockProcessExit(); setupDefaultGlobalEnv(); createDummyProject(projectRoot); - mockedExec.mockImplementation((command) => { - if (command.startsWith('git clone')) { - dummyGitClone(command, {}); + mockedSimpleExec.mockImplementation((cmd, args) => { + if (cmd === 'git' && args[0] === 'clone') { + dummyGitClone(args, {}); } + return Promise.resolve(''); }); // --- Act --- @@ -172,22 +178,17 @@ describe('install command', () => { }); -function dummyGitClone(command: string, dependencies: {[name: string]: string}) { - const urlRegex = /(https?:\/\/\S+|git@\S+)/; - const match = command.match(urlRegex); - - if (!match) { +function dummyGitClone(args: string[], dependencies: {[name: string]: string}) { + const url = args.find((arg) => arg.startsWith('http') || arg.startsWith('git@')); + if (!url) { console.error("Could not find git url."); return null; } - const fullUrl = match[0]; + const fullUrl = url; const cleanUrl = fullUrl.replace(/\.git$/, ''); const repoName = cleanUrl.split('/').pop() || ''; - - const urlIndex = command.indexOf(fullUrl); - const textAfterUrl = command.substring(urlIndex + fullUrl.length).trim(); - const targetDir = textAfterUrl.length > 0 ? textAfterUrl : repoName; + const targetDir = args[args.length - 1]; fs.makeDir(targetDir); const bsConfig = { diff --git a/cli/tests/config/global-config.test.ts b/cli/tests/config/global-config.test.ts index bf00adef..81c78381 100644 --- a/cli/tests/config/global-config.test.ts +++ b/cli/tests/config/global-config.test.ts @@ -44,7 +44,13 @@ describe('GlobalConfigHandler', () => { idfVersion: 'v5.4', rootDir: 'root/dir', exportFile: 'export.sh', - xtensaGccDir:'gcc', + toolchain: { + gcc: 'gcc', + ar: 'ar', + ld: 'ld', + make: 'make', + python: 'python3', + }, } const handler = GlobalConfigHandler.load(); handler.updateBoardConfig('esp32', boardConfig); diff --git a/cli/tests/global-mocks.ts b/cli/tests/global-mocks.ts index 7cb1a027..72254fc9 100644 --- a/cli/tests/global-mocks.ts +++ b/cli/tests/global-mocks.ts @@ -23,7 +23,7 @@ jest.mock('../src/core/logger', () => { success: jest.fn(), log: jest.fn(), br: jest.fn(), - showError: jest.fn(), + showError: jest.fn() }, } }); @@ -37,6 +37,6 @@ jest.mock('../src/core/fs', () => { }) -jest.mock('../src/core/shell'); +jest.mock('../src/core/command-exec'); // jest.mock('../src/core/fs'); jest.mock('inquirer'); diff --git a/cli/tests/integration/host-run-helper.ts b/cli/tests/integration/host-run-helper.ts index eb205809..38dbc0b9 100644 --- a/cli/tests/integration/host-run-helper.ts +++ b/cli/tests/integration/host-run-helper.ts @@ -1,7 +1,14 @@ +import * as nodeFs from 'fs'; +import * as os from 'os'; import * as path from 'path'; import * as fs from '../../src/core/fs'; import { ProjectConfigHandler } from '../../src/config/project-config'; import { PROJECT_DEFAULT_PATHS } from '../../src/config/project-config'; +import { BoardEnv, createBoardEnv } from '../../src/platforms/board-env'; +import { logger } from '../../src/core/logger'; + +const isHostPlatform = os.platform() === 'darwin' || os.platform() === 'win32'; +export const describeHostIntegration = isHostPlatform ? describe : describe.skip; export type HostPackageSpec = { name: string; @@ -62,9 +69,32 @@ export function createHostProject( handler.save(root); } -export function removeDirIfExists(dir: string) { - if (fs.exists(dir)) { - fs.removeDir(dir); +export async function removeDirIfExists(dir: string, retries = 5): Promise { + if (!fs.exists(dir)) { + return; + } + for (let i = 0; i < retries; i++) { + try { + fs.removeDir(dir); + return; + } catch (error: unknown) { + const code = (error as NodeJS.ErrnoException).code; + if ((code !== 'EPERM' && code !== 'EBUSY') || i === retries - 1) { + throw error; + } + await new Promise((resolve) => setTimeout(resolve, 200 * (i + 1))); + } + } +} + +export async function removeChildDirsWithPrefix(parentDir: string, prefix: string): Promise { + if (!nodeFs.existsSync(parentDir)) { + return; + } + for (const name of nodeFs.readdirSync(parentDir)) { + if (name.startsWith(prefix)) { + await removeDirIfExists(path.join(parentDir, name)); + } } } @@ -129,3 +159,57 @@ export const HOST_INTEGRATION_RUNTIME_DIR = path.resolve(__dirname, '../../../microcontroller'); export const HOST_INTEGRATION_BUILD_DIR = path.join(HOST_INTEGRATION_RUNTIME_DIR, 'ports/host/build'); + +export async function assertHostIntegrationPrerequisites(): Promise { + if (process.platform !== 'win32') { + return; + } + const hostEnv = createBoardEnv('host'); + if (!await hostEnv.isPackageInstalled('gcc')) { + throw new Error( + 'MinGW-w64 gcc is required for host integration tests on Windows.', + ); + } + if (!await hostEnv.isPackageInstalled('mingw32-make')) { + throw new Error( + 'mingw32-make is required for host integration tests on Windows.', + ); + } +} + +export async function ensureHostRuntimeBuilt(): Promise { + await assertHostIntegrationPrerequisites(); + jest.spyOn(BoardEnv.prototype, 'runtimeDir', 'get') + .mockReturnValue(HOST_INTEGRATION_RUNTIME_DIR); + const hostEnv = createBoardEnv('host'); + await hostEnv.buildHostRuntime(); +} + +export function dumpRunDiagnostics( + exitSpy: jest.SpyInstance, + stdout?: { text: () => string }, +): void { + const code = exitSpy.mock.calls[0]?.[0]; + const lines = [ + `exit code: ${code}`, + `logger.error: ${JSON.stringify((logger.error as jest.Mock).mock.calls, null, 2)}`, + `logger.showError: ${(logger.showError as jest.Mock).mock.calls + .map(([err]) => (err instanceof Error ? err.message : String(err))) + .join(' | ')}`, + ]; + if (stdout) { + lines.push(`stdout: ${stdout.text()}`); + } + console.error(lines.join('\n')); +} +export function expectExitCode( + exitSpy: jest.SpyInstance, + expected: number, + stdout?: { text: () => string }, +): void { + const actual = exitSpy.mock.calls[0]?.[0]; + if (actual !== expected) { + dumpRunDiagnostics(exitSpy, stdout); + } + expect(exitSpy).toHaveBeenCalledWith(expected); +} diff --git a/cli/tests/integration/project/repl.host.test.ts b/cli/tests/integration/project/repl.host.test.ts index 03f8403f..08c3616e 100644 --- a/cli/tests/integration/project/repl.host.test.ts +++ b/cli/tests/integration/project/repl.host.test.ts @@ -1,5 +1,5 @@ -jest.mock('../../../src/core/shell', () => ({ - ...jest.requireActual('../../../src/core/shell'), +jest.mock('../../../src/core/command-exec', () => ({ + ...jest.requireActual('../../../src/core/command-exec'), cwd: jest.fn(), })); @@ -8,7 +8,6 @@ import * as path from 'path'; import * as fs from '../../../src/core/fs'; import { handleReplCommand } from '../../../src/commands/repl'; import { logger } from '../../../src/core/logger'; -import { buildHostRuntime } from '../../../src/platforms/runtime/host-board-runtime'; import { deleteGlobalEnv, setupGlobalEnvWithHostIntegration, @@ -16,22 +15,24 @@ import { } from '../../commands/global-env-helper'; import { captureOutput, + describeHostIntegration, + ensureHostRuntimeBuilt, + expectExitCode, HOST_INTEGRATION_BUILD_DIR, HOST_INTEGRATION_RUNTIME_DIR, mockProcessExit, removeDirIfExists, + removeChildDirsWithPrefix, waitFor, waitForStdoutContains, } from '../host-run-helper'; const TEMP_DIR = path.join(__dirname, '../../../temp-files/integration-repl'); -const SHELL_PATH = path.join(HOST_INTEGRATION_BUILD_DIR, 'shell'); -const RUNTIME_SO_PATH = path.join(HOST_INTEGRATION_BUILD_DIR, 'c-runtime.so'); - -const describeHost = process.platform === 'darwin' ? describe : describe.skip; +const GLOBAL_TEMP_DIR = path.join(__dirname, '../../../temp-files'); let replLineHandler: ((line: string) => void) | undefined; let replCloseHandler: (() => void) | undefined; +let replTestCounter = 0; function createMockReadline(): readline.Interface { return { @@ -52,20 +53,17 @@ function createMockReadline(): readline.Interface { } as unknown as readline.Interface; } -describeHost('repl command (host integration)', () => { +describeHostIntegration('repl command (host integration)', () => { jest.setTimeout(30000); beforeAll(async () => { - spyGlobalSettings('repl-integration'); fs.makeDir(TEMP_DIR); - - if (!fs.exists(SHELL_PATH) || !fs.exists(RUNTIME_SO_PATH)) { - await buildHostRuntime(HOST_INTEGRATION_RUNTIME_DIR, HOST_INTEGRATION_BUILD_DIR); - } + await ensureHostRuntimeBuilt(); }); beforeEach(() => { jest.clearAllMocks(); + spyGlobalSettings(`repl-integration-${++replTestCounter}`); deleteGlobalEnv(); setupGlobalEnvWithHostIntegration( HOST_INTEGRATION_RUNTIME_DIR, @@ -75,9 +73,10 @@ describeHost('repl command (host integration)', () => { replCloseHandler = undefined; }); - afterAll(() => { + afterAll(async () => { deleteGlobalEnv(); - removeDirIfExists(TEMP_DIR); + await removeDirIfExists(TEMP_DIR); + await removeChildDirsWithPrefix(GLOBAL_TEMP_DIR, '.bluescript-repl-integration-'); }); async function sendReplLine( @@ -121,7 +120,7 @@ describeHost('repl command (host integration)', () => { await closeRepl(); await replPromise; - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, output); expect(output.text()).toContain('repl entry'); output.restore(); @@ -136,7 +135,7 @@ describeHost('repl command (host integration)', () => { await closeRepl(); await replPromise; - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, output); expect(output.text()).toContain('init'); expect(output.text()).toContain('via print'); @@ -152,7 +151,7 @@ describeHost('repl command (host integration)', () => { await closeRepl(); await replPromise; - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, output); output.restore(); exitSpy.mockRestore(); @@ -170,7 +169,7 @@ describeHost('repl command (host integration)', () => { await closeRepl(); await replPromise; - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, output); output.restore(); exitSpy.mockRestore(); @@ -190,7 +189,7 @@ describeHost('repl command (host integration)', () => { await closeRepl(); await replPromise; - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, output); expect(output.text()).toContain('after error'); output.restore(); diff --git a/cli/tests/integration/project/run.host.test.ts b/cli/tests/integration/project/run.host.test.ts index 43267d19..82941a74 100644 --- a/cli/tests/integration/project/run.host.test.ts +++ b/cli/tests/integration/project/run.host.test.ts @@ -1,13 +1,12 @@ -jest.mock('../../../src/core/shell', () => ({ - ...jest.requireActual('../../../src/core/shell'), +jest.mock('../../../src/core/command-exec', () => ({ + ...jest.requireActual('../../../src/core/command-exec'), cwd: jest.fn(), })); import * as path from 'path'; -import { cwd } from '../../../src/core/shell'; +import { cwd } from '../../../src/core/command-exec'; import * as fs from '../../../src/core/fs'; import { handleRunCommand } from '../../../src/commands/project/run'; -import { buildHostRuntime } from '../../../src/platforms/runtime/host-board-runtime'; import { deleteGlobalEnv, setupGlobalEnvWithHostIntegration, @@ -16,6 +15,11 @@ import { import { captureStdout, createHostProject, + describeHostIntegration, + ensureHostRuntimeBuilt, + expectExitCode, + HOST_INTEGRATION_BUILD_DIR, + HOST_INTEGRATION_RUNTIME_DIR, mockProcessExit, removeDirIfExists, } from '../host-run-helper'; @@ -23,48 +27,44 @@ import { const mockedCwd = cwd as jest.Mock; const TEMP_DIR = path.join(__dirname, '../../../temp-files/integration'); -const RUNTIME_DIR = path.resolve(__dirname, '../../../../microcontroller'); -const BUILD_DIR = path.join(RUNTIME_DIR, 'ports/host/build'); -const PROJECT_ROOT = path.join(TEMP_DIR, 'run-project'); -const SHELL_PATH = path.join(BUILD_DIR, 'shell'); -const RUNTIME_SO_PATH = path.join(BUILD_DIR, 'c-runtime.so'); -const describeHost = process.platform === 'darwin' ? describe : describe.skip; +let currentProjectRoot: string; +let testCounter = 0; -describeHost('project run command (host integration)', () => { +describeHostIntegration('project run command (host integration)', () => { beforeAll(async () => { spyGlobalSettings('run-integration'); fs.makeDir(TEMP_DIR); - - if (!fs.exists(SHELL_PATH) || !fs.exists(RUNTIME_SO_PATH)) { - await buildHostRuntime(RUNTIME_DIR, BUILD_DIR); - } + await ensureHostRuntimeBuilt(); }); beforeEach(() => { deleteGlobalEnv(); - setupGlobalEnvWithHostIntegration(RUNTIME_DIR, BUILD_DIR); - removeDirIfExists(PROJECT_ROOT); - fs.makeDir(PROJECT_ROOT); - mockedCwd.mockReturnValue(PROJECT_ROOT); + setupGlobalEnvWithHostIntegration( + HOST_INTEGRATION_RUNTIME_DIR, + HOST_INTEGRATION_BUILD_DIR, + ); + currentProjectRoot = path.join(TEMP_DIR, `run-project-${++testCounter}`); + fs.makeDir(currentProjectRoot); + mockedCwd.mockReturnValue(currentProjectRoot); }); - afterAll(() => { + afterAll(async () => { deleteGlobalEnv(); - removeDirIfExists(TEMP_DIR); + await removeDirIfExists(TEMP_DIR); }); it('runs a program and prints output', async () => { const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': 'console.log("hello from run");', - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('hello from run'); stdout.restore(); @@ -75,17 +75,17 @@ describeHost('project run command (host integration)', () => { const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': ` console.log("built-in"); print("via print"); console.log(time.now()); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('built-in'); expect(stdout.text()).toContain('via print'); @@ -97,7 +97,7 @@ console.log(time.now()); const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': ` const message = "hello"; function greet(): void { @@ -105,11 +105,11 @@ function greet(): void { } greet(); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('hello'); stdout.restore(); @@ -120,7 +120,7 @@ greet(); const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/math-utils.bs': ` export function add(a: integer, b: integer): integer { return a + b; @@ -130,11 +130,11 @@ export function add(a: integer, b: integer): integer { import { add } from "./math-utils"; console.log(add(10, 20)); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('30'); stdout.restore(); @@ -145,12 +145,12 @@ console.log(add(10, 20)); const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': ` import { mul } from "math-lib"; console.log(mul(3, 4)); `.trim(), - }, RUNTIME_DIR, 'test-run', [{ + }, HOST_INTEGRATION_RUNTIME_DIR, 'test-run', [{ name: 'math-lib', sources: { 'src/index.bs': ` @@ -163,7 +163,7 @@ export function mul(a: integer, b: integer): integer { await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('12'); stdout.restore(); @@ -174,7 +174,7 @@ export function mul(a: integer, b: integer): integer { const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': ` code\`#include \` @@ -186,11 +186,11 @@ function pow(x: float, y: float): float { console.log(pow(2.0, 3.0)); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toMatch(/8(\.0+)?/); stdout.restore(); @@ -201,7 +201,7 @@ console.log(pow(2.0, 3.0)); const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/add.c': 'int add(int a, int b) { return a + b; }', 'src/index.bs': ` code\`#include "./add.c"\` @@ -214,11 +214,11 @@ function main(): void { main(); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('30'); stdout.restore(); @@ -229,7 +229,7 @@ main(); const exitSpy = mockProcessExit(); const stdout = captureStdout(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/add.h': 'int add(int a, int b);', 'src/add.c': '#include "add.h"\nint add(int a, int b) { return a + b; }', 'src/index.bs': ` @@ -243,11 +243,11 @@ function main(): void { main(); `.trim(), - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); - expect(exitSpy).toHaveBeenCalledWith(0); + expectExitCode(exitSpy, 0, stdout); expect(stdout.text()).toContain('11'); stdout.restore(); @@ -257,9 +257,9 @@ main(); it('exits with an error when compilation fails', async () => { const exitSpy = mockProcessExit(); - createHostProject(PROJECT_ROOT, { + createHostProject(currentProjectRoot, { 'src/index.bs': 'this is not valid bluescript', - }, RUNTIME_DIR); + }, HOST_INTEGRATION_RUNTIME_DIR); await handleRunCommand({ withRepl: false, withNotebook: false }); diff --git a/lang/src/compiler/board-toolchain/board-toolchain.ts b/lang/src/compiler/board-toolchain/board-toolchain.ts index de564456..8ed4ba5b 100644 --- a/lang/src/compiler/board-toolchain/board-toolchain.ts +++ b/lang/src/compiler/board-toolchain/board-toolchain.ts @@ -1,4 +1,5 @@ import { Project } from '../project'; +import { Package } from '../package'; export type MemoryLayout = { iram:{address:number, size:number}, @@ -38,16 +39,16 @@ export type MemoryImage = { entryPoints: {isMain: boolean, address: number}[] } -export type SharedObject = { - soFile: string, +export type SharedLibrary = { + filePath: string, entryNames: { isMain: boolean, name: string}[], }; -export type CompileOutput = MemoryImage | SharedObject; +export type CompileOutput = MemoryImage | SharedLibrary; -export interface BoardToolchain

{ +export interface BoardToolchain

{ get cProlog(): string; get builtinModulePath(): string; - compileAndLink(project: P, entryPoints: string[]): Promise; - additionalCompileAndLink(project: P, entryPoints: string[]): Promise; + compileAndLink(project: Project

, entryPoints: string[]): Promise; + additionalCompileAndLink(project: Project, entryPoints: string[]): Promise; } \ No newline at end of file diff --git a/lang/src/compiler/board-toolchain/esp32-toolchain.ts b/lang/src/compiler/board-toolchain/esp32-toolchain.ts index 40d3789a..6064c7c3 100644 --- a/lang/src/compiler/board-toolchain/esp32-toolchain.ts +++ b/lang/src/compiler/board-toolchain/esp32-toolchain.ts @@ -1,6 +1,7 @@ import * as path from "path"; import * as fs from "fs"; -import { PackageForEsp32, ProjectForEsp32 } from "../project"; +import { PackageForEsp32 } from "../package"; +import { Project } from "../project"; import { BoardToolchain, MemoryImage, MemoryLayout, ShadowMemory } from "./board-toolchain"; import { executeCommand, getErrorMessage } from "../utils"; import { generateMakefile, esp32MakefilePreset } from "./tools/makefile"; @@ -10,11 +11,16 @@ import generateLinkerScript from "./tools/linker-script"; export type Esp32ToolchainConfig = { runtimeDir: string, - compilerToolchainDir: string, + compilerToolchain: { + gcc: string, + ar: string, + ld: string, + make: string + }, espDir: string } -export class Esp32Toolchain implements BoardToolchain { +export class Esp32Toolchain implements BoardToolchain { public memory: ShadowMemory; private config: Esp32ToolchainConfig; @@ -31,7 +37,8 @@ export class Esp32Toolchain implements BoardToolchain [s.name, s])); } - async compileAndLink(project: ProjectForEsp32, entryPoints: string[]): Promise { + async compileAndLink(project: Project, entryPoints: string[]): Promise { for (const pkg of project.usedDependencies) { - await this.compileC(project, pkg); + await this.compilePackage(pkg); this.compiledPackages.add(pkg.name); } - await this.compileC(project, project.mainPackage); + await this.compilePackage(project.mainPackage); const elfPath = await this.link(project, entryPoints); return this.extractBinary(elfPath, entryPoints); } - async additionalCompileAndLink(project: ProjectForEsp32, entryPoints: string[]): Promise { + async additionalCompileAndLink(project: Project, entryPoints: string[]): Promise { for (const pkg of project.usedDependencies) { if (!this.compiledPackages.has(pkg.name)) { - await this.compileC(project, pkg); + await this.compilePackage(pkg); this.compiledPackages.add(pkg.name); } } - await this.compileC(project, project.mainPackage); + await this.compilePackage(project.mainPackage); const elfPath = await this.link(project, entryPoints); return this.extractBinary(elfPath, entryPoints); } - private async compileC(project: ProjectForEsp32, pkg: PackageForEsp32): Promise { + private async compilePackage(pkg: PackageForEsp32): Promise { try { - const archivePath = project.archiveFile(pkg); + const archivePath = pkg.archiveFile; const includeDirs = [ ...this.espIdfComponents.getIncludeDirs(pkg.espIdfComponents), ...this.espIdfComponents.commonIncludeDirs @@ -77,21 +84,21 @@ export class Esp32Toolchain implements BoardToolchain { + private async link(project: Project, entryPoints: string[]): Promise { try { const cwd = process.cwd(); - const elfPath = project.elfFile(); + const elfPath = project.mainPackage.elfFile; const archives = this.getArchivesWithEspComponents(project); const linkerscript = generateLinkerScript( @@ -102,7 +109,7 @@ export class Esp32Toolchain implements BoardToolchain): string[] { const espArchivesFromMain = this.espIdfComponents.getArchiveFilePaths(project.mainPackage.espIdfComponents); - const resultArchives = [project.archiveFile(project.mainPackage), ...espArchivesFromMain]; + const resultArchives = [project.mainPackage.archiveFile, ...espArchivesFromMain]; const visitedEspArchives = new Set(espArchivesFromMain); @@ -157,7 +164,7 @@ export class Esp32Toolchain implements BoardToolchain addEspArchive(ar)); } diff --git a/lang/src/compiler/board-toolchain/host-toolchain.ts b/lang/src/compiler/board-toolchain/host-toolchain.ts index 7f7cd957..cebdcbe0 100644 --- a/lang/src/compiler/board-toolchain/host-toolchain.ts +++ b/lang/src/compiler/board-toolchain/host-toolchain.ts @@ -1,19 +1,28 @@ import * as path from "path"; import * as fs from "fs"; -import { BoardToolchain, SharedObject } from "./board-toolchain"; -import { Package, ProjectForHost } from "../project"; -import { generateMakefile, hostMakefilePreset } from "./tools/makefile"; +import { BoardToolchain, SharedLibrary } from "./board-toolchain"; +import { Project } from "../project"; +import { Package, PackageForHostUnix, PackageForHostWindows } from "../package"; +import { generateMakefile, hostUnixMakefilePrest, hostWindowsMakefilePreset } from "./tools/makefile"; import { executeCommand, getErrorMessage } from "../utils"; +export type HostToolchainConfig = { + runtimeDir: string, + compilerToolchain: { + gcc: string, + ar: string, + make: string + }, +} -export class HostToolchain implements BoardToolchain { - private runtimeDir: string; - private compileId: number = 0; - private compiledPackages = new Set(); - private generatedSoFiles: string[] = []; +export abstract class HostToolchain

implements BoardToolchain { + protected config: HostToolchainConfig; + protected compileId: number = 0; + protected compiledPackages = new Set(); + protected generatedSharedLibs: string[] = []; - constructor(runtimeDir: string) { - this.runtimeDir = runtimeDir; + constructor(config: HostToolchainConfig) { + this.config = config; } get cProlog() { @@ -22,85 +31,151 @@ export class HostToolchain implements BoardToolchain { + async compileAndLink(project: Project

, entryPoints: string[]): Promise { const archiveFiles: string[] = []; for (const pkg of project.usedDependencies) { - archiveFiles.push(await this.compilePackage(project, pkg)); + archiveFiles.push(await this.compilePackage(pkg)); this.compiledPackages.add(pkg.name); } - archiveFiles.push(await this.compilePackage(project, project.mainPackage)); - const soFile = project.soFile(); - await this.link(archiveFiles, entryPoints, soFile); - this.generatedSoFiles.push(soFile); + archiveFiles.push(await this.compilePackage(project.mainPackage)); + const sharedLib = await this.link(project, archiveFiles, entryPoints); + this.generatedSharedLibs.push(sharedLib); + // Prevent the main package's generated C from being recompiled into the + // next (fragment) shared library. Otherwise each fragment statically + // redefines all prior globals, which breaks cross-fragment variable + // access on platforms without symbol interposition (e.g. Windows). + project.mainPackage.removeGeneratedCFiles(); return { - soFile, + filePath: sharedLib, entryNames: entryPoints.map(name => ({isMain: name === project.mainPackage.name, name})), } } - async additionalCompileAndLink(project: ProjectForHost, entryPoints: string[]): Promise { + async additionalCompileAndLink(project: Project

, entryPoints: string[]): Promise { const archiveFiles: string[] = []; for (const pkg of project.usedDependencies) { if (!this.compiledPackages.has(pkg.name)) { - archiveFiles.push(await this.compilePackage(project, pkg)); + archiveFiles.push(await this.compilePackage(pkg)); this.compiledPackages.add(pkg.name); + pkg.removeGeneratedCFiles(); } } - archiveFiles.push(await this.compilePackage(project, project.mainPackage)); - const soFile = project.soFile(this.compileId++); - await this.link(archiveFiles, entryPoints, soFile); - this.generatedSoFiles.push(soFile); + archiveFiles.push(await this.compilePackage(project.mainPackage)); + const sharedLib = await this.link(project, archiveFiles, entryPoints); + this.generatedSharedLibs.push(sharedLib); + project.mainPackage.removeGeneratedCFiles(); return { - soFile, + filePath: sharedLib, entryNames: entryPoints.map(name => ({isMain: name === project.mainPackage.name, name})), } } - private async compilePackage(project: ProjectForHost, pkg: Package): Promise { + abstract compilePackage(pkg: P): Promise; + abstract link(project: Project

, archiveFiles: string[], entryPoints: string[]): Promise; +} + +export class HostUnixToolchain extends HostToolchain { + get runtimeSo() { return path.join(this.runtimeBuildDir, 'c-runtime.so'); } + + async compilePackage(pkg: PackageForHostUnix): Promise { try { - const archiveFile = project.archiveFile(pkg); + const archiveFile = pkg.archiveFile; // Remove old archive file. if (fs.existsSync(archiveFile)) { fs.rmSync(archiveFile, { force: true }); } - const makefile = generateMakefile(hostMakefilePreset(pkg, archiveFile)); - project.writeMakefile(pkg, makefile); - await executeCommand('make', [], pkg.resolvedDistDir); + pkg.copyNativeFilesToDist(); + const makefile = generateMakefile(hostUnixMakefilePrest(pkg, this.config.compilerToolchain)); + pkg.writeMakefile(makefile); + await executeCommand(this.config.compilerToolchain.make, [], pkg.resolvedDistDir); return archiveFile; } catch (error) { throw new Error(`Failed to compile package ${pkg.name}: ${getErrorMessage(error)}`, {cause: error}); } } - private linkerSymbolName(sym: string): string { - return process.platform === 'darwin' ? `_${sym}` : sym; - } - - private async link(archiveFiles: string[], entryPoints: string[], outputFile: string): Promise { + async link(project: Project, archiveFiles: string[], entryPoints: string[]): Promise { try { const keepEntrySymbols = entryPoints.map( - (sym) => `-Wl,-u,${this.linkerSymbolName(sym)}`, + (sym) => `-Wl,-u,_${sym}`, ); + const outputFile = project.mainPackage.soFile(this.compileId++); const args = [ '-shared', '-fPIC', '-o', outputFile, ...archiveFiles, - ...this.generatedSoFiles, + ...this.generatedSharedLibs, this.runtimeSo, '-lm', '-ldl', ...keepEntrySymbols, ]; - await executeCommand('cc', args); + await executeCommand(this.config.compilerToolchain.gcc, args); + return outputFile; } catch (error) { throw new Error(`Failed to link: ${getErrorMessage(error)}`, {cause: error}); } } +} + +export class HostWindowsToolchain extends HostToolchain { + get runtimeDll(): string { + return path.join(this.runtimeBuildDir, 'c-runtime.dll'); + } + + async compilePackage(pkg: PackageForHostWindows): Promise { + try { + const archiveFile = pkg.archiveFile; + if (fs.existsSync(archiveFile)) { + fs.rmSync(archiveFile, { force: true }); + } + pkg.copyNativeFilesToDist(); + const makefile = generateMakefile( + hostWindowsMakefilePreset(pkg, this.config.compilerToolchain), + ); + pkg.writeMakefile(makefile); + + await executeCommand(this.config.compilerToolchain.make, [], pkg.resolvedDistDir); + return archiveFile; + } catch (error) { + throw new Error( + `Failed to compile package ${pkg.name}: ${getErrorMessage(error)}`, + { cause: error }, + ); + } + } + + async link( + project: Project, + archiveFiles: string[], + entryPoints: string[], + ): Promise { + try { + const keepEntrySymbols = entryPoints.map( + (sym) => `-Wl,-u,${sym}`, + ); + const outputFile = project.mainPackage.dllFile(this.compileId++); + const args = [ + '-shared', + '-o', outputFile, + ...archiveFiles, + ...this.generatedSharedLibs, + this.runtimeDll, + '-lm', + ...keepEntrySymbols, + '-Wl,--export-all-symbols', + '-Wl,--enable-auto-import', + '-Wl,--enable-runtime-pseudo-reloc' + ]; + await executeCommand(this.config.compilerToolchain.gcc, args); + return outputFile; + } catch (error) { + throw new Error(`Failed to link: ${getErrorMessage(error)}`, { cause: error }); + } + } } \ No newline at end of file diff --git a/lang/src/compiler/board-toolchain/result.txt b/lang/src/compiler/board-toolchain/result.txt new file mode 100644 index 00000000..52a6d79a --- /dev/null +++ b/lang/src/compiler/board-toolchain/result.txt @@ -0,0 +1,41 @@ +C:\Users\qiand\.bluescript\temp\dist\build>objdump -x temp1.dll | findstr /i "__imp__a temp0 .refptr._a" +[ 59](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000000002b0 .rdata$.refptr._a +[675](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000198 __imp__amsg_exit +[718](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000002b0 .refptr._a + +C:\Users\qiand\.bluescript\temp\dist\build>objdump -x temp1.dll | findstr /i "_a" + 00009198 00b6 _amsg_exit + [ 0] +base[ 1] 0000 _a + 10 .debug_aranges 00000050 000000033e4ec000 000000033e4ec000 00003400 2**0 + 12 .debug_abbrev 000000ca 000000033e4ef000 000000033e4ef000 00004a00 2**0 +[ 4](sec 6)(fl 0x00)(ty 0)(scl 3) (nx 0) 0x0000000000000018 __proc_attached +[ 12](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000290 .rdata$.refptr.__xi_a +[ 16](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000270 .rdata$.refptr.__xc_a +[ 23](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000230 .rdata$.refptr.__mingw_app_type +[ 59](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x00000000000002b0 .rdata$.refptr._a +[124](sec 3)(fl 0x00)(ty 0)(scl 3) (nx 0) 0x0000000000000410 __xd_a +[212](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x0000000000000b50 ___w64_mingwthr_add_key_dtor +[283](sec 13)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000000 .debug_abbrev +[295](sec 11)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000000 .debug_aranges +[311](sec 13)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000014 .debug_abbrev +[313](sec 11)(fl 0x00)(ty 0)(scl 3) (nx 1) 0x0000000000000030 .debug_aranges +[349](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 1) 0x0000000000001210 __acrt_iob_func +[644](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000001b8 __imp_abort +[645](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000003e4 __lib64_libkernel32_a_iname +[651](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000014 _head_lib64_libmsvcrt_def_a +[657](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000003f0 __xl_a +[663](sec 6)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000040 _a +[675](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000198 __imp__amsg_exit +[690](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000230 .refptr.__mingw_app_type +[703](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000290 .refptr.__xi_a +[705](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000001000 __section_alignment__ +[716](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x000000000000042c __lib64_libmsvcrt_def_a_iname +[718](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000002b0 .refptr._a +[721](sec 8)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000000 _head_lib64_libkernel32_a +[741](sec -1)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000200 __file_alignment__ +[752](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000003e0 __xi_a +[756](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x00000000000003d0 __xc_a +[760](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x00000000000013b8 _amsg_exit +[777](sec 2)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000050 __imp___acrt_iob_func +[788](sec 3)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000270 .refptr.__xc_a +[801](sec 6)(fl 0x00)(ty 0)(scl 2) (nx 0) 0x0000000000000080 __mingw_app_type diff --git a/lang/src/compiler/board-toolchain/tools/makefile.ts b/lang/src/compiler/board-toolchain/tools/makefile.ts index 6f6b504a..72a068dc 100644 --- a/lang/src/compiler/board-toolchain/tools/makefile.ts +++ b/lang/src/compiler/board-toolchain/tools/makefile.ts @@ -1,164 +1,117 @@ -import { Package } from "../../project"; - +import { PackageForEsp32, PackageForHostUnix, PackageForHostWindows } from "../../package" type MakefileConfig = { - pkg: Package, + outputFile: string, + objectFiles: string[], + headerFilesInDist: string[], includeDirs: string[], compileFlags: string[], - outputFile: string, - toolchain: { cc: string; ar: string } + distDir: string, + buildDir: string, + toolchain: { + cc: string, + ar: string + } } +function toMakePath(p: string) { + return p.replace(/\\/g, '/'); +}; -export function esp32MakefilePreset(toolchainDir: string, pkg: Package, includeDirs: string[], outputFile: string): MakefileConfig { +export function esp32MakefilePreset(pkg: PackageForEsp32, includeDirs: string[], toolchain: {gcc: string, ar: string}): MakefileConfig { return { - pkg, includeDirs, + outputFile: toMakePath(pkg.archiveFile), + objectFiles: pkg.objectFiles.map(toMakePath), + headerFilesInDist: pkg.headerFilesInDist.map(toMakePath), + includeDirs: [pkg.resolvedBuildDir, ...includeDirs].map(toMakePath), compileFlags: [ '-O2', '-w', '-fno-common', '-ffunction-sections', '-fdata-sections', '-mtext-section-literals', '-mlongcalls', '-fno-zero-initialized-in-bss', ], - outputFile, + distDir: toMakePath(pkg.resolvedDistDir), + buildDir: toMakePath(pkg.resolvedBuildDir), toolchain: { - cc: `${toolchainDir}/xtensa-esp32-elf-gcc`, - ar: `${toolchainDir}/xtensa-esp32-elf-ar` + cc: toMakePath(toolchain.gcc), + ar: toMakePath(toolchain.ar) } } } -export function hostMakefilePreset(pkg: Package, outputFile: string): MakefileConfig { +export function hostUnixMakefilePrest(pkg: PackageForHostUnix, toolchain: {gcc: string, ar: string}) { return { - pkg, - includeDirs: [], + outputFile: pkg.archiveFile, + objectFiles: pkg.objectFiles, + headerFilesInDist: pkg.headerFilesInDist, + includeDirs: [pkg.resolvedDistDir], compileFlags: ['-O2', '-w', '-fPIC', '-DLINUX64'], - outputFile, + distDir: pkg.resolvedDistDir, + buildDir: pkg.resolvedBuildDir, toolchain: { - cc: `cc`, - ar: `ar` + cc: toolchain.gcc, + ar: toolchain.ar } } } -export function generateMakefile(config: MakefileConfig): string { - return [ - // preamble - renderHeader(config), - renderValidation(config), - renderSourceVars(config), - renderCompileFlags(config), - - // body - renderPhonyAll(config), - renderCopyRules(config), - renderBuildRules(config), - renderClean(config) - ].join('\n\n'); +export function hostWindowsMakefilePreset(pkg: PackageForHostWindows, toolchain: {gcc: string, ar: string}): MakefileConfig { + return { + outputFile: toMakePath(pkg.archiveFile), + objectFiles: pkg.objectFiles.map(toMakePath), + headerFilesInDist: pkg.headerFilesInDist.map(toMakePath), + includeDirs: [toMakePath(pkg.resolvedDistDir)], + compileFlags: ['-O2', '-w', '-DLINUX64', '-fno-common'], + distDir: toMakePath(pkg.resolvedDistDir), + buildDir: toMakePath(pkg.resolvedBuildDir), + toolchain: { + cc: toolchain.gcc, + ar: toolchain.ar + }, + }; } -function renderHeader(config: MakefileConfig): string { - const { pkg } = config; - return `# === Basic settings === +export function generateMakefile(config: MakefileConfig) { + return ` + +# === Variable settings === CC := ${config.toolchain.cc} AR := ${config.toolchain.ar} +DIST_DIR := ${config.distDir} +BUILD_DIR := ${config.buildDir} +TARGET := ${config.outputFile} +OBJECTS := ${config.objectFiles.join(' ')} +DIST_HEADERS := ${config.headerFilesInDist} +INCLUDES := ${config.includeDirs.map(path => `-I ${path}`).join(' ')} +CFLAGS := $(INCLUDES) ${config.compileFlags.join(' ')} -# === Directory settings === -SRC_DIR := ${pkg.resolvedSourceDir} -DIST_DIR := ${pkg.resolvedDistDir} -BUILD_DIR := ${pkg.resolvedBuildDir} -PACKAGES_DIR := ${pkg.resolvedPackageDir} - -TARGET := ${config.outputFile}`; -} - -function renderValidation(_config: MakefileConfig): string { - return `# === Check for illegal file name prefixes === -ILLEGAL_PREFIX_FILES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "bs_*.c" -print) -ifneq ($(ILLEGAL_PREFIX_FILES),) - $(error ERROR: You cannot use 'bs_' prefix for C source file names. Please remove or rename the following files: \\ - $(ILLEGAL_PREFIX_FILES)) -endif`; -} - -function renderSourceVars(_config: MakefileConfig): string { - return `# === Source and object file settings === -ORIG_SOURCES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.c" -print) -ORIG_HEADERS := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.h" -print) -DIST_SOURCES := $(foreach src,$(ORIG_SOURCES), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)) \\ -) -DIST_HEADERS := $(foreach hdr,$(ORIG_HEADERS), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)) \\ -) -DIST_SOURCES += $(shell find $(DIST_DIR) -path $(BUILD_DIR) -prune -o -type f -name "bs_*.c" -print) -OBJECTS := $(patsubst $(DIST_DIR)/%.c, $(BUILD_DIR)/%.o, $(DIST_SOURCES))`; -} - -function renderCompileFlags(config: MakefileConfig): string { - const extraIncludes = config.includeDirs.map(path => `-I ${path}`).join(' '); - const includes = extraIncludes - ? `-I $(DIST_DIR) -I $(SRC_DIR) ${extraIncludes}` - : `-I $(DIST_DIR) -I $(SRC_DIR)`; - return `# === Compilation settings === -INCLUDES := ${includes} -CFLAGS := $(INCLUDES) ${config.compileFlags.join(' ')}`; -} -function renderPhonyAll(_config: MakefileConfig): string { - return `# ==================================================================== .PHONY: all +all: $(TARGET) -all: $(TARGET)`; -} - -function renderCopyRules(_config: MakefileConfig): string { - return `# Copy rules -# -------------------------------------------------------- - -define COPY_RULE_TEMPLATE -$(1): $(2) -\t@echo "Copying $$< to $$@" -\t@mkdir -p $$(dir $$@) -\t@cp $$< $$@ -endef - -$(foreach src,$(ORIG_SOURCES), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)), \\ - $(src) \\ - )) \\ -) - -$(foreach hdr,$(ORIG_HEADERS), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)), \\ - $(hdr) \\ - )) \\ -)`; -} - -function renderBuildRules(_config: MakefileConfig): string { - return `# Build rules +# Build rules # -------------------------------------------------------- $(TARGET): $(OBJECTS) | $(DIST_HEADERS) \t@echo "Archiving library: $@" -\t@mkdir -p $(@D) +\t@mkdir -p "$(@D)" \t$(AR) rcs $@ $^ vpath %.c $(DIST_DIR) $(BUILD_DIR)/%.o: $(DIST_DIR)/%.c \t@echo "Compiling: $< -> $@" -\t@mkdir -p $(@D) +\t@mkdir -p "$(@D)" \t$(CC) $(CFLAGS) -MMD -MP -c $< -o $@ --include $(wildcard $(BUILD_DIR)/*.d)`; -} +-include $(wildcard $(BUILD_DIR)/*.d); + +# -------------------------------------------------------- -function renderClean(_config: MakefileConfig): string { - return `.PHONY: clean +.PHONY: clean clean: \t@echo "Cleaning dist directory..." -\t@rm -rf $(DIST_DIR)`; -} +\t@rm -rf $(DIST_DIR) + + ` +} \ No newline at end of file diff --git a/lang/src/compiler/compiler-session.ts b/lang/src/compiler/compiler-session.ts index 532edd91..ea0d9d8b 100644 --- a/lang/src/compiler/compiler-session.ts +++ b/lang/src/compiler/compiler-session.ts @@ -1,18 +1,19 @@ import { BoardToolchain, CompileOutput } from "./board-toolchain/board-toolchain"; +import { Package } from "./package"; import { Project } from "./project"; import { TranspilerSession } from "./transpiler-session"; -export class CompilerSession

{ +export class CompilerSession

{ private transpiler: TranspilerSession; private toolchain: BoardToolchain; - private project: P | null = null; + private project: Project

| null = null; constructor(toolchain: BoardToolchain) { this.transpiler = new TranspilerSession(toolchain.builtinModulePath, toolchain.cProlog); this.toolchain = toolchain; } - public async buildProject(project: P): Promise { + public async buildProject(project: Project

): Promise { this.project = project; project.check(); diff --git a/lang/src/compiler/package.ts b/lang/src/compiler/package.ts new file mode 100644 index 00000000..4e6b7c1e --- /dev/null +++ b/lang/src/compiler/package.ts @@ -0,0 +1,226 @@ +import * as fs from "fs"; +import * as path from "path"; + + +type RelativePath = string; +type AbsolutePath = string; + + +export class Package { + readonly name: string; + readonly rootDir: AbsolutePath; + readonly entry: RelativePath; + readonly sourceDir: RelativePath; + readonly distDir: RelativePath; + readonly buildDir: RelativePath; + readonly packageDir: RelativePath; + readonly dependencies: string[]; + + get resolvedEntry(): AbsolutePath { return path.join(this.rootDir, this.entry); } + get resolvedSourceDir(): AbsolutePath { return path.join(this.rootDir, this.sourceDir); } + get resolvedDistDir(): AbsolutePath { return path.join(this.rootDir, this.distDir); } + get resolvedBuildDir(): AbsolutePath { return path.join(this.rootDir, this.buildDir); } + get resolvedPackageDir(): AbsolutePath { return path.join(this.rootDir, this.packageDir); } + get archiveFile(): AbsolutePath { return path.join(this.resolvedBuildDir, `lib${this.name}.a`); } + get objectFiles(): AbsolutePath[] { + const objects: AbsolutePath[] = []; + this.walkFiles(this.resolvedDistDir, (name, fullPath) => { + if (fullPath.endsWith('.c')) { + objects.push(this.toObjectFile(fullPath)); + } + }, [this.resolvedBuildDir]); + return objects; + } + get headerFilesInDist(): AbsolutePath[] { + const headers: AbsolutePath[] = []; + this.walkFiles(this.resolvedDistDir, (name, fullPath) => { + if (fullPath.endsWith('.h')) { + headers.push(fullPath); + } + }, [this.resolvedBuildDir]); + return headers; + } + + constructor( + name: string, + path: { + rootDir: AbsolutePath, + entry: RelativePath, + sourceDir: RelativePath, + distDir: RelativePath, + buildDir: RelativePath, + packageDir: RelativePath, + }, + dependencies: string[], + ) { + this.name = name; + this.dependencies = dependencies; + this.rootDir = path.rootDir; + this.sourceDir = path.sourceDir; + this.entry = path.entry; + this.distDir = path.distDir; + this.buildDir = path.buildDir; + this.packageDir = path.packageDir; + } + + check() { + const invalidBsFilePattern = /^\d+\.bs$/; + const invalidCFilePattern = /^bs_.*\.c$/; + if (!fs.existsSync(this.sourceDir)) { + return; + } + this.walkFiles(this.resolvedSourceDir, (name, fullPath) => { + if (invalidBsFilePattern.test(name)) { + throw new Error( + `Invalid file name: ${fullPath}\n` + + `BlueScript source file names cannot consist solely of digits.` + ); + } + if (invalidCFilePattern.test(name)) { + throw new Error( + `Invalid file name: ${fullPath}\n` + + `You cannot use 'bs_' prefix for C source file names.` + ); + } + }, [this.resolvedDistDir, this.packageDir]); + } + + copyNativeFilesToDist() { + this.walkFiles(this.resolvedSourceDir, (name, fullPath) => { + if (name.endsWith('.c') || name.endsWith('.h')) { + const dest = this.replacePrefix(this.resolvedSourceDir, this.resolvedDistDir, fullPath); + fs.cpSync(fullPath, dest); + } + }, [this.resolvedDistDir, this.packageDir]); + } + + clean(): void { + fs.rmSync(this.resolvedDistDir, { recursive: true, force: true }); + } + + readSourceFile(p: RelativePath): string { + const filePath = path.join(this.rootDir, p); + try { + return fs.readFileSync(filePath).toString('utf-8'); + } + catch (e) { + throw new Error(`Cannot find a module ${filePath} in ${this.name}`); + } + } + + writeCFile(srcPath: RelativePath, data: string) { + const parsed = path.parse(srcPath); + const cRelativePath = path.join(parsed.dir, `bs_${parsed.name}.c`); + const filePath = path.join(this.resolvedDistDir, cRelativePath); + const cDir = path.dirname(filePath); + fs.mkdirSync(cDir, { recursive: true }); + fs.writeFileSync(filePath, data); + } + + writeMakefile(data: string) { + const filePath = path.join(this.resolvedDistDir, 'Makefile'); + fs.writeFileSync(filePath, data); + return filePath; + } + + // Removes the transpiler-generated C files (bs_*.c) from the dist dir. + // Used for incremental (REPL) builds so that each fragment is compiled into + // a shared library containing only its own object, referencing symbols of + // previous fragments from their shared libraries instead of statically + // redefining them. Native (user-provided) C files are left untouched. + removeGeneratedCFiles() { + if (!fs.existsSync(this.resolvedDistDir)) { + return; + } + const generatedCFilePattern = /^bs_.*\.c$/; + this.walkFiles(this.resolvedDistDir, (name, fullPath) => { + if (generatedCFilePattern.test(name)) { + fs.rmSync(fullPath, { force: true }); + } + }, [this.resolvedBuildDir]); + } + + protected walkFiles(dir: string, handler: (name: string, fullPath: string) => void, ignorDirs?: string[]) { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + if (ignorDirs?.includes(fullPath)) { + continue; + } + this.walkFiles(fullPath, handler, ignorDirs); + } else if (entry.isFile()) { + handler(entry.name, fullPath); + } + } + } + + protected toObjectFile(cFileInDist: AbsolutePath): AbsolutePath { + const source = cFileInDist; + const dist = this.resolvedDistDir; + const prefix = dist.endsWith(path.sep) ? dist : dist + path.sep; + if (!source.startsWith(prefix) || !source.endsWith(".c")) { + throw new Error(`Invalid dist source: ${source}`); + } + const relative = source.slice(prefix.length, -2); // remove ".c" + return path.join(this.resolvedBuildDir, `${relative}.o`); + } + + protected replacePrefix(fromDir: AbsolutePath, toDir: AbsolutePath, filePath: AbsolutePath): string { + if (filePath === fromDir) return toDir; + const prefix = filePath.endsWith(path.sep) ? fromDir : fromDir + path.sep; + if (!filePath.startsWith(prefix)) { + throw new Error(`Path ${filePath} is not under ${fromDir}`); + } + return toDir + "/" + filePath.slice(prefix.length); + } +} + +export class PackageForEsp32 extends Package { + public readonly espIdfComponents: string[]; + + constructor( + name: string, + path: { + rootDir: AbsolutePath, + entry: RelativePath, + sourceDir: RelativePath, + distDir: RelativePath, + buildDir: RelativePath, + packageDir: RelativePath, + }, + dependencies: string[], + espIdfComponents: string[], + ) { + super(name, path, dependencies); + this.espIdfComponents = espIdfComponents; + } + + get elfFile(): AbsolutePath { + return path.join(this.resolvedBuildDir, `${this.name}.elf`); + } + + writeLinkerScript(data: string) { + const filePath = path.join(this.resolvedBuildDir, "linkerscript.ld"); + fs.writeFileSync(filePath, data); + return filePath; + } +} + +export class PackageForHostUnix extends Package { + soFile(id?: number): AbsolutePath { + return path.join( + this.resolvedBuildDir, + `${this.name}${id ?? ''}.so` + ); + } +} + +export class PackageForHostWindows extends Package { + dllFile(id?: number): AbsolutePath { + return path.join( + this.resolvedBuildDir, + `${this.name}${id ?? ''}.dll` + ); + } +} + diff --git a/lang/src/compiler/project.ts b/lang/src/compiler/project.ts index 78224c56..3dd63447 100644 --- a/lang/src/compiler/project.ts +++ b/lang/src/compiler/project.ts @@ -1,70 +1,6 @@ -import * as fs from "fs"; -import * as path from "path"; +import { Package } from "./package"; -export type RelativePath = string; -export type AbsolutePath = string; - -export class Package { - public readonly name: string; - public readonly rootDir: AbsolutePath; - public readonly entry: RelativePath; - public readonly sourceDir: RelativePath; - public readonly distDir: RelativePath; - public readonly buildDir: RelativePath; - public readonly packageDir: RelativePath; - public readonly dependencies: string[]; - - public get resolvedEntry(): AbsolutePath { return path.join(this.rootDir, this.entry); } - public get resolvedSourceDir(): AbsolutePath { return path.join(this.rootDir, this.sourceDir); } - public get resolvedDistDir(): AbsolutePath { return path.join(this.rootDir, this.distDir); } - public get resolvedBuildDir(): AbsolutePath { return path.join(this.rootDir, this.buildDir); } - public get resolvedPackageDir(): AbsolutePath { return path.join(this.rootDir, this.packageDir); } - - constructor( - name: string, - path: { - rootDir: AbsolutePath, - entry: RelativePath, - sourceDir: RelativePath, - distDir: RelativePath, - buildDir: RelativePath, - packageDir: RelativePath, - }, - dependencies: string[], - ) { - this.name = name; - this.dependencies = dependencies; - this.rootDir = path.rootDir; - this.sourceDir = path.sourceDir; - this.entry = path.entry; - this.distDir = path.distDir; - this.buildDir = path.buildDir; - this.packageDir = path.packageDir; - } -} - -export class PackageForEsp32 extends Package { - public readonly espIdfComponents: string[]; - - constructor( - name: string, - path: { - rootDir: AbsolutePath, - entry: RelativePath, - sourceDir: RelativePath, - distDir: RelativePath, - buildDir: RelativePath, - packageDir: RelativePath, - }, - dependencies: string[], - espIdfComponents: string[], - ) { - super(name, path, dependencies); - this.espIdfComponents = espIdfComponents; - } -} - export class Project

{ public readonly mainPackage: P; public readonly dependencies: Map; @@ -79,7 +15,7 @@ export class Project

{ return [...this.usedDependenciesMap.values()]; } - protected static loadHelper

( + static load

( mainPackageName: string, packageReader: (name: string) => P ) { @@ -104,58 +40,15 @@ export class Project

{ return new Project

(mainPackage, dependencies); } - check() { - const invalidFilePattern = /^\d+\.bs$/; - if (!fs.existsSync(this.mainPackage.sourceDir)) { - return; - } - const files = fs.readdirSync(this.mainPackage.resolvedSourceDir); - - for (const file of files) { - if (invalidFilePattern.test(file)) { - const fullPath = path.join(this.mainPackage.resolvedSourceDir, file); - throw new Error( - `Invalid file name: ${fullPath}\n` + - `BlueScript source file names cannot consist solely of digits.` - ); - } - } - } - - clean() { - this.cleanDistDir(this.mainPackage); + clean() { + this.mainPackage.clean(); for (const dep of this.dependencies.values()) { - this.cleanDistDir(dep); - } - } - - private cleanDistDir(pkg: P): void { - fs.rmSync(pkg.resolvedDistDir, { recursive: true, force: true }); - } - - readSourceFile(pkg: P, relativePath: RelativePath): string { - const filePath = path.join(pkg.rootDir, relativePath); - try { - return fs.readFileSync(filePath).toString('utf-8'); + dep.clean(); } - catch (e) { - throw new Error(`Cannot find a module ${filePath} in ${pkg.name}`); - } - } - - writeCFile(pkg: P, relativeSourceFilePath: RelativePath, data: string) { - const parsed = path.parse(relativeSourceFilePath); - const cRelativePath = path.join(parsed.dir, `bs_${parsed.name}.c`); - const filePath = path.join(pkg.resolvedDistDir, cRelativePath); - const cDir = path.dirname(filePath); - fs.mkdirSync(cDir, { recursive: true }); - fs.writeFileSync(filePath, data); } - writeMakefile(pkg: P, data: string) { - const filePath = path.join(pkg.resolvedDistDir, 'Makefile'); - fs.writeFileSync(filePath, data); - return filePath; + check() { + this.mainPackage.check(); } addUsedDependency(pkg: P) { @@ -163,58 +56,4 @@ export class Project

{ this.usedDependenciesMap.set(pkg.name, pkg); } } - - archiveFile(pkg: Package): AbsolutePath { - return path.join(pkg.resolvedBuildDir, `lib${pkg.name}.a`); - } -} - - -export class ProjectForEsp32 extends Project { - private constructor(mainPackage: PackageForEsp32, dependencies: Map) { - super(mainPackage, dependencies); - } - - public static load( - mainPackageName: string, - packageReader: (name: string) => PackageForEsp32, - ): ProjectForEsp32 { - const project = Project.loadHelper(mainPackageName, packageReader); - return new ProjectForEsp32(project.mainPackage, project.dependencies); - } - - writeLinkerScript(data: string) { - const filePath = path.join(this.mainPackage.resolvedBuildDir, "linkerscript.ld"); - fs.writeFileSync(filePath, data); - return filePath; - } - - elfFile(): AbsolutePath { - return path.join( - this.mainPackage.resolvedBuildDir, - `${this.mainPackage.name}.elf` - ); - } } - - -export class ProjectForHost extends Project { - private constructor(mainPackage: Package, dependencies: Map) { - super(mainPackage, dependencies); - } - - public static load( - mainPackageName: string, - packageReader: (name: string) => Package, - ): ProjectForHost { - const project = Project.loadHelper(mainPackageName, packageReader); - return new ProjectForHost(project.mainPackage, project.dependencies); - } - - soFile(id?: number): AbsolutePath { - return path.join( - this.mainPackage.resolvedBuildDir, - `${this.mainPackage.name}${id ?? ''}.so` - ); - } -} \ No newline at end of file diff --git a/lang/src/compiler/transpiler-session.ts b/lang/src/compiler/transpiler-session.ts index 02eb1bfa..502a3bac 100644 --- a/lang/src/compiler/transpiler-session.ts +++ b/lang/src/compiler/transpiler-session.ts @@ -2,28 +2,29 @@ import { GlobalVariableNameTable } from "../transpiler/code-generator/variables" import { transpile } from "../transpiler/code-generator/code-generator"; import * as fs from "fs"; import * as path from "path"; -import { AbsolutePath, RelativePath, Package, Project } from "./project"; +import { Package } from "./package"; +import { Project } from "./project"; class PathInPkg { public pkg: Package; - public relativePath: RelativePath; - public absolutePath: AbsolutePath; + public relativePath: string; + public absolutePath: string; - constructor(pkg: Package, relativePath: RelativePath) { + constructor(pkg: Package, relativePath: string) { this.pkg = pkg; this.checkRelativePath(relativePath); this.relativePath = relativePath; this.absolutePath = path.join(pkg.rootDir, this.relativePath); } - public resolve(targetRelativePath: RelativePath): PathInPkg { + public resolve(targetRelativePath: string): PathInPkg { const currentDir = path.dirname(this.relativePath); const resolvedRelativePath = path.join(currentDir, targetRelativePath); return new PathInPkg(this.pkg, resolvedRelativePath); } - private checkRelativePath(relativePath: RelativePath) { + private checkRelativePath(relativePath: string) { // check if the relative path is under the source dir const absolutePath = path.join(this.pkg.rootDir, relativePath); if (!absolutePath.startsWith(this.pkg.resolvedSourceDir)) { @@ -49,7 +50,7 @@ export class TranspilerSession { public transpile(project: Project): string[] { let entryPath: PathInPkg = new PathInPkg(project.mainPackage, project.mainPackage.entry); - const src = project.readSourceFile(entryPath.pkg, entryPath.relativePath); + const src = entryPath.pkg.readSourceFile(entryPath.relativePath); return this.transpileHelper(project, entryPath, src); } @@ -67,7 +68,7 @@ export class TranspilerSession { this.globalNames, this.makeImporter(entryPath, entryPoints, project) ); - project.writeCFile(entryPath.pkg, entryPath.relativePath, this.cProlog + result.code); + entryPath.pkg.writeCFile(entryPath.relativePath, this.cProlog + result.code); this.globalNames = result.names; entryPoints.push(result.main); return entryPoints; @@ -81,7 +82,7 @@ export class TranspilerSession { if (mod) return mod; else { - const src = project.readSourceFile(newPath.pkg, newPath.relativePath); + const src = newPath.pkg.readSourceFile(newPath.relativePath); const result = transpile( this.sessionId++, src, @@ -91,7 +92,7 @@ export class TranspilerSession { ); this.modules.set(newPath.absolutePath, result.names); entryPoints.push(result.main); - project.writeCFile(newPath.pkg, newPath.relativePath, this.cProlog + result.code); + newPath.pkg.writeCFile(newPath.relativePath, this.cProlog + result.code); return result.names; } } diff --git a/lang/src/index.ts b/lang/src/index.ts index c4e5ad9f..89a76b5d 100644 --- a/lang/src/index.ts +++ b/lang/src/index.ts @@ -1,6 +1,7 @@ export { ErrorLog as CompileError } from './transpiler/utils'; export { CompilerSession } from './compiler/compiler-session'; -export { Package, PackageForEsp32, Project, ProjectForEsp32, ProjectForHost } from './compiler/project'; +export { Project } from './compiler/project'; +export { Package, PackageForEsp32, PackageForHostUnix, PackageForHostWindows } from './compiler/package'; export { Esp32Toolchain, Esp32ToolchainConfig } from './compiler/board-toolchain/esp32-toolchain'; -export { HostToolchain } from './compiler/board-toolchain/host-toolchain'; -export { MemoryLayout, MemoryImage, CompileOutput, SharedObject } from './compiler/board-toolchain/board-toolchain'; \ No newline at end of file +export { HostToolchain, HostToolchainConfig, HostUnixToolchain, HostWindowsToolchain } from './compiler/board-toolchain/host-toolchain'; +export { MemoryLayout, MemoryImage, CompileOutput, SharedLibrary } from './compiler/board-toolchain/board-toolchain'; \ No newline at end of file diff --git a/lang/tests/compiler/__snapshots__/makefile.test.ts.snap b/lang/tests/compiler/__snapshots__/makefile.test.ts.snap deleted file mode 100644 index 31635675..00000000 --- a/lang/tests/compiler/__snapshots__/makefile.test.ts.snap +++ /dev/null @@ -1,177 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`generateMakefile generates makefile for esp32 archive file. 1`] = ` -"# === Basic settings === -CC := /opt/esp-toolchain/xtensa-esp32-elf-gcc -AR := /opt/esp-toolchain/xtensa-esp32-elf-ar - -# === Directory settings === -SRC_DIR := /project/myapp/src -DIST_DIR := /project/myapp/dist -BUILD_DIR := /project/myapp/dist/build -PACKAGES_DIR := /project/myapp/packages - -TARGET := /project/myapp/dist/build/libmyapp.a - -# === Check for illegal file name prefixes === -ILLEGAL_PREFIX_FILES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "bs_*.c" -print) -ifneq ($(ILLEGAL_PREFIX_FILES),) - $(error ERROR: You cannot use 'bs_' prefix for C source file names. Please remove or rename the following files: \\ - $(ILLEGAL_PREFIX_FILES)) -endif - -# === Source and object file settings === -ORIG_SOURCES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.c" -print) -ORIG_HEADERS := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.h" -print) -DIST_SOURCES := $(foreach src,$(ORIG_SOURCES), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)) \\ -) -DIST_HEADERS := $(foreach hdr,$(ORIG_HEADERS), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)) \\ -) -DIST_SOURCES += $(shell find $(DIST_DIR) -path $(BUILD_DIR) -prune -o -type f -name "bs_*.c" -print) -OBJECTS := $(patsubst $(DIST_DIR)/%.c, $(BUILD_DIR)/%.o, $(DIST_SOURCES)) - -# === Compilation settings === -INCLUDES := -I $(DIST_DIR) -I $(SRC_DIR) -I /opt/esp-idf/components/freertos/include -I /opt/esp-idf/components/esp_common/include -CFLAGS := $(INCLUDES) -O2 -w -fno-common -ffunction-sections -fdata-sections -mtext-section-literals -mlongcalls -fno-zero-initialized-in-bss - -# ==================================================================== -.PHONY: all - -all: $(TARGET) - -# Copy rules -# -------------------------------------------------------- - -define COPY_RULE_TEMPLATE -$(1): $(2) - @echo "Copying $$< to $$@" - @mkdir -p $$(dir $$@) - @cp $$< $$@ -endef - -$(foreach src,$(ORIG_SOURCES), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)), \\ - $(src) \\ - )) \\ -) - -$(foreach hdr,$(ORIG_HEADERS), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)), \\ - $(hdr) \\ - )) \\ -) - -# Build rules -# -------------------------------------------------------- - -$(TARGET): $(OBJECTS) | $(DIST_HEADERS) - @echo "Archiving library: $@" - @mkdir -p $(@D) - $(AR) rcs $@ $^ - -vpath %.c $(DIST_DIR) - -$(BUILD_DIR)/%.o: $(DIST_DIR)/%.c - @echo "Compiling: $< -> $@" - @mkdir -p $(@D) - $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ - --include $(wildcard $(BUILD_DIR)/*.d) - -.PHONY: clean -clean: - @echo "Cleaning dist directory..." - @rm -rf $(DIST_DIR)" -`; - -exports[`generateMakefile generates makefile for host shared library. 1`] = ` -"# === Basic settings === -CC := cc -AR := ar - -# === Directory settings === -SRC_DIR := /project/myapp/src -DIST_DIR := /project/myapp/dist -BUILD_DIR := /project/myapp/dist/build -PACKAGES_DIR := /project/myapp/packages - -TARGET := /project/myapp/dist/build/libmyapp.a - -# === Check for illegal file name prefixes === -ILLEGAL_PREFIX_FILES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "bs_*.c" -print) -ifneq ($(ILLEGAL_PREFIX_FILES),) - $(error ERROR: You cannot use 'bs_' prefix for C source file names. Please remove or rename the following files: \\ - $(ILLEGAL_PREFIX_FILES)) -endif - -# === Source and object file settings === -ORIG_SOURCES := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.c" -print) -ORIG_HEADERS := $(shell find $(SRC_DIR) -path $(DIST_DIR) -prune -o -path $(PACKAGES_DIR) -prune -o -type f -name "*.h" -print) -DIST_SOURCES := $(foreach src,$(ORIG_SOURCES), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)) \\ -) -DIST_HEADERS := $(foreach hdr,$(ORIG_HEADERS), \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)) \\ -) -DIST_SOURCES += $(shell find $(DIST_DIR) -path $(BUILD_DIR) -prune -o -type f -name "bs_*.c" -print) -OBJECTS := $(patsubst $(DIST_DIR)/%.c, $(BUILD_DIR)/%.o, $(DIST_SOURCES)) - -# === Compilation settings === -INCLUDES := -I $(DIST_DIR) -I $(SRC_DIR) -CFLAGS := $(INCLUDES) -O2 -w -fPIC -DLINUX64 - -# ==================================================================== -.PHONY: all - -all: $(TARGET) - -# Copy rules -# -------------------------------------------------------- - -define COPY_RULE_TEMPLATE -$(1): $(2) - @echo "Copying $$< to $$@" - @mkdir -p $$(dir $$@) - @cp $$< $$@ -endef - -$(foreach src,$(ORIG_SOURCES), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(src)), \\ - $(src) \\ - )) \\ -) - -$(foreach hdr,$(ORIG_HEADERS), \\ - $(eval $(call COPY_RULE_TEMPLATE, \\ - $(subst $(SRC_DIR),$(DIST_DIR), $(hdr)), \\ - $(hdr) \\ - )) \\ -) - -# Build rules -# -------------------------------------------------------- - -$(TARGET): $(OBJECTS) | $(DIST_HEADERS) - @echo "Archiving library: $@" - @mkdir -p $(@D) - $(AR) rcs $@ $^ - -vpath %.c $(DIST_DIR) - -$(BUILD_DIR)/%.o: $(DIST_DIR)/%.c - @echo "Compiling: $< -> $@" - @mkdir -p $(@D) - $(CC) $(CFLAGS) -MMD -MP -c $< -o $@ - --include $(wildcard $(BUILD_DIR)/*.d) - -.PHONY: clean -clean: - @echo "Cleaning dist directory..." - @rm -rf $(DIST_DIR)" -`; diff --git a/lang/tests/compiler/compiler-esp32.test.ts b/lang/tests/compiler/compiler-esp32.test.ts index c7dd6929..9386b137 100644 --- a/lang/tests/compiler/compiler-esp32.test.ts +++ b/lang/tests/compiler/compiler-esp32.test.ts @@ -1,8 +1,10 @@ import * as fs from 'fs'; import * as path from 'path'; -import { getEsp32CompilerConfig, Esp32CompilerTestEnv } from './test-utils'; +import { Esp32CompilerTestEnv } from './test-env'; +import { getEsp32ToolchainConfig } from './test-utils-esp32'; import { CompilerSession } from '../../src/compiler/compiler-session'; -import { ProjectForEsp32 } from '../../src/compiler/project'; +import { Project } from '../../src/compiler/project'; +import { PackageForEsp32 } from '../../src/compiler/package'; import { Esp32Toolchain } from '../../src/compiler/board-toolchain/esp32-toolchain'; import { MemoryImage } from '../../src/compiler/board-toolchain/board-toolchain'; @@ -12,15 +14,15 @@ const memoryLayout = { iflash: { address: 0x40150000, size: 10000 }, dflash: { address: 0x3f43d000, size: 10000 }, } -const compilerConfig = getEsp32CompilerConfig(); +const toolchainConfig = getEsp32ToolchainConfig(); const compile = async (testEnv: Esp32CompilerTestEnv) => { - const project = ProjectForEsp32.load( + const project = Project.load( testEnv.mainPackageName, testEnv.getPackageReader() ); - const toolchain = new Esp32Toolchain(compilerConfig, memoryLayout); - const session = new CompilerSession(toolchain); + const toolchain = new Esp32Toolchain(toolchainConfig, memoryLayout); + const session = new CompilerSession(toolchain); await session.buildProject(project); return session; } @@ -97,7 +99,8 @@ describe('Test single compile: Compiler for ESP32', () => { it('should throw error if an imported module is imported with absolute path.', async () => { testEnv.createMainPackage(); testEnv.addSourceFile(testEnv.mainPackageName, './module1.bs', `export function add(a: integer, b:integer) {return a + b}`); - testEnv.addSourceFile(testEnv.mainPackageName, '/index.bs', `import {add} from '${testEnv.getSourceFilePath(testEnv.mainPackageName, './module1.bs')}';\nadd(1, 2);`); + const absPath = testEnv.getSourceFilePath(testEnv.mainPackageName, './module1.bs').replace(/\\/g, '/'); + testEnv.addSourceFile(testEnv.mainPackageName, '/index.bs', `import {add} from '${absPath}';\nadd(1, 2);`); await expect(compile(testEnv)).rejects.toThrow(`This module system does not support importing from absolute paths.`); }); diff --git a/lang/tests/compiler/compiler-host.test.ts b/lang/tests/compiler/compiler-host.test.ts index 980afa91..6daa5b29 100644 --- a/lang/tests/compiler/compiler-host.test.ts +++ b/lang/tests/compiler/compiler-host.test.ts @@ -1,39 +1,10 @@ import * as path from "path"; import * as fs from "fs"; -import { ProjectForHost } from "../../src/compiler/project"; -import { HostToolchain } from "../../src/compiler/board-toolchain/host-toolchain"; -import { HostCompilerTestEnv, runtimeDir } from "./test-utils"; -import { SharedObject } from "../../src/compiler/board-toolchain/board-toolchain"; -import { CompilerSession } from "../../src/compiler/compiler-session"; -import { executeCommand } from "../../src/compiler/utils"; - -const runtimeBuildDir = path.join(runtimeDir, 'ports/host/build'); -const builtinModuleC = path.join(runtimeDir, 'ports/host/std-module.c'); -const shellC = path.join(runtimeDir, 'ports/host/shell.c'); -const executableShell = path.join(runtimeBuildDir, 'shell'); -const runtimeSo = path.join(runtimeBuildDir, 'c-runtime.so'); -const runtimeC = path.join(runtimeDir, 'core/src/c-runtime.c'); - -const buildRuntime = async () => { - fs.mkdirSync(runtimeBuildDir, { recursive: true }); - await executeCommand('cc', ["-DLINUX64", "-O2", "-shared", "-fPIC", "-o", runtimeSo, runtimeC, builtinModuleC]); - await executeCommand('cc', ["-DLINUX64", "-O2", "-o", executableShell, shellC, runtimeSo, "-lm", "-ldl"]); -} - -const compile = async (testEnv: HostCompilerTestEnv) => { - const project = ProjectForHost.load( - testEnv.mainPackageName, - testEnv.getPackageReader() - ); - const toolchain = new HostToolchain(runtimeDir); - const session = new CompilerSession(toolchain); - await session.buildProject(project); - return session; -} +import { buildRuntime, createTestEnv, compile } from "./test-utils-host"; describe('Test single compile: Compiler for Host', () => { - const testEnv = new HostCompilerTestEnv('compiler-test-host'); + const testEnv = createTestEnv(); beforeAll(async () => { await buildRuntime(); @@ -44,7 +15,7 @@ describe('Test single compile: Compiler for Host', () => { }); afterAll(() => { - // testEnv.delete(); + testEnv.delete(); }); @@ -53,7 +24,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', '1 + 1'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should throw error if index.bs does not exist.', async () => { @@ -67,7 +38,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', 'print("hello world")'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with std object.', async () => { @@ -75,7 +46,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', 'console.log("hello world")'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('can change source directory.', async () => { @@ -83,7 +54,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './src/index.bs', 'print("hello world")'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('can change entry file.', async () => { @@ -91,7 +62,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './src/main.bs', 'print("hello world")'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a module import.', async () => { @@ -102,7 +73,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from './module1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should throw error if an imported module does not exist.', async () => { @@ -115,7 +86,8 @@ describe('Test single compile: Compiler for Host', () => { it('should throw error if an imported module is imported with absolute path.', async () => { testEnv.createMainPackage(); testEnv.addSourceFile(testEnv.mainPackageName, './module1.bs', `export function add(a: integer, b:integer) {return a + b}`); - testEnv.addSourceFile(testEnv.mainPackageName, '/index.bs', `import {add} from '${testEnv.getSourceFilePath(testEnv.mainPackageName, './module1.bs')}';\nadd(1, 2);`); + const absPath = testEnv.getSourceFilePath(testEnv.mainPackageName, './module1.bs').replace(/\\/g, '/'); + testEnv.addSourceFile(testEnv.mainPackageName, '/index.bs', `import {add} from '${absPath}';\nadd(1, 2);`); await expect(compile(testEnv)).rejects.toThrow(`This module system does not support importing from absolute paths.`); }); @@ -143,7 +115,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {addMul} from './module1';\naddMul(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with module imports from dir.', async () => { @@ -161,7 +133,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {addMul} from './dir/module1';\naddMul(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a package import.', async () => { @@ -173,7 +145,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from 'package1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a package import 2.', async () => { @@ -185,7 +157,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from 'package1/module1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a package import from different source directory.', async () => { @@ -195,7 +167,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from 'package1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a package import from different source directory and different entry file.', async () => { @@ -205,7 +177,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from 'package1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with an unused package.', async () => { @@ -215,7 +187,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `1 + 1;`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with a package import 2.', async () => { @@ -227,7 +199,7 @@ describe('Test single compile: Compiler for Host', () => { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {add} from 'package1/module1';\nadd(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs with some package imports.', async () => { @@ -248,7 +220,7 @@ mul(1, 2);` ); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should throw error if an imported package does not exist.', async () => { @@ -275,7 +247,7 @@ mul(1, 2);` testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {addMul} from 'package1';\naddMul(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs which imports a package with a module import.', async () => { @@ -291,7 +263,7 @@ mul(1, 2);` testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', `import {addMul} from 'package1';\naddMul(1, 2);`); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs which imports a package and a module.', async () => { @@ -313,7 +285,7 @@ add(1, 2); // mul(1,2); `); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should treat a class imported via different routes as the same class.', async () => { @@ -340,7 +312,7 @@ getShapeArea(shape); ` ); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs which includes c file.', async () => { @@ -353,7 +325,7 @@ function foo() { `); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs which includes custom c file.', async () => { @@ -367,7 +339,7 @@ function foo() { `); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); it('should compile index.bs which includes custom header file.', async () => { @@ -382,7 +354,7 @@ function foo() { `); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); expect(fs.existsSync(path.join(testEnv.root, 'dist/add.h'))).toBe(true); }); @@ -394,7 +366,7 @@ function foo() { } `); - await expect(compile(testEnv)).rejects.toThrow(`do not support implicit function declarations`); + await expect(compile(testEnv)).rejects.toThrow(/implicit/i); }); it('should compile index.bs again after editing file.', async () => { @@ -402,19 +374,19 @@ function foo() { testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', '1 + 1'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); testEnv.addSourceFile(testEnv.mainPackageName, './index.bs', '1 + 3'); await compile(testEnv); - expect(testEnv.resultSharedObjectExists()).toBe(true); + expect(testEnv.resultSharedLibraryExists()).toBe(true); }); }); describe('Test additional compile: Compiler for ESP32', () => { - const testEnv = new HostCompilerTestEnv('compiler-test-host'); + const testEnv = createTestEnv(); beforeEach(() => { testEnv.init(); diff --git a/lang/tests/compiler/makefile.test.ts b/lang/tests/compiler/makefile.test.ts deleted file mode 100644 index beac149c..00000000 --- a/lang/tests/compiler/makefile.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { describe, expect, test } from '@jest/globals'; -import { generateMakefile, esp32MakefilePreset, hostMakefilePreset } from '../../src/compiler/board-toolchain/tools/makefile'; -import { Package } from '../../src/compiler/project'; - -function createTestPackage(name = 'myapp', rootDir = '/project/myapp'): Package { - return new Package( - name, - { - rootDir, - entry: './src/index.bs', - sourceDir: './src', - distDir: './dist', - buildDir: './dist/build', - packageDir: './packages', - }, - [], - ); -} - -describe('generateMakefile', () => { - test('generates makefile for esp32 archive file.', () => { - const pkg = createTestPackage(); - const makefile = generateMakefile(esp32MakefilePreset( - '/opt/esp-toolchain', - pkg, - [ - '/opt/esp-idf/components/freertos/include', - '/opt/esp-idf/components/esp_common/include', - ], - '/project/myapp/dist/build/libmyapp.a', - )); - expect(makefile).toMatchSnapshot(); - }); - - test('generates makefile for host shared library.', () => { - const pkg = createTestPackage(); - const makefile = generateMakefile(hostMakefilePreset( - pkg, - '/project/myapp/dist/build/libmyapp.a', - )); - expect(makefile).toMatchSnapshot(); - }); -}); diff --git a/lang/tests/compiler/test-utils.ts b/lang/tests/compiler/test-env.ts similarity index 66% rename from lang/tests/compiler/test-utils.ts rename to lang/tests/compiler/test-env.ts index b71470f9..46f4236b 100644 --- a/lang/tests/compiler/test-utils.ts +++ b/lang/tests/compiler/test-env.ts @@ -1,21 +1,9 @@ -import { execSync } from "child_process"; -import { Esp32ToolchainConfig } from "../../src/compiler/board-toolchain/esp32-toolchain"; -import { Package, PackageForEsp32 } from "../../src/compiler/project"; +import { Package, PackageForEsp32, PackageForHostUnix, PackageForHostWindows } from "../../src/compiler/package"; import * as fs from 'fs'; import * as path from 'path'; -import * as os from 'os'; export const runtimeDir = path.resolve(__dirname, '../../../microcontroller'); -export function getEsp32CompilerConfig(): Esp32ToolchainConfig { - const gccPath = execSync('source ~/esp/esp-idf/export.sh &> /dev/null && which xtensa-esp32-elf-gcc').toString(); - const espDir = path.join(os.homedir(), 'esp'); - return { - runtimeDir: runtimeDir, - compilerToolchainDir: path.resolve(gccPath, '../'), - espDir - } -} class CompilerTestEnv

{ readonly root: string; @@ -110,9 +98,55 @@ export class Esp32CompilerTestEnv extends CompilerTestEnv { } } -export class HostCompilerTestEnv extends CompilerTestEnv { + +export class HostUnixCompilerTestEnv extends CompilerTestEnv { + public createMainPackage(dependencies: string[] = [], srcDir: string = ".", entryFile?: string): void { + const pkg = new PackageForHostUnix( + this.mainPackageName, + { + rootDir: this.root, + entry: entryFile ?? path.join(srcDir, 'index.bs'), + sourceDir: srcDir, + distDir: "./dist", + buildDir: "./dist/build", + packageDir: "./packages", + }, + dependencies, + ); + this.addPackage(pkg); + } + + public createSubPackage(name: string, dependencies: string[] = [], srcDir: string = ".", entryFile?: string): void { + const root = path.join(this.root, 'packages', name); + fs.mkdirSync(root, {recursive: true}); + const pkg = new PackageForHostUnix( + name, + { + rootDir: root, + entry: entryFile ?? path.join(srcDir, 'index.bs'), + sourceDir: srcDir, + distDir: "./dist", + buildDir: "./dist/build", + packageDir: "./packages", + }, + dependencies, + ) + this.addPackage(pkg); + } + + public resultSharedLibraryExists() { + const buildDir = path.join(this.root, 'dist/build/'); + const pattern = new RegExp(`^${this.mainPackageName}\\d+\\.so$`); + for (const name of fs.readdirSync(buildDir)) { + if(pattern.test(name) ) { return true; } + } + return false; + } +} + +export class HostWindowsCompilerTestEnv extends CompilerTestEnv { public createMainPackage(dependencies: string[] = [], srcDir: string = ".", entryFile?: string): void { - const pkg = new Package( + const pkg = new PackageForHostWindows( this.mainPackageName, { rootDir: this.root, @@ -130,7 +164,7 @@ export class HostCompilerTestEnv extends CompilerTestEnv { public createSubPackage(name: string, dependencies: string[] = [], srcDir: string = ".", entryFile?: string): void { const root = path.join(this.root, 'packages', name); fs.mkdirSync(root, {recursive: true}); - const pkg = new Package( + const pkg = new PackageForHostWindows( name, { rootDir: root, @@ -145,8 +179,12 @@ export class HostCompilerTestEnv extends CompilerTestEnv { this.addPackage(pkg); } - public resultSharedObjectExists() { - const soPath = path.join(this.root, `dist/build/${this.mainPackageName}.so`); - return fs.existsSync(soPath); + public resultSharedLibraryExists() { + const buildDir = path.join(this.root, 'dist/build/'); + const pattern = new RegExp(`^${this.mainPackageName}\\d+\\.dll$`); + for (const name of fs.readdirSync(buildDir)) { + if(pattern.test(name) ) { return true; } + } + return false; } } \ No newline at end of file diff --git a/lang/tests/compiler/test-utils-esp32.ts b/lang/tests/compiler/test-utils-esp32.ts new file mode 100644 index 00000000..98978465 --- /dev/null +++ b/lang/tests/compiler/test-utils-esp32.ts @@ -0,0 +1,85 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { execFileSync } from "child_process"; +import { Esp32ToolchainConfig } from "../../src/compiler/board-toolchain/esp32-toolchain"; + + +const XTENSA_TOOLCHAIN_DIR = 'xtensa-esp-elf'; +const XTENSA_GCC_NAME = 'xtensa-esp32-elf-gcc'; +const XTENSA_AR_NAME = 'xtensa-esp32-elf-ar'; +const XTENSA_LD_NAME = 'xtensa-esp32-elf-ld'; + +export const runtimeDir = path.resolve(__dirname, '../../../microcontroller'); + +export function getEsp32ToolchainConfig(): Esp32ToolchainConfig { + const osType = os.platform(); + if (osType === 'darwin') { + const espDir = path.join(os.homedir(), 'esp'); + const idfToolsPy = path.join(espDir, 'esp-idf', 'tools', 'idf_tools.py'); + const stdout = runIdfToolsExport(idfToolsPy); + const compilerToolchainDir = findXtensaGccDirFromIdfExport(stdout, ':', XTENSA_GCC_NAME); + return { + runtimeDir, + compilerToolchain: { + gcc: path.join(compilerToolchainDir, XTENSA_GCC_NAME), + ar: path.join(compilerToolchainDir, XTENSA_AR_NAME), + ld: path.join(compilerToolchainDir, XTENSA_LD_NAME), + make: 'make' + }, + espDir + }; + } else if (osType === 'win32') { + const espDir = path.join(os.homedir(), 'esp'); + const idfToolsPy = path.join(espDir, 'esp-idf', 'tools', 'idf_tools.py'); + const stdout = runIdfToolsExport(idfToolsPy); + const compilerToolchainDir = findXtensaGccDirFromIdfExport( + stdout, + ';', + `${XTENSA_GCC_NAME}.exe`, + ); + return { + runtimeDir, + compilerToolchain: { + gcc: path.join(compilerToolchainDir, `${XTENSA_GCC_NAME}.exe`), + ar: path.join(compilerToolchainDir, `${XTENSA_AR_NAME}.exe`), + ld: path.join(compilerToolchainDir, `${XTENSA_LD_NAME}.exe`), + make: 'mingw32-make' + }, + espDir + }; + } else { + throw new Error('Unsupported OS.'); + } + +} + + +function findXtensaGccDirFromIdfExport( + stdout: string, + pathSeparator: string, + gccFileName: string, +): string { + for (const line of stdout.trim().split('\n')) { + if (!line || line.startsWith('ERROR:') || line.startsWith('WARNING:')) continue; + const eq = line.indexOf('='); + if (eq === -1) continue; + + const key = line.slice(0, eq); + const value = line.slice(eq + 1); + if (key !== 'PATH') continue; + + for (const entry of value.split(pathSeparator).map((p) => p.trim()).filter(Boolean)) { + if (entry.includes(XTENSA_TOOLCHAIN_DIR) && fs.existsSync(path.join(entry, gccFileName))) { + return entry; + } + } + } + throw new Error(`${XTENSA_TOOLCHAIN_DIR} not found in idf_tools.py export output`); +} + +function runIdfToolsExport(idfToolsPy: string): string { + return execFileSync('python', [idfToolsPy, 'export', '--format', 'key-value'], { + encoding: 'utf8', + }); +} \ No newline at end of file diff --git a/lang/tests/compiler/test-utils-host.ts b/lang/tests/compiler/test-utils-host.ts new file mode 100644 index 00000000..b779783f --- /dev/null +++ b/lang/tests/compiler/test-utils-host.ts @@ -0,0 +1,115 @@ +import * as path from "path"; +import * as fs from "fs"; +import * as os from 'os'; +import { Project } from "../../src/compiler/project"; +import { PackageForHostUnix, PackageForHostWindows } from "../../src/compiler/package"; +import { HostToolchainConfig, HostUnixToolchain, HostWindowsToolchain } from "../../src/compiler/board-toolchain/host-toolchain"; +import { HostUnixCompilerTestEnv, HostWindowsCompilerTestEnv, runtimeDir } from "./test-env"; +import { SharedLibrary } from "../../src/compiler/board-toolchain/board-toolchain"; +import { CompilerSession } from "../../src/compiler/compiler-session"; +import { executeCommand } from "../../src/compiler/utils"; + + +const runtimeBuildDir = path.join(runtimeDir, 'ports/host/build'); +const builtinModuleC = path.join(runtimeDir, 'ports/host/std-module.c'); +const shellC = path.join(runtimeDir, 'ports/host/shell.c'); +const runtimeC = path.join(runtimeDir, 'core/src/c-runtime.c'); +const commC = path.join(runtimeDir, 'ports/host/comm.c'); + +// Unix (darwin) +const runtimeSo = path.join(runtimeBuildDir, 'c-runtime.so'); +const executableShell = path.join(runtimeBuildDir, 'shell'); + +// Windows +const runtimeDll = path.join(runtimeBuildDir, 'c-runtime.dll'); +const executableShellWin = path.join(runtimeBuildDir, 'shell.exe'); + +const buildRuntimeUnix = async () => { + fs.mkdirSync(runtimeBuildDir, { recursive: true }); + await executeCommand('cc', [ + '-DLINUX64', '-O2', '-shared', '-fPIC', + '-o', runtimeSo, + runtimeC, builtinModuleC, commC, + ]); + await executeCommand('cc', [ + '-DLINUX64', '-O2', + '-o', executableShell, + shellC, runtimeSo, '-lm', '-ldl', + ]); +}; + +const buildRuntimeWindows = async () => { + fs.mkdirSync(runtimeBuildDir, { recursive: true }); + await executeCommand('gcc', [ + '-DLINUX64', '-O2', '-shared', + '-o', runtimeDll, + runtimeC, builtinModuleC, commC, + ]); + await executeCommand('gcc', [ + '-DLINUX64', '-O2', + '-o', executableShellWin, + shellC, runtimeDll, '-lm', + ]); +}; + +export const buildRuntime = async () => { + if (os.platform() === 'darwin') { + await buildRuntimeUnix(); + } else if (os.platform() === 'win32') { + await buildRuntimeWindows(); + } else { + throw new Error('Unsupported OS.'); + } +}; + +export const createTestEnv = () => { + if (os.platform() === 'darwin') { + return new HostUnixCompilerTestEnv('compiler-test-host') + } else if (os.platform() === 'win32') { + return new HostWindowsCompilerTestEnv('compiler-test-host') + } else { + throw new Error('Unsupported OS.'); + } +} + +export const compile = async (testEnv: HostUnixCompilerTestEnv | HostWindowsCompilerTestEnv) => { + if (os.platform() === 'darwin') { + const compilerConfig: HostToolchainConfig = { + runtimeDir, + compilerToolchain: { + gcc: 'cc', + ar: 'ar', + make: 'make' + } + } + let toolchain = new HostUnixToolchain(compilerConfig); + const project = Project.load( + testEnv.mainPackageName, + testEnv.getPackageReader() as (name: string) => PackageForHostUnix + ); + const session = new CompilerSession(toolchain); + await session.buildProject(project); + return session; + } else if (os.platform() === 'win32') { + const compilerConfig: HostToolchainConfig = { + runtimeDir, + compilerToolchain: { + gcc: 'gcc', + ar: 'ar', + make: 'mingw32-make' + } + } + let toolchain = new HostWindowsToolchain(compilerConfig); + const project = Project.load( + testEnv.mainPackageName, + testEnv.getPackageReader() as (name: string) => PackageForHostWindows + ); + const session = new CompilerSession(toolchain); + await session.buildProject(project); + return session; + } else { + throw new Error('Unsupported OS.'); + } +} + + diff --git a/microcontroller/core/src/c-runtime.c b/microcontroller/core/src/c-runtime.c index a1f0bb7d..dc55c969 100644 --- a/microcontroller/core/src/c-runtime.c +++ b/microcontroller/core/src/c-runtime.c @@ -43,6 +43,16 @@ static const void* pointer_table[PTR_TABLE_SIZE]; static int pointer_table_num = 0; +#ifdef _WIN32 +static char* stpcpy(char* dest, const char* src) { + while ((*dest = *src) != '\0') { + dest++; + src++; + } + return dest; +} +#endif + static void initialize_pointer_table() { for (int i = 0; i < PTR_TABLE_SIZE; i++) pointer_table[i] = 0; diff --git a/microcontroller/ports/esp32/main/ble.c b/microcontroller/ports/esp32/main/ble.c index 4ada41a4..91ece14a 100644 --- a/microcontroller/ports/esp32/main/ble.c +++ b/microcontroller/ports/esp32/main/ble.c @@ -28,8 +28,8 @@ ///Declare the static function static void gatts_profile_shell_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); -#define GATTS_SERVICE_UUID_SHELL 0x00FF -#define GATTS_CHAR_UUID_SHELL 0xFF01 +#define GATTS_SERVICE_UUID_SHELL 0xB500 +#define GATTS_CHAR_UUID_SHELL 0xB501 #define GATTS_DESCR_UUID_SHELL 0x3333 #define GATTS_NUM_HANDLE_SHELL 4 @@ -59,11 +59,8 @@ static uint8_t adv_config_done = 0; #define scan_rsp_config_flag (1 << 1) static uint8_t adv_service_uuid128[32] = { - /* LSB <--------------------------------------------------------------------------------> MSB */ - //first uuid, 16bit, [12],[13] is the value - 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, - //second uuid, 32bit, [12], [13], [14], [15] is the value - 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, + // BlueScript shell service (0xB500). [12],[13] is the value. + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0xB5, 0x00, 0x00, }; // The length of adv data must be less than 31 bytes @@ -79,7 +76,7 @@ static esp_ble_adv_data_t adv_data = { .p_manufacturer_data = NULL, .service_data_len = 0, .p_service_data = NULL, - .service_uuid_len = 32, + .service_uuid_len = 16, .p_service_uuid = adv_service_uuid128, .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), }; @@ -95,7 +92,7 @@ static esp_ble_adv_data_t scan_rsp_data = { .p_manufacturer_data = NULL, .service_data_len = 0, .p_service_data = NULL, - .service_uuid_len = 32, + .service_uuid_len = 16, .p_service_uuid = adv_service_uuid128, .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), }; @@ -461,6 +458,7 @@ void bs_ble_init(void) ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed\n", __func__); return; } + puts(DEVICE_NAME); ret = esp_ble_gatts_register_callback(gatts_event_handler); if (ret){ diff --git a/microcontroller/ports/esp32/sdkconfig b/microcontroller/ports/esp32/sdkconfig index 098056d1..add276d4 100644 --- a/microcontroller/ports/esp32/sdkconfig +++ b/microcontroller/ports/esp32/sdkconfig @@ -244,7 +244,7 @@ CONFIG_IDF_TOOLCHAIN_GCC=y CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32" -CONFIG_IDF_INIT_VERSION="5.4.1" +CONFIG_IDF_INIT_VERSION="5.4.4" CONFIG_IDF_TARGET_ESP32=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 @@ -337,7 +337,7 @@ CONFIG_APP_COMPILE_TIME_DATE=y # CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set # CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set # CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set -CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 # end of Application manager CONFIG_ESP_ROM_HAS_CRC_LE=y @@ -371,14 +371,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_40M=y # CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set CONFIG_ESPTOOLPY_FLASHFREQ="40m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -406,9 +406,9 @@ CONFIG_PARTITION_TABLE_MD5=y # # Compiler options # -# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y # CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set # CONFIG_COMPILER_OPTIMIZATION_NONE is not set CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set @@ -471,7 +471,7 @@ CONFIG_BT_BTC_TASK_STACK_SIZE=3072 CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y # CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1 is not set CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0 -CONFIG_BT_BTU_TASK_STACK_SIZE=4096 +CONFIG_BT_BTU_TASK_STACK_SIZE=4352 # CONFIG_BT_BLUEDROID_MEM_DEBUG is not set CONFIG_BT_BLUEDROID_ESP_COEX_VSC=y # CONFIG_BT_CLASSIC_ENABLED is not set @@ -682,7 +682,7 @@ CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT=30 CONFIG_BT_MAX_DEVICE_NAME_LEN=32 # CONFIG_BT_BLE_RPA_SUPPORTED is not set CONFIG_BT_BLE_RPA_TIMEOUT=900 -# CONFIG_BT_BLE_42_FEATURES_SUPPORTED is not set +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set # CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set # end of Bluedroid Options @@ -1283,8 +1283,15 @@ CONFIG_ESP_CONSOLE_UART=y CONFIG_ESP_CONSOLE_UART_NUM=0 CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 -# CONFIG_ESP_INT_WDT is not set -# CONFIG_ESP_TASK_WDT_EN is not set +CONFIG_ESP_INT_WDT=y +CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 +CONFIG_ESP_INT_WDT_CHECK_CPU1=y +CONFIG_ESP_TASK_WDT_EN=y +CONFIG_ESP_TASK_WDT_INIT=y +# CONFIG_ESP_TASK_WDT_PANIC is not set +CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y # CONFIG_ESP_PANIC_HANDLER_IRAM is not set # CONFIG_ESP_DEBUG_STUBS_ENABLE is not set CONFIG_ESP_DEBUG_OCDAWARE=y @@ -1369,7 +1376,7 @@ CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y -# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set +CONFIG_ESP_WIFI_GMAC_SUPPORT=y CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y # CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 @@ -1488,6 +1495,7 @@ CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 # # Port # +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y # CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set @@ -1582,7 +1590,7 @@ CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 # # Format # -CONFIG_LOG_COLORS=y +# CONFIG_LOG_COLORS is not set CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set # end of Format @@ -1630,7 +1638,7 @@ CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y # CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set -CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_DHCP_OPTIONS_LEN=69 CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 @@ -1764,8 +1772,8 @@ CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y # CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set -CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y -# CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y # CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set # end of Hooks @@ -1810,7 +1818,7 @@ CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 # CONFIG_MBEDTLS_ECP_RESTARTABLE is not set CONFIG_MBEDTLS_CMAC_C=y CONFIG_MBEDTLS_HARDWARE_AES=y -# CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER is not set +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y CONFIG_MBEDTLS_HARDWARE_MPI=y # CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set CONFIG_MBEDTLS_HARDWARE_SHA=y @@ -1895,7 +1903,7 @@ CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y CONFIG_MBEDTLS_ECP_NIST_OPTIM=y -CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set # CONFIG_MBEDTLS_POLY1305_C is not set # CONFIG_MBEDTLS_CHACHA20_C is not set # CONFIG_MBEDTLS_HKDF_C is not set @@ -2181,9 +2189,9 @@ CONFIG_LOG_BOOTLOADER_LEVEL=3 CONFIG_FLASHMODE_DIO=y # CONFIG_FLASHMODE_DOUT is not set CONFIG_MONITOR_BAUD=115200 -# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set -# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set -# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +CONFIG_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG=y +CONFIG_COMPILER_OPTIMIZATION_DEFAULT=y # CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set # CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y @@ -2205,7 +2213,7 @@ CONFIG_BTC_TASK_STACK_SIZE=3072 CONFIG_BLUEDROID_PINNED_TO_CORE_0=y # CONFIG_BLUEDROID_PINNED_TO_CORE_1 is not set CONFIG_BLUEDROID_PINNED_TO_CORE=0 -CONFIG_BTU_TASK_STACK_SIZE=4096 +CONFIG_BTU_TASK_STACK_SIZE=4352 # CONFIG_BLUEDROID_MEM_DEBUG is not set # CONFIG_CLASSIC_BT_ENABLED is not set CONFIG_GATTS_ENABLE=y @@ -2460,7 +2468,15 @@ CONFIG_CONSOLE_UART_DEFAULT=y CONFIG_CONSOLE_UART=y CONFIG_CONSOLE_UART_NUM=0 CONFIG_CONSOLE_UART_BAUDRATE=115200 -# CONFIG_INT_WDT is not set +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y # CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set CONFIG_ESP32_DEBUG_OCDAWARE=y CONFIG_BROWNOUT_DET=y diff --git a/microcontroller/ports/esp32/sdkconfig.old b/microcontroller/ports/esp32/sdkconfig.old index 93ec205c..c7ea4e9c 100644 --- a/microcontroller/ports/esp32/sdkconfig.old +++ b/microcontroller/ports/esp32/sdkconfig.old @@ -1,6 +1,6 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 5.4.0 Project Configuration # CONFIG_SOC_BROWNOUT_RESET_SUPPORTED="Not determined" CONFIG_SOC_TWAI_BRP_DIV_SUPPORTED="Not determined" @@ -8,13 +8,17 @@ CONFIG_SOC_DPORT_WORKAROUND="Not determined" CONFIG_SOC_CAPS_ECO_VER_MAX=301 CONFIG_SOC_ADC_SUPPORTED=y CONFIG_SOC_DAC_SUPPORTED=y +CONFIG_SOC_UART_SUPPORTED=y CONFIG_SOC_MCPWM_SUPPORTED=y +CONFIG_SOC_GPTIMER_SUPPORTED=y CONFIG_SOC_SDMMC_HOST_SUPPORTED=y CONFIG_SOC_BT_SUPPORTED=y CONFIG_SOC_PCNT_SUPPORTED=y +CONFIG_SOC_PHY_SUPPORTED=y CONFIG_SOC_WIFI_SUPPORTED=y CONFIG_SOC_SDIO_SLAVE_SUPPORTED=y CONFIG_SOC_TWAI_SUPPORTED=y +CONFIG_SOC_EFUSE_SUPPORTED=y CONFIG_SOC_EMAC_SUPPORTED=y CONFIG_SOC_ULP_SUPPORTED=y CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y @@ -24,6 +28,9 @@ CONFIG_SOC_RTC_MEM_SUPPORTED=y CONFIG_SOC_I2S_SUPPORTED=y CONFIG_SOC_RMT_SUPPORTED=y CONFIG_SOC_SDM_SUPPORTED=y +CONFIG_SOC_GPSPI_SUPPORTED=y +CONFIG_SOC_LEDC_SUPPORTED=y +CONFIG_SOC_I2C_SUPPORTED=y CONFIG_SOC_SUPPORT_COEXISTENCE=y CONFIG_SOC_AES_SUPPORTED=y CONFIG_SOC_MPI_SUPPORTED=y @@ -31,6 +38,17 @@ CONFIG_SOC_SHA_SUPPORTED=y CONFIG_SOC_FLASH_ENC_SUPPORTED=y CONFIG_SOC_SECURE_BOOT_SUPPORTED=y CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y +CONFIG_SOC_BOD_SUPPORTED=y +CONFIG_SOC_ULP_FSM_SUPPORTED=y +CONFIG_SOC_CLK_TREE_SUPPORTED=y +CONFIG_SOC_MPU_SUPPORTED=y +CONFIG_SOC_WDT_SUPPORTED=y +CONFIG_SOC_SPI_FLASH_SUPPORTED=y +CONFIG_SOC_RNG_SUPPORTED=y +CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y +CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y +CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y +CONFIG_SOC_PM_SUPPORTED=y CONFIG_SOC_DPORT_WORKAROUND_DIS_INTERRUPT_LVL=5 CONFIG_SOC_XTAL_SUPPORT_26M=y CONFIG_SOC_XTAL_SUPPORT_40M=y @@ -47,45 +65,54 @@ CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=9 CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12 CONFIG_SOC_ADC_DIGI_RESULT_BYTES=2 CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4 +CONFIG_SOC_ADC_DIGI_MONITOR_NUM=0 CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=2 CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=20 CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=9 CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12 -CONFIG_SOC_RTC_SLOW_CLOCK_SUPPORT_8MD256=y +CONFIG_SOC_ADC_SHARED_POWER=y CONFIG_SOC_SHARED_IDCACHE_SUPPORTED=y CONFIG_SOC_IDCACHE_PER_CORE=y -CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=5 CONFIG_SOC_CPU_CORES_NUM=2 CONFIG_SOC_CPU_INTR_NUM=32 CONFIG_SOC_CPU_HAS_FPU=y +CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 -CONFIG_SOC_DAC_PERIPH_NUM=2 +CONFIG_SOC_DAC_CHAN_NUM=2 CONFIG_SOC_DAC_RESOLUTION=8 +CONFIG_SOC_DAC_DMA_16BIT_ALIGN=y CONFIG_SOC_GPIO_PORT=1 CONFIG_SOC_GPIO_PIN_COUNT=40 CONFIG_SOC_GPIO_VALID_GPIO_MASK=0xFFFFFFFFFF +CONFIG_SOC_GPIO_IN_RANGE_MAX=39 +CONFIG_SOC_GPIO_OUT_RANGE_MAX=33 CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0xEF0FEA +CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y +CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3 +CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y CONFIG_SOC_I2C_NUM=2 +CONFIG_SOC_HP_I2C_NUM=2 CONFIG_SOC_I2C_FIFO_LEN=32 +CONFIG_SOC_I2C_CMD_REG_NUM=16 CONFIG_SOC_I2C_SUPPORT_SLAVE=y CONFIG_SOC_I2C_SUPPORT_APB=y -CONFIG_SOC_CLK_APLL_SUPPORTED=y -CONFIG_SOC_APLL_MULTIPLIER_OUT_MIN_HZ=350000000 -CONFIG_SOC_APLL_MULTIPLIER_OUT_MAX_HZ=500000000 -CONFIG_SOC_APLL_MIN_HZ=5303031 -CONFIG_SOC_APLL_MAX_HZ=125000000 +CONFIG_SOC_I2C_STOP_INDEPENDENT=y CONFIG_SOC_I2S_NUM=2 CONFIG_SOC_I2S_HW_VERSION_1=y CONFIG_SOC_I2S_SUPPORTS_APLL=y +CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y CONFIG_SOC_I2S_SUPPORTS_PDM=y CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y +CONFIG_SOC_I2S_PDM_MAX_TX_LINES=1 CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y +CONFIG_SOC_I2S_PDM_MAX_RX_LINES=1 CONFIG_SOC_I2S_SUPPORTS_ADC_DAC=y CONFIG_SOC_I2S_SUPPORTS_ADC=y CONFIG_SOC_I2S_SUPPORTS_DAC=y CONFIG_SOC_I2S_SUPPORTS_LCD_CAMERA=y +CONFIG_SOC_I2S_MAX_DATA_WIDTH=24 CONFIG_SOC_I2S_TRANS_SIZE_ALIGN_WORD=y CONFIG_SOC_I2S_LCD_I80_VARIANT=y CONFIG_SOC_LCD_I80_SUPPORTED=y @@ -95,8 +122,9 @@ CONFIG_SOC_LEDC_HAS_TIMER_SPECIFIC_MUX=y CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y CONFIG_SOC_LEDC_SUPPORT_REF_TICK=y CONFIG_SOC_LEDC_SUPPORT_HS_MODE=y +CONFIG_SOC_LEDC_TIMER_NUM=4 CONFIG_SOC_LEDC_CHANNEL_NUM=8 -CONFIG_SOC_LEDC_TIMER_BIT_WIDE_NUM=20 +CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=20 CONFIG_SOC_MCPWM_GROUPS=2 CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3 CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3 @@ -107,6 +135,8 @@ CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3 CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3 CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3 +CONFIG_SOC_MMU_PERIPH_NUM=2 +CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=3 CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 CONFIG_SOC_PCNT_GROUPS=1 @@ -127,11 +157,13 @@ CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y CONFIG_SOC_SDM_GROUPS=1 CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 +CONFIG_SOC_SDM_CLK_SUPPORT_APB=y CONFIG_SOC_SPI_HD_BOTH_INOUT_SUPPORTED=y CONFIG_SOC_SPI_AS_CS_SUPPORTED=y CONFIG_SOC_SPI_PERIPH_NUM=3 CONFIG_SOC_SPI_DMA_CHAN_NUM=2 CONFIG_SOC_SPI_MAX_CS_NUM=3 +CONFIG_SOC_SPI_SUPPORT_CLK_APB=y CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64 CONFIG_SOC_SPI_MAX_PRE_DIVIDER=8192 CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y @@ -143,12 +175,15 @@ CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2 CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=64 CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y -CONFIG_SOC_TOUCH_VERSION_1=y +CONFIG_SOC_TOUCH_SENSOR_VERSION=1 CONFIG_SOC_TOUCH_SENSOR_NUM=10 -CONFIG_SOC_TOUCH_PAD_MEASURE_WAIT_MAX=0xFF +CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1 +CONFIG_SOC_TWAI_CONTROLLER_NUM=1 CONFIG_SOC_TWAI_BRP_MIN=2 +CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y CONFIG_SOC_TWAI_SUPPORT_MULTI_ADDRESS_LAYOUT=y CONFIG_SOC_UART_NUM=3 +CONFIG_SOC_UART_HP_NUM=3 CONFIG_SOC_UART_SUPPORT_APB_CLK=y CONFIG_SOC_UART_SUPPORT_REF_TICK=y CONFIG_SOC_UART_FIFO_LEN=128 @@ -156,10 +191,13 @@ CONFIG_SOC_UART_BITRATE_MAX=5000000 CONFIG_SOC_SPIRAM_SUPPORTED=y CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y CONFIG_SOC_SHA_SUPPORT_PARALLEL_ENG=y +CONFIG_SOC_SHA_ENDIANNESS_BE=y CONFIG_SOC_SHA_SUPPORT_SHA1=y CONFIG_SOC_SHA_SUPPORT_SHA256=y CONFIG_SOC_SHA_SUPPORT_SHA384=y CONFIG_SOC_SHA_SUPPORT_SHA512=y +CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4 +CONFIG_SOC_MPI_OPERATIONS_NUM=y CONFIG_SOC_RSA_MAX_BIT_LEN=4096 CONFIG_SOC_AES_SUPPORT_AES_128=y CONFIG_SOC_AES_SUPPORT_AES_192=y @@ -168,24 +206,45 @@ CONFIG_SOC_SECURE_BOOT_V1=y CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=y CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=32 CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21 +CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y +CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y CONFIG_SOC_PM_SUPPORT_RTC_FAST_MEM_PD=y CONFIG_SOC_PM_SUPPORT_RTC_SLOW_MEM_PD=y +CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y +CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y CONFIG_SOC_PM_SUPPORT_MODEM_PD=y +CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y +CONFIG_SOC_PM_MODEM_PD_BY_SW=y +CONFIG_SOC_CLK_APLL_SUPPORTED=y +CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y +CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y +CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y +CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y CONFIG_SOC_SDMMC_USE_IOMUX=y CONFIG_SOC_SDMMC_NUM_SLOTS=2 CONFIG_SOC_WIFI_WAPI_SUPPORT=y CONFIG_SOC_WIFI_CSI_SUPPORT=y CONFIG_SOC_WIFI_MESH_SUPPORT=y +CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y +CONFIG_SOC_WIFI_NAN_SUPPORT=y CONFIG_SOC_BLE_SUPPORTED=y CONFIG_SOC_BLE_MESH_SUPPORTED=y CONFIG_SOC_BT_CLASSIC_SUPPORTED=y +CONFIG_SOC_BLUFI_SUPPORTED=y +CONFIG_SOC_BT_H2C_ENC_KEY_CTRL_ENH_VSC_SUPPORTED=y +CONFIG_SOC_ULP_HAS_ADC=y +CONFIG_SOC_PHY_COMBO_MODULE=y +CONFIG_SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=y CONFIG_IDF_CMAKE=y +CONFIG_IDF_TOOLCHAIN="gcc" +CONFIG_IDF_TOOLCHAIN_GCC=y CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32" +CONFIG_IDF_INIT_VERSION="5.4.4" CONFIG_IDF_TARGET_ESP32=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 @@ -193,7 +252,7 @@ CONFIG_IDF_FIRMWARE_CHIP_ID=0x0000 # Build type # CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y -# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_APP_BUILD_TYPE_RAM is not set CONFIG_APP_BUILD_GENERATE_BINARIES=y CONFIG_APP_BUILD_BOOTLOADER=y CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y @@ -206,11 +265,23 @@ CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y # # Bootloader config # + +# +# Bootloader manager +# +CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y +CONFIG_BOOTLOADER_PROJECT_VER=1 +# end of Bootloader manager + CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x1000 CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF is not set # CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set + +# +# Log +# # CONFIG_BOOTLOADER_LOG_LEVEL_NONE is not set # CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set # CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set @@ -219,6 +290,14 @@ CONFIG_BOOTLOADER_LOG_LEVEL_INFO=y # CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set CONFIG_BOOTLOADER_LOG_LEVEL=3 +# +# Format +# +# CONFIG_BOOTLOADER_LOG_COLORS is not set +CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y +# end of Format +# end of Log + # # Serial Flash Configurations # @@ -258,14 +337,23 @@ CONFIG_APP_COMPILE_TIME_DATE=y # CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set # CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set # CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set -CONFIG_APP_RETRIEVE_LEN_ELF_SHA=16 +CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9 # end of Application manager CONFIG_ESP_ROM_HAS_CRC_LE=y CONFIG_ESP_ROM_HAS_CRC_BE=y CONFIG_ESP_ROM_HAS_MZ_CRC32=y CONFIG_ESP_ROM_HAS_JPEG_DECODE=y +CONFIG_ESP_ROM_HAS_UART_BUF_SWITCH=y CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y +CONFIG_ESP_ROM_HAS_NEWLIB=y +CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y +CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y +CONFIG_ESP_ROM_HAS_SW_FLOAT=y +CONFIG_ESP_ROM_USB_OTG_NUM=-1 +CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=-1 +CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y +CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y # # Serial flasher config @@ -283,14 +371,14 @@ CONFIG_ESPTOOLPY_FLASHFREQ_40M=y # CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set CONFIG_ESPTOOLPY_FLASHFREQ="40m" # CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set -# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHSIZE_2MB=y +# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_16MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set # CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set -CONFIG_ESPTOOLPY_FLASHSIZE="4MB" +CONFIG_ESPTOOLPY_FLASHSIZE="2MB" # CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set CONFIG_ESPTOOLPY_BEFORE_RESET=y # CONFIG_ESPTOOLPY_BEFORE_NORESET is not set @@ -304,12 +392,13 @@ CONFIG_ESPTOOLPY_MONITOR_BAUD=115200 # # Partition Table # -# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +CONFIG_PARTITION_TABLE_SINGLE_APP=y # CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set # CONFIG_PARTITION_TABLE_TWO_OTA is not set -CONFIG_PARTITION_TABLE_CUSTOM=y +# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set +# CONFIG_PARTITION_TABLE_CUSTOM is not set CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" -CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_FILENAME="partitions_singleapp.csv" CONFIG_PARTITION_TABLE_OFFSET=0x8000 CONFIG_PARTITION_TABLE_MD5=y # end of Partition Table @@ -317,13 +406,14 @@ CONFIG_PARTITION_TABLE_MD5=y # # Compiler options # -# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y # CONFIG_COMPILER_OPTIMIZATION_SIZE is not set -CONFIG_COMPILER_OPTIMIZATION_PERF=y +# CONFIG_COMPILER_OPTIMIZATION_PERF is not set # CONFIG_COMPILER_OPTIMIZATION_NONE is not set CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set # CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set +CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2 # CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set @@ -334,8 +424,18 @@ CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y # CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set # CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set # CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set +# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set # CONFIG_COMPILER_WARN_WRITE_STRINGS is not set +CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y +# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set +# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set # CONFIG_COMPILER_DUMP_RTL_FILES is not set +CONFIG_COMPILER_RT_LIB_GCCLIB=y +CONFIG_COMPILER_RT_LIB_NAME="gcc" +CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y +# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set +# CONFIG_COMPILER_STATIC_ANALYZER is not set # end of Compiler options # @@ -371,8 +471,9 @@ CONFIG_BT_BTC_TASK_STACK_SIZE=3072 CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y # CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1 is not set CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0 -CONFIG_BT_BTU_TASK_STACK_SIZE=4096 +CONFIG_BT_BTU_TASK_STACK_SIZE=4352 # CONFIG_BT_BLUEDROID_MEM_DEBUG is not set +CONFIG_BT_BLUEDROID_ESP_COEX_VSC=y # CONFIG_BT_CLASSIC_ENABLED is not set CONFIG_BT_BLE_ENABLED=y CONFIG_BT_GATTS_ENABLE=y @@ -581,7 +682,9 @@ CONFIG_BT_BLE_ESTAB_LINK_CONN_TOUT=30 CONFIG_BT_MAX_DEVICE_NAME_LEN=32 # CONFIG_BT_BLE_RPA_SUPPORTED is not set CONFIG_BT_BLE_RPA_TIMEOUT=900 +CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y # CONFIG_BT_BLE_HIGH_DUTY_ADV_INTERVAL is not set +# CONFIG_BT_ABORT_WHEN_ALLOCATION_FAILS is not set # end of Bluedroid Options # @@ -594,6 +697,7 @@ CONFIG_BTDM_CTRL_BLE_MAX_CONN=3 CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_EFF=0 CONFIG_BTDM_CTRL_PCM_ROLE_EFF=0 CONFIG_BTDM_CTRL_PCM_POLAR_EFF=0 +CONFIG_BTDM_CTRL_PCM_FSYNCSHP_EFF=0 CONFIG_BTDM_CTRL_BLE_MAX_CONN_EFF=3 CONFIG_BTDM_CTRL_BR_EDR_MAX_ACL_CONN_EFF=0 CONFIG_BTDM_CTRL_BR_EDR_MAX_SYNC_CONN_EFF=0 @@ -624,52 +728,42 @@ CONFIG_BTDM_SCAN_DUPL_CACHE_REFRESH_PERIOD=0 # CONFIG_BTDM_BLE_MESH_SCAN_DUPL_EN is not set CONFIG_BTDM_CTRL_FULL_SCAN_SUPPORTED=y # CONFIG_BTDM_CTRL_SCAN_BACKOFF_UPPERLIMITMAX is not set +# CONFIG_BTDM_CTRL_CHECK_CONNECT_IND_ACCESS_ADDRESS is not set CONFIG_BTDM_BLE_ADV_REPORT_FLOW_CTRL_SUPP=y CONFIG_BTDM_BLE_ADV_REPORT_FLOW_CTRL_NUM=100 CONFIG_BTDM_BLE_ADV_REPORT_DISCARD_THRSHOLD=20 + +# +# BLE disconnect when instant passed +# +# CONFIG_BTDM_BLE_LLCP_CONN_UPDATE is not set +# CONFIG_BTDM_BLE_LLCP_CHAN_MAP_UPDATE is not set +# end of BLE disconnect when instant passed + CONFIG_BTDM_RESERVE_DRAM=0xdb5c CONFIG_BTDM_CTRL_HLI=y # end of Controller Options -# CONFIG_BT_HCI_LOG_DEBUG_EN is not set - # # Common Options # CONFIG_BT_ALARM_MAX_NUM=50 # end of Common Options + +# CONFIG_BT_HCI_LOG_DEBUG_EN is not set # end of Bluetooth # CONFIG_BLE_MESH is not set # -# Driver Configurations -# - -# -# Legacy ADC Configuration -# -CONFIG_ADC_DISABLE_DAC=y -# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set - -# -# Legacy ADC Calibration Configuration +# Console Library # -CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y -CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y -CONFIG_ADC_CAL_LUT_ENABLE=y -# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set -# end of Legacy ADC Calibration Configuration -# end of Legacy ADC Configuration +# CONFIG_CONSOLE_SORTED_HELP is not set +# end of Console Library # -# SPI Configuration +# Driver Configurations # -# CONFIG_SPI_MASTER_IN_IRAM is not set -CONFIG_SPI_MASTER_ISR_IN_IRAM=y -# CONFIG_SPI_SLAVE_IN_IRAM is not set -CONFIG_SPI_SLAVE_ISR_IN_IRAM=y -# end of SPI Configuration # # TWAI Configuration @@ -683,69 +777,62 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # end of TWAI Configuration # -# UART Configuration +# Legacy ADC Driver Configuration # -# CONFIG_UART_ISR_IN_IRAM is not set -# end of UART Configuration +CONFIG_ADC_DISABLE_DAC=y +# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set # -# GPIO Configuration +# Legacy ADC Calibration Configuration # -# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set -# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set -# end of GPIO Configuration +CONFIG_ADC_CAL_EFUSE_TP_ENABLE=y +CONFIG_ADC_CAL_EFUSE_VREF_ENABLE=y +CONFIG_ADC_CAL_LUT_ENABLE=y +# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy ADC Calibration Configuration +# end of Legacy ADC Driver Configuration # -# Sigma Delta Modulator Configuration +# Legacy DAC Driver Configurations # -# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_SDM_ENABLE_DEBUG_LOG is not set -# end of Sigma Delta Modulator Configuration +# CONFIG_DAC_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy DAC Driver Configurations # -# GPTimer Configuration +# Legacy MCPWM Driver Configurations # -# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set -# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set -# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set -# end of GPTimer Configuration +# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy MCPWM Driver Configurations # -# PCNT Configuration +# Legacy Timer Group Driver Configurations # -# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set -# CONFIG_PCNT_ISR_IRAM_SAFE is not set -# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set -# end of PCNT Configuration +# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy Timer Group Driver Configurations # -# RMT Configuration +# Legacy RMT Driver Configurations # -# CONFIG_RMT_ISR_IRAM_SAFE is not set -# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set # CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_RMT_ENABLE_DEBUG_LOG is not set -# end of RMT Configuration +# end of Legacy RMT Driver Configurations # -# MCPWM Configuration +# Legacy I2S Driver Configurations # -# CONFIG_MCPWM_ISR_IRAM_SAFE is not set -# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set -# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set -# end of MCPWM Configuration +# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy I2S Driver Configurations # -# I2S Configuration +# Legacy PCNT Driver Configurations # -# CONFIG_I2S_ISR_IRAM_SAFE is not set -# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set -# CONFIG_I2S_ENABLE_DEBUG_LOG is not set -# end of I2S Configuration +# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy PCNT Driver Configurations + +# +# Legacy SDM Driver Configurations +# +# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# end of Legacy SDM Driver Configurations # end of Driver Configurations # @@ -765,7 +852,9 @@ CONFIG_EFUSE_MAX_BLK_LEN=192 CONFIG_ESP_TLS_USING_MBEDTLS=y # CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set # CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set -# CONFIG_ESP_TLS_SERVER is not set +# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set +# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set +# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set # CONFIG_ESP_TLS_PSK_VERIFICATION is not set # CONFIG_ESP_TLS_INSECURE is not set # end of ESP-TLS @@ -785,14 +874,124 @@ CONFIG_ADC_CALI_LUT_ENABLE=y # end of ADC Calibration Configurations CONFIG_ADC_DISABLE_DAC_OUTPUT=y +# CONFIG_ADC_ENABLE_DEBUG_LOG is not set # end of ADC and ADC Calibration +# +# Wireless Coexistence +# +CONFIG_ESP_COEX_ENABLED=y +CONFIG_ESP_COEX_SW_COEXIST_ENABLE=y +# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set +# CONFIG_ESP_COEX_GPIO_DEBUG is not set +# end of Wireless Coexistence + # # Common ESP-related # CONFIG_ESP_ERR_TO_NAME_LOOKUP=y # end of Common ESP-related +# +# ESP-Driver:DAC Configurations +# +# CONFIG_DAC_CTRL_FUNC_IN_IRAM is not set +# CONFIG_DAC_ISR_IRAM_SAFE is not set +# CONFIG_DAC_ENABLE_DEBUG_LOG is not set +CONFIG_DAC_DMA_AUTO_16BIT_ALIGN=y +# end of ESP-Driver:DAC Configurations + +# +# ESP-Driver:GPIO Configurations +# +# CONFIG_GPIO_ESP32_SUPPORT_SWITCH_SLP_PULL is not set +# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:GPIO Configurations + +# +# ESP-Driver:GPTimer Configurations +# +CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y +# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set +# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:GPTimer Configurations + +# +# ESP-Driver:I2C Configurations +# +# CONFIG_I2C_ISR_IRAM_SAFE is not set +# CONFIG_I2C_ENABLE_DEBUG_LOG is not set +# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set +# end of ESP-Driver:I2C Configurations + +# +# ESP-Driver:I2S Configurations +# +# CONFIG_I2S_ISR_IRAM_SAFE is not set +# CONFIG_I2S_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:I2S Configurations + +# +# ESP-Driver:LEDC Configurations +# +# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set +# end of ESP-Driver:LEDC Configurations + +# +# ESP-Driver:MCPWM Configurations +# +# CONFIG_MCPWM_ISR_IRAM_SAFE is not set +# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:MCPWM Configurations + +# +# ESP-Driver:PCNT Configurations +# +# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set +# CONFIG_PCNT_ISR_IRAM_SAFE is not set +# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:PCNT Configurations + +# +# ESP-Driver:RMT Configurations +# +# CONFIG_RMT_ISR_IRAM_SAFE is not set +# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set +# CONFIG_RMT_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:RMT Configurations + +# +# ESP-Driver:Sigma Delta Modulator Configurations +# +# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set +# CONFIG_SDM_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Sigma Delta Modulator Configurations + +# +# ESP-Driver:SPI Configurations +# +# CONFIG_SPI_MASTER_IN_IRAM is not set +CONFIG_SPI_MASTER_ISR_IN_IRAM=y +# CONFIG_SPI_SLAVE_IN_IRAM is not set +CONFIG_SPI_SLAVE_ISR_IN_IRAM=y +# end of ESP-Driver:SPI Configurations + +# +# ESP-Driver:Touch Sensor Configurations +# +# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set +# CONFIG_TOUCH_ISR_IRAM_SAFE is not set +# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:Touch Sensor Configurations + +# +# ESP-Driver:UART Configurations +# +# CONFIG_UART_ISR_IN_IRAM is not set +# end of ESP-Driver:UART Configurations + # # Ethernet # @@ -825,6 +1024,10 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y # # GDB Stub # +CONFIG_ESP_GDBSTUB_ENABLED=y +# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y +CONFIG_ESP_GDBSTUB_MAX_TASKS=32 # end of GDB Stub # @@ -833,6 +1036,8 @@ CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y # CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set # CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set +# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set +CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000 # end of ESP HTTP client # @@ -845,6 +1050,7 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32 # CONFIG_HTTPD_LOG_PURGE_DATA is not set # CONFIG_HTTPD_WS_SUPPORT is not set # CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set +CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000 # end of HTTP Server # @@ -852,12 +1058,14 @@ CONFIG_HTTPD_PURGE_BUF_LEN=32 # # CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set # CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set +CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000 # end of ESP HTTPS OTA # # ESP HTTPS server # # CONFIG_ESP_HTTPS_SERVER_ENABLE is not set +CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000 # end of ESP HTTPS server # @@ -882,6 +1090,12 @@ CONFIG_ESP_REV_MIN_FULL=0 # CONFIG_ESP32_REV_MAX_FULL=399 CONFIG_ESP_REV_MAX_FULL=399 +CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0 +CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=99 + +# +# Maximum Supported ESP32 eFuse Block Revision (eFuse Block Rev v0.99) +# # end of Chip revision # @@ -891,10 +1105,13 @@ CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y +CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4 # CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_TWO is not set CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES_FOUR=y CONFIG_ESP32_UNIVERSAL_MAC_ADDRESSES=4 # CONFIG_ESP_MAC_IGNORE_MAC_CRC_ERROR is not set +# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set # end of MAC Config # @@ -905,7 +1122,9 @@ CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y # CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU is not set CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y # CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set -CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000 +# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set +# CONFIG_ESP_SLEEP_DEBUG is not set CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y # end of Sleep Config @@ -922,64 +1141,76 @@ CONFIG_RTC_CLK_CAL_CYCLES=1024 # # Peripheral Control # -# CONFIG_PERIPH_CTRL_FUNC_IN_IRAM is not set +CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y # end of Peripheral Control # # Main XTAL Config # # CONFIG_XTAL_FREQ_26 is not set +# CONFIG_XTAL_FREQ_32 is not set CONFIG_XTAL_FREQ_40=y # CONFIG_XTAL_FREQ_AUTO is not set CONFIG_XTAL_FREQ=40 # end of Main XTAL Config -# end of Hardware Settings -# -# LCD and Touch Panel -# +CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y +# end of Hardware Settings # -# LCD Touch Drivers are maintained in the IDF Component Registry +# ESP-Driver:LCD Controller Configurations # +# CONFIG_LCD_ENABLE_DEBUG_LOG is not set +# end of ESP-Driver:LCD Controller Configurations # -# LCD Peripheral Configuration +# ESP-MM: Memory Management Configurations # -CONFIG_LCD_PANEL_IO_FORMAT_BUF_SIZE=32 -# CONFIG_LCD_ENABLE_DEBUG_LOG is not set -# end of LCD Peripheral Configuration -# end of LCD and Touch Panel +# end of ESP-MM: Memory Management Configurations # # ESP NETIF Adapter # CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120 +# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set CONFIG_ESP_NETIF_TCPIP_LWIP=y # CONFIG_ESP_NETIF_LOOPBACK is not set +CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y +CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y # CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set # CONFIG_ESP_NETIF_L2_TAP is not set # CONFIG_ESP_NETIF_BRIDGE_EN is not set +# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set # end of ESP NETIF Adapter +# +# Partition API Configuration +# +# end of Partition API Configuration + # # PHY # +CONFIG_ESP_PHY_ENABLED=y CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y # CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20 CONFIG_ESP_PHY_MAX_TX_POWER=20 # CONFIG_ESP_PHY_REDUCE_TX_POWER is not set +# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set CONFIG_ESP_PHY_RF_CAL_PARTIAL=y # CONFIG_ESP_PHY_RF_CAL_NONE is not set # CONFIG_ESP_PHY_RF_CAL_FULL is not set CONFIG_ESP_PHY_CALIBRATION_MODE=0 +# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set # end of PHY # # Power Management # # CONFIG_PM_ENABLE is not set +# CONFIG_PM_SLP_IRAM_OPT is not set # end of Power Management # @@ -994,6 +1225,11 @@ CONFIG_ESP_PHY_CALIBRATION_MODE=0 # CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set # end of ESP Ringbuf +# +# ESP Security Specific +# +# end of ESP Security Specific + # # ESP System Settings # @@ -1006,6 +1242,12 @@ CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160 # Memory # # CONFIG_ESP32_USE_FIXED_STATIC_RAM_SIZE is not set + +# +# Non-backward compatible options +# +# CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM is not set +# end of Non-backward compatible options # end of Memory # @@ -1019,7 +1261,7 @@ CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y # CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set # CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set -# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set +CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0 # # Memory protection @@ -1038,8 +1280,8 @@ CONFIG_ESP_CONSOLE_UART_DEFAULT=y # CONFIG_ESP_CONSOLE_UART_CUSTOM is not set # CONFIG_ESP_CONSOLE_NONE is not set CONFIG_ESP_CONSOLE_UART=y -CONFIG_ESP_CONSOLE_MULTIPLE_UART=y CONFIG_ESP_CONSOLE_UART_NUM=0 +CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0 CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 CONFIG_ESP_INT_WDT=y CONFIG_ESP_INT_WDT_TIMEOUT_MS=300 @@ -1083,54 +1325,80 @@ CONFIG_ESP_IPC_ISR_ENABLE=y # end of IPC (Inter-Processor Call) # -# High resolution timer (esp_timer) +# ESP Timer (High Resolution Timer) # # CONFIG_ESP_TIMER_PROFILING is not set CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584 CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1 +# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set +CONFIG_ESP_TIMER_TASK_AFFINITY=0x0 +CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y +CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y # CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set CONFIG_ESP_TIMER_IMPL_TG0_LAC=y -# end of High resolution timer (esp_timer) +# end of ESP Timer (High Resolution Timer) # # Wi-Fi # -CONFIG_ESP32_WIFI_ENABLED=y -CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y -# CONFIG_ESP_COEX_POWER_MANAGEMENT is not set -CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 -CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 -# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y -CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1 -CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32 +CONFIG_ESP_WIFI_ENABLED=y +CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y +CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1 +CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32 CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y # CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5 -# CONFIG_ESP32_WIFI_CSI_ENABLED is not set -CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y -CONFIG_ESP32_WIFI_TX_BA_WIN=6 -CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_RX_BA_WIN=6 -CONFIG_ESP32_WIFI_NVS_ENABLED=y -CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y -# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set -CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 -CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 -CONFIG_ESP32_WIFI_IRAM_OPT=y -CONFIG_ESP32_WIFI_RX_IRAM_OPT=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y -CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +# CONFIG_ESP_WIFI_CSI_ENABLED is not set +CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP_WIFI_TX_BA_WIN=6 +CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP_WIFI_RX_BA_WIN=6 +CONFIG_ESP_WIFI_NVS_ENABLED=y +CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP_WIFI_IRAM_OPT=y +# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set +CONFIG_ESP_WIFI_RX_IRAM_OPT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP_WIFI_ENABLE_SAE_PK=y +CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y +CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y # CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set +CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50 +CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10 +CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15 CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y -# CONFIG_ESP_WIFI_GMAC_SUPPORT is not set +CONFIG_ESP_WIFI_GMAC_SUPPORT=y CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y # CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7 +# CONFIG_ESP_WIFI_NAN_ENABLE is not set +CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y +CONFIG_ESP_WIFI_MBEDTLS_TLS_CLIENT=y +# CONFIG_ESP_WIFI_WAPI_PSK is not set +# CONFIG_ESP_WIFI_11KV_SUPPORT is not set +# CONFIG_ESP_WIFI_MBO_SUPPORT is not set +# CONFIG_ESP_WIFI_DPP_SUPPORT is not set +# CONFIG_ESP_WIFI_11R_SUPPORT is not set +# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set + +# +# WPS Configuration Options +# +# CONFIG_ESP_WIFI_WPS_STRICT is not set # CONFIG_ESP_WIFI_WPS_PASSPHRASE is not set +# end of WPS Configuration Options + +# CONFIG_ESP_WIFI_DEBUG_PRINT is not set +# CONFIG_ESP_WIFI_TESTING_OPTIONS is not set CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=y # CONFIG_ESP_WIFI_ENT_FREE_DYNAMIC_BUFFER is not set # end of Wi-Fi @@ -1147,18 +1415,11 @@ CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y # FAT Filesystem support # CONFIG_FATFS_VOLUME_COUNT=2 +CONFIG_FATFS_LFN_NONE=y +# CONFIG_FATFS_LFN_HEAP is not set +# CONFIG_FATFS_LFN_STACK is not set # CONFIG_FATFS_SECTOR_512 is not set -# CONFIG_FATFS_SECTOR_1024 is not set -# CONFIG_FATFS_SECTOR_2048 is not set CONFIG_FATFS_SECTOR_4096=y -CONFIG_FATFS_SECTORS_PER_CLUSTER_1=y -# CONFIG_FATFS_SECTORS_PER_CLUSTER_2 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_4 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_8 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_16 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_32 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_64 is not set -# CONFIG_FATFS_SECTORS_PER_CLUSTER_128 is not set # CONFIG_FATFS_CODEPAGE_DYNAMIC is not set CONFIG_FATFS_CODEPAGE_437=y # CONFIG_FATFS_CODEPAGE_720 is not set @@ -1181,17 +1442,18 @@ CONFIG_FATFS_CODEPAGE_437=y # CONFIG_FATFS_CODEPAGE_936 is not set # CONFIG_FATFS_CODEPAGE_949 is not set # CONFIG_FATFS_CODEPAGE_950 is not set -CONFIG_FATFS_AUTO_TYPE=y -# CONFIG_FATFS_FAT12 is not set -# CONFIG_FATFS_FAT16 is not set CONFIG_FATFS_CODEPAGE=437 -CONFIG_FATFS_LFN_NONE=y -# CONFIG_FATFS_LFN_HEAP is not set -# CONFIG_FATFS_LFN_STACK is not set CONFIG_FATFS_FS_LOCK=0 CONFIG_FATFS_TIMEOUT_MS=10000 CONFIG_FATFS_PER_FILE_CACHE=y # CONFIG_FATFS_USE_FASTSEEK is not set +CONFIG_FATFS_USE_STRFUNC_NONE=y +# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set +# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set +CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0 +# CONFIG_FATFS_IMMEDIATE_FSYNC is not set +# CONFIG_FATFS_USE_LABEL is not set +CONFIG_FATFS_LINK_LOCK=y # end of FAT Filesystem support # @@ -1213,18 +1475,30 @@ CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536 # CONFIG_FREERTOS_USE_TICK_HOOK is not set CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16 # CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set +CONFIG_FREERTOS_USE_TIMERS=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc" +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set +# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set +CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y +CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1 CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048 CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10 CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0 +CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1 # CONFIG_FREERTOS_USE_TRACE_FACILITY is not set +# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set # CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS is not set +# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set # end of Kernel # # Port # +CONFIG_FREERTOS_TASK_FUNCTION_WRAPPER=y # CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set +CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y +# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set # CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y CONFIG_FREERTOS_ISR_STACKSIZE=1536 @@ -1235,15 +1509,21 @@ CONFIG_FREERTOS_CORETIMER_0=y # CONFIG_FREERTOS_CORETIMER_1 is not set CONFIG_FREERTOS_SYSTICK_USES_CCOUNT=y # CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set -# CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH is not set # CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set -CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION=y -CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y # end of Port +# +# Extra +# +# end of Extra + +CONFIG_FREERTOS_PORT=y CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y CONFIG_FREERTOS_DEBUG_OCDAWARE=y +CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y +CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y +CONFIG_FREERTOS_NUMBER_OF_CORES=2 # end of FreeRTOS # @@ -1254,6 +1534,9 @@ CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y # CONFIG_HAL_ASSERTION_SILENT is not set # CONFIG_HAL_ASSERTION_ENABLE is not set CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 +CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y +CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y +# CONFIG_HAL_ECDSA_GEN_SIG_CM is not set # end of Hardware Abstraction Layer (HAL) and Low Level (LL) # @@ -1265,11 +1548,18 @@ CONFIG_HEAP_POISONING_DISABLED=y CONFIG_HEAP_TRACING_OFF=y # CONFIG_HEAP_TRACING_STANDALONE is not set # CONFIG_HEAP_TRACING_TOHOST is not set +# CONFIG_HEAP_USE_HOOKS is not set +# CONFIG_HEAP_TASK_TRACKING is not set # CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set +# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set # end of Heap memory debugging # -# Log output +# Log +# + +# +# Log Level # # CONFIG_LOG_DEFAULT_LEVEL_NONE is not set # CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set @@ -1282,14 +1572,34 @@ CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y # CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set # CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set CONFIG_LOG_MAXIMUM_LEVEL=3 -CONFIG_LOG_COLORS=y + +# +# Level Settings +# +# CONFIG_LOG_MASTER_LEVEL is not set +CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y +# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set +# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y +# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set +CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y +CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31 +# end of Level Settings +# end of Log Level + +# +# Format +# +# CONFIG_LOG_COLORS is not set CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y # CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set -# end of Log output +# end of Format +# end of Log # # LWIP # +CONFIG_LWIP_ENABLE=y CONFIG_LWIP_LOCAL_HOSTNAME="espressif" # CONFIG_LWIP_NETIF_API is not set CONFIG_LWIP_TCPIP_TASK_PRIO=18 @@ -1298,7 +1608,10 @@ CONFIG_LWIP_TCPIP_TASK_PRIO=18 CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y # CONFIG_LWIP_L2_TO_L3_COPY is not set # CONFIG_LWIP_IRAM_OPTIMIZATION is not set +# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set CONFIG_LWIP_TIMERS_ONDEMAND=y +CONFIG_LWIP_ND6=y +# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set CONFIG_LWIP_MAX_SOCKETS=10 # CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set # CONFIG_LWIP_SO_LINGER is not set @@ -1311,6 +1624,7 @@ CONFIG_LWIP_IP4_FRAG=y CONFIG_LWIP_IP6_FRAG=y # CONFIG_LWIP_IP4_REASSEMBLY is not set # CONFIG_LWIP_IP6_REASSEMBLY is not set +CONFIG_LWIP_IP_REASS_MAX_PBUFS=10 # CONFIG_LWIP_IP_FORWARD is not set # CONFIG_LWIP_STATS is not set CONFIG_LWIP_ESP_GRATUITOUS_ARP=y @@ -1319,10 +1633,12 @@ CONFIG_LWIP_ESP_MLDV6_REPORT=y CONFIG_LWIP_MLDV6_TMR_INTERVAL=40 CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32 CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y +# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set +# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set # CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y # CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set -CONFIG_LWIP_DHCP_OPTIONS_LEN=68 +CONFIG_LWIP_DHCP_OPTIONS_LEN=69 CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0 CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 @@ -1332,9 +1648,12 @@ CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1 CONFIG_LWIP_DHCPS=y CONFIG_LWIP_DHCPS_LEASE_UNIT=60 CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8 +CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y +CONFIG_LWIP_DHCPS_ADD_DNS=y # end of DHCP server # CONFIG_LWIP_AUTOIP is not set +CONFIG_LWIP_IPV4=y CONFIG_LWIP_IPV6=y # CONFIG_LWIP_IPV6_AUTOCONFIG is not set CONFIG_LWIP_IPV6_NUM_ADDRESSES=3 @@ -1358,6 +1677,7 @@ CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000 CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760 CONFIG_LWIP_TCP_WND_DEFAULT=5760 CONFIG_LWIP_TCP_RECVMBOX_SIZE=6 +CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6 CONFIG_LWIP_TCP_QUEUE_OOSEQ=y CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6 CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4 @@ -1388,11 +1708,12 @@ CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y # CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set # CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF -# CONFIG_LWIP_PPP_SUPPORT is not set CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3 CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5 -CONFIG_LWIP_ND6=y -# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set +CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5 +CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3 +CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10 +# CONFIG_LWIP_PPP_SUPPORT is not set # CONFIG_LWIP_SLIP_SUPPORT is not set # @@ -1415,17 +1736,20 @@ CONFIG_LWIP_MAX_RAW_PCBS=16 CONFIG_LWIP_SNTP_MAX_SERVERS=1 # CONFIG_LWIP_DHCP_GET_NTP_SRV is not set CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000 +CONFIG_LWIP_SNTP_STARTUP_DELAY=y +CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000 # end of SNTP -CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 - # # DNS # +CONFIG_LWIP_DNS_MAX_HOST_IP=1 CONFIG_LWIP_DNS_MAX_SERVERS=3 # CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set +# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set # end of DNS +CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7 CONFIG_LWIP_ESP_LWIP_ASSERT=y # @@ -1440,11 +1764,16 @@ CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y # CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set # CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set +CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set +# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set # CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set -CONFIG_LWIP_HOOK_IP6_INPUT_NONE=y -# CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT is not set +CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y +# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set +# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set +CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y # CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set # end of Hooks @@ -1482,14 +1811,16 @@ CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y # CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set # CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set # CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set +# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200 # end of Certificate Bundle # CONFIG_MBEDTLS_ECP_RESTARTABLE is not set CONFIG_MBEDTLS_CMAC_C=y CONFIG_MBEDTLS_HARDWARE_AES=y -# CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER is not set +CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y CONFIG_MBEDTLS_HARDWARE_MPI=y +# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set CONFIG_MBEDTLS_HARDWARE_SHA=y CONFIG_MBEDTLS_ROM_MD5=y # CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set @@ -1499,6 +1830,7 @@ CONFIG_MBEDTLS_HAVE_TIME=y # CONFIG_MBEDTLS_HAVE_TIME_DATE is not set CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y CONFIG_MBEDTLS_SHA512_C=y +# CONFIG_MBEDTLS_SHA3_C is not set CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y # CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set # CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set @@ -1552,6 +1884,8 @@ CONFIG_MBEDTLS_X509_CSR_PARSE_C=y # end of Certificates CONFIG_MBEDTLS_ECP_C=y +CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y +CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y # CONFIG_MBEDTLS_DHM_C is not set CONFIG_MBEDTLS_ECDH_C=y CONFIG_MBEDTLS_ECDSA_C=y @@ -1569,12 +1903,13 @@ CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y CONFIG_MBEDTLS_ECP_NIST_OPTIM=y -CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM=y +# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set # CONFIG_MBEDTLS_POLY1305_C is not set # CONFIG_MBEDTLS_CHACHA20_C is not set # CONFIG_MBEDTLS_HKDF_C is not set # CONFIG_MBEDTLS_THREADING_C is not set -# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set +CONFIG_MBEDTLS_ERROR_STRINGS=y +CONFIG_MBEDTLS_FS_IO=y # end of mbedTLS # @@ -1613,12 +1948,19 @@ CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y # NVS # # CONFIG_NVS_ASSERT_ERROR_CHECK is not set +# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set # end of NVS # # OpenThread # # CONFIG_OPENTHREAD_ENABLED is not set + +# +# OpenThread Spinel +# +# CONFIG_OPENTHREAD_SPINEL_ONLY is not set +# end of OpenThread Spinel # end of OpenThread # @@ -1668,6 +2010,8 @@ CONFIG_SPI_FLASH_BROWNOUT_RESET=y # # Features here require specific hardware (READ DOCS FIRST!) # +CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50 +# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set # end of Optional and Experimental Features (READ DOCS FIRST) # end of Main Flash configuration @@ -1693,6 +2037,11 @@ CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192 # # Auto-detect flash chips # +CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y +CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y @@ -1757,6 +2106,11 @@ CONFIG_WS_BUFFER_SIZE=1024 # Ultra Low Power (ULP) Co-processor # # CONFIG_ULP_COPROC_ENABLED is not set + +# +# ULP Debugging Options +# +# end of ULP Debugging Options # end of Ultra Low Power (ULP) Co-processor # @@ -1771,11 +2125,6 @@ CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y # CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set # end of Unity unit testing library -# -# Root Hub configuration -# -# end of Root Hub configuration - # # Virtual file system # @@ -1783,13 +2132,17 @@ CONFIG_VFS_SUPPORT_IO=y CONFIG_VFS_SUPPORT_DIR=y CONFIG_VFS_SUPPORT_SELECT=y CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y +# CONFIG_VFS_SELECT_IN_RAM is not set CONFIG_VFS_SUPPORT_TERMIOS=y +CONFIG_VFS_MAX_COUNT=8 # # Host File System I/O (Semihosting) # CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1 # end of Host File System I/O (Semihosting) + +CONFIG_VFS_INITIALIZE_DEV_NULL=y # end of Virtual file system # @@ -1807,24 +2160,11 @@ CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16 CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30 # CONFIG_WIFI_PROV_BLE_BONDING is not set # CONFIG_WIFI_PROV_BLE_FORCE_ENCRYPTION is not set +# CONFIG_WIFI_PROV_BLE_NOTIFY is not set # CONFIG_WIFI_PROV_KEEP_BLE_ON_AFTER_PROV is not set CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager - -# -# Supplicant -# -CONFIG_WPA_MBEDTLS_CRYPTO=y -CONFIG_WPA_MBEDTLS_TLS_CLIENT=y -# CONFIG_WPA_WAPI_PSK is not set -# CONFIG_WPA_DEBUG_PRINT is not set -# CONFIG_WPA_TESTING_OPTIONS is not set -# CONFIG_WPA_WPS_STRICT is not set -# CONFIG_WPA_11KV_SUPPORT is not set -# CONFIG_WPA_MBO_SUPPORT is not set -# CONFIG_WPA_DPP_SUPPORT is not set -# CONFIG_WPA_11R_SUPPORT is not set -# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set -# end of Supplicant # end of Component config + +# CONFIG_IDF_EXPERIMENTAL_FEATURES is not set diff --git a/microcontroller/ports/host/comm.c b/microcontroller/ports/host/comm.c index 184bfc73..4e31b8df 100644 --- a/microcontroller/ports/host/comm.c +++ b/microcontroller/ports/host/comm.c @@ -1,7 +1,6 @@ #include #include #include -#include #include "./comm.h" @@ -12,7 +11,7 @@ static void comm_send(host_protocol_t protocol, char* payload) { snprintf((char*)(line + PROTO_SIZE), PAYLOAD_LEN_SIZE, "%04d", (int)strlen(payload)); line[HEADER_SIZE - 1] = ' '; strcpy((char*)(line + HEADER_SIZE), payload); - fprintf(stdout, line); + fprintf(stdout, "%s", line); fflush(stdout); } @@ -25,13 +24,13 @@ void bs_comm_send_error(char* message) { } void bs_comm_send_exectime(float time) { - char* timestr[16]; + char timestr[16]; snprintf(timestr, sizeof(timestr), "%.4f", time); comm_send(H_PROTOCOL_EXECTIME, timestr); } void bs_comm_send_loadtime(float time) { - char* timestr[16]; + char timestr[16]; snprintf(timestr, sizeof(timestr), "%.2f", time); comm_send(H_PROTOCOL_LOADTIME, timestr); } @@ -40,13 +39,13 @@ static void parse_line(char* line, host_protocol_t* protocol, char* payload) { char protocol_char[PROTO_SIZE]; protocol_char[0] = line[0]; protocol_char[1] = line[1]; - protocol_char[2] = NULL; + protocol_char[2] = '\0'; char payload_len_char[PAYLOAD_LEN_SIZE]; payload_len_char[0] = line[PROTO_SIZE + 0]; payload_len_char[1] = line[PROTO_SIZE + 1]; payload_len_char[2] = line[PROTO_SIZE + 2]; payload_len_char[3] = line[PROTO_SIZE + 3]; - payload_len_char[4] = NULL; + payload_len_char[4] = '\0'; *protocol = atoi(protocol_char); int payload_len = atoi(payload_len_char); for (int i = 0; i < payload_len; i++) { @@ -77,7 +76,7 @@ char* bs_comm_wait_receive(void (*on_load)(char* filename), void (*on_call)(char if (res == NULL) return NULL; - int protocol; + host_protocol_t protocol; char payload[MAX_PAYLOAD_SIZE] = {0}; parse_line(line, &protocol, payload); diff --git a/microcontroller/ports/host/comm.h b/microcontroller/ports/host/comm.h index e7f5ecb1..22f00a1e 100644 --- a/microcontroller/ports/host/comm.h +++ b/microcontroller/ports/host/comm.h @@ -3,7 +3,7 @@ #include -#define MAX_PAYLOAD_SIZE 128 +#define MAX_PAYLOAD_SIZE 256 #define PROTO_SIZE 3 #define PAYLOAD_LEN_SIZE 5 #define HEADER_SIZE PROTO_SIZE + PAYLOAD_LEN_SIZE diff --git a/microcontroller/ports/host/shell.c b/microcontroller/ports/host/shell.c index 3d7114d2..4357c390 100644 --- a/microcontroller/ports/host/shell.c +++ b/microcontroller/ports/host/shell.c @@ -3,17 +3,25 @@ #include #include #include -#include -#include #include "../../core/include/c-runtime.h" #include "./comm.h" +#ifndef _WIN32 +#include +#include +#else +#include +#include +#include +#endif + extern void bluescript_main0_(); void* file_handle; static float get_time_ms() { +#ifndef _WIN32 static struct timespec ts0 = { 0, -1 }; struct timespec ts; if (ts0.tv_nsec < 0) @@ -21,32 +29,62 @@ static float get_time_ms() { clock_gettime(CLOCK_REALTIME, &ts); return (float)(ts.tv_sec - ts0.tv_sec) * 1000.0 + (float)(ts.tv_nsec - ts0.tv_nsec) / 1000000.0; +#else + static LARGE_INTEGER freq = { 0 }; + static LARGE_INTEGER start = { 0 }; + LARGE_INTEGER now; + if (freq.QuadPart == 0) { + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + } + QueryPerformanceCounter(&now); + return (float)(now.QuadPart - start.QuadPart) * 1000.0f / (float)freq.QuadPart; +#endif } static void load(char* filename) { float start_time = get_time_ms(); +#ifndef _WIN32 file_handle = dlopen(filename, RTLD_NOW | RTLD_GLOBAL); + if (file_handle == NULL) { + fprintf(stderr, "Error: failed to load %s\n", filename); + } +#else + file_handle = (void*)LoadLibraryA(filename); + if (file_handle == NULL) { + fprintf(stderr, "Error: failed to load %s (error %lu)\n", filename, GetLastError()); + } +#endif bs_comm_send_loadtime(get_time_ms() - start_time); } -static int call(char* funcname) { +static void call(char* funcname) { if (file_handle == NULL) { fprintf(stderr, "Error: module is not loaded\n"); - return 1; + //return 1; } - void (*fptr)() = dlsym(file_handle, funcname); +#ifndef _WIN32 + void (*fptr)(void) = (void (*)(void))dlsym(file_handle, funcname); +#else + void (*fptr)(void) = (void (*)(void))GetProcAddress((HMODULE)file_handle, funcname); +#endif if (fptr == NULL) { fprintf(stderr, "Error: %s() is not found\n", funcname); - return 1; + //return 1; } else { float start_time = get_time_ms(); int r2 = try_and_catch(fptr); bs_comm_send_exectime(get_time_ms() - start_time); - return r2; + //return r2; } } int main() { +#ifdef _WIN32 + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + gc_initialize(); bluescript_main0_(); diff --git a/microcontroller/ports/host/std-module.bs b/microcontroller/ports/host/std-module.bs index 6a0c504f..221e59ad 100644 --- a/microcontroller/ports/host/std-module.bs +++ b/microcontroller/ports/host/std-module.bs @@ -5,10 +5,15 @@ export function code(strings: any, ... keys: any[]) {} code` #include #include -#include #include #include "../../core/include/c-runtime.h" #include "./comm.h" +#ifndef _WIN32 +#include +#include +#else +#include +#endif void send_message(const char* format, ...) { @@ -41,6 +46,28 @@ void print_message(value_t m) { send_message("\n", cls->name); } } + +static float get_time_ms() { +#ifndef _WIN32 + static struct timespec ts0 = { 0, -1 }; + struct timespec ts; + if (ts0.tv_nsec < 0) + clock_gettime(CLOCK_REALTIME, &ts0); + + clock_gettime(CLOCK_REALTIME, &ts); + return (float)(ts.tv_sec - ts0.tv_sec) * 1000.0 + (float)(ts.tv_nsec - ts0.tv_nsec) / 1000000.0; +#else + static LARGE_INTEGER freq = { 0 }; + static LARGE_INTEGER start = { 0 }; + LARGE_INTEGER now; + if (freq.QuadPart == 0) { + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + } + QueryPerformanceCounter(&now); + return (float)(now.QuadPart - start.QuadPart) * 1000.0f / (float)freq.QuadPart; +#endif +} ` function print(message: any) { @@ -61,15 +88,7 @@ class Console { class Time { now(): float { let t: integer = 0 - code` - static struct timespec ts0 = { 0, -1 }; - struct timespec ts; - if (ts0.tv_nsec < 0) - clock_gettime(CLOCK_REALTIME, &ts0); - - clock_gettime(CLOCK_REALTIME, &ts); - ${t} = (int32_t)((ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000); - ` + code`${t} = get_time_ms();` return t } } diff --git a/microcontroller/ports/host/std-module.c b/microcontroller/ports/host/std-module.c index 3dbdbf4e..3e458e7f 100644 --- a/microcontroller/ports/host/std-module.c +++ b/microcontroller/ports/host/std-module.c @@ -1,10 +1,15 @@ #include #include -#include #include #include "../../core/include/c-runtime.h" #include "./comm.h" +#ifndef _WIN32 +#include +#include +#else +#include +#endif void send_message(const char* format, ...) { static char message[MAX_LINE_SIZE]; @@ -38,6 +43,29 @@ void print_message(value_t m) { } +static float get_time_ms() { +#ifndef _WIN32 + static struct timespec ts0 = { 0, -1 }; + struct timespec ts; + if (ts0.tv_nsec < 0) + clock_gettime(CLOCK_REALTIME, &ts0); + + clock_gettime(CLOCK_REALTIME, &ts); + return (float)(ts.tv_sec - ts0.tv_sec) * 1000.0 + (float)(ts.tv_nsec - ts0.tv_nsec) / 1000000.0; +#else + static LARGE_INTEGER freq = { 0 }; + static LARGE_INTEGER start = { 0 }; + LARGE_INTEGER now; + if (freq.QuadPart == 0) { + QueryPerformanceFrequency(&freq); + QueryPerformanceCounter(&start); + } + QueryPerformanceCounter(&now); + return (float)(now.QuadPart - start.QuadPart) * 1000.0f / (float)freq.QuadPart; +#endif +} + + extern struct func_body _print; void mth_0_Console(value_t self, value_t _message); void mth_1_Console(value_t self, value_t _message); @@ -97,14 +125,7 @@ float mth_0_Time(value_t self) { func_rootset.values[0] = self; { int32_t _t = 0; - - static struct timespec ts0 = { 0, -1 }; - struct timespec ts; - if (ts0.tv_nsec < 0) - clock_gettime(CLOCK_REALTIME, &ts0); - - clock_gettime(CLOCK_REALTIME, &ts); - _t = (int32_t)((ts.tv_sec - ts0.tv_sec) * 1000 + (ts.tv_nsec - ts0.tv_nsec) / 1000000); + _t = get_time_ms(); ; { float ret_value_ = (_t); DELETE_ROOT_SET(func_rootset); return ret_value_; } } diff --git a/package-lock.json b/package-lock.json index 7ef8ed20..9e51884e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19511,23 +19511,6 @@ } } }, - "node_modules/tailwindcss/node_modules/yaml": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", - "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", - "license": "ISC", - "optional": true, - "peer": true, - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", diff --git a/package.json b/package.json index d5604285..0e0b4fb3 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,6 @@ "typescript": "^5.9.2" }, "scripts": { - "build": "tsc -b && npm run build -w @bscript/notebook" + "build": "tsc -b" } } diff --git a/website/docs/reference/cli.md b/website/docs/reference/cli.md index 3f1bd65d..dd695e9f 100644 --- a/website/docs/reference/cli.md +++ b/website/docs/reference/cli.md @@ -8,6 +8,12 @@ The BlueScript CLI (`bscript`) is the primary tool for managing projects, settin npm install -g @bscript/cli ``` +:::info Supported platforms +BlueScript CLI supports **macOS** and **Windows**. **Linux is not supported.** + +On Windows, install the Visual C++ Build Environment before `npm install -g @bscript/cli` (required by node-gyp for native dependencies such as `serialport`). See [Windows prerequisites](../tutorial/get-started/setup-environment-windows.md). +::: + ## Project Management ### `bscript project create` @@ -150,7 +156,14 @@ bscript board setup **Arguments:** * ``: The target board identifier (`esp32` or `host`). -For `esp32`, this downloads ESP-IDF and related tools. For `host`, this builds the local runtime process and requires a C compiler toolchain (`cc` and `make`). See [Try Without Microcontroller](../tutorial/guides/try-without-microcontroller.md). +**Platform requirements:** + +| Board | macOS | Windows | +| :--- | :--- | :--- | +| `host` | `cc`, `make` | MinGW-w64: `gcc`, `mingw32-make` | +| `esp32` | Homebrew, Git, Python 3, `make` | Git, Python 3, `make` or `mingw32-make`. See [Windows prerequisites](../tutorial/get-started/setup-environment-windows.md). | + +For `host`, see [Try Without Microcontroller](../tutorial/guides/try-without-microcontroller.md). --- @@ -170,7 +183,7 @@ bscript board flash-runtime [options] | Option | Alias | Description | | :--- | :--- | :--- | -| `--port` | `-p` | Specify the serial port connected to the device (e.g., `COM3`, `/dev/ttyUSB0`). If omitted, the CLI will list available ports for selection. | +| `--port` | `-p` | Serial port (e.g. macOS: `/dev/tty.usbserial-xxxx`; Windows: `COM3`). If omitted, the CLI lists available ports for selection. | | `--device-name` | `-d` | Bluetooth device name advertised by the runtime after flashing (default: `"BLUESCRIPT"`). Must match `deviceName` in your project's `bsconfig.json` when connecting wirelessly. | **Example:** diff --git a/website/docs/tutorial/get-started/setup-environment-windows.md b/website/docs/tutorial/get-started/setup-environment-windows.md new file mode 100644 index 00000000..52cc6bcc --- /dev/null +++ b/website/docs/tutorial/get-started/setup-environment-windows.md @@ -0,0 +1,365 @@ +--- +slug: /tutorial/get-started/setup-environment-windows +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Windows prerequisites + +This page walks you through installing the software BlueScript needs on **Windows**. + +## Node.js and Visual C++ Build Environment {#nodejs-and-visual-c-build-environment} + +`@bscript/cli` depends on native Node.js add-ons (for example `serialport` and Bluetooth libraries). On Windows, `npm install -g @bscript/cli` uses **node-gyp**, which compiles those add-ons and requires a **Visual C++ Build Environment** in addition to Node.js. + +Install **both** before running `npm install -g @bscript/cli`. + +### Node.js {#nodejs} + +**What it is:** The JavaScript runtime that powers `npm` and the BlueScript CLI. + +**Version:** v20 or later (LTS recommended). + + + + +1. Download the **LTS** installer from [nodejs.org](https://nodejs.org/). +2. Run the installer and accept the defaults. The option to **add Node.js to PATH** is enabled by default — leave it on. +3. Close any open terminals and open a **new** one. + + + + +If [winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/) is available: + +```powershell +winget install OpenJS.NodeJS.LTS +``` + +Then open a **new** terminal. + + + + +If [Chocolatey](https://chocolatey.org/install) is installed: + +```powershell +choco install nodejs-lts -y +``` + +Then open a **new** terminal. + + + + +**Verify:** + +```powershell +node --version +npm --version +``` + +You should see version numbers (for example `v22.x.x` and `10.x.x`), not "command not found". + +### Visual C++ Build Environment {#visual-c-build-environment} + +**What it is:** Microsoft’s C++ compiler and Windows SDK. **node-gyp** uses them to build native Node.js modules during `npm install`. + +**You do not need to add anything to PATH manually** — the installer registers the build tools with Visual Studio’s locator, which node-gyp finds automatically. + + + + +1. Download [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) (free). +2. Run the installer. On the **Workloads** tab, check **"Desktop development with C++"** (C++ によるデスクトップ開発). +3. Click **Install** and wait until it finishes (several GB; may take a while). +4. Open a **new** terminal. + +You can use full **Visual Studio** (Community edition is fine) instead of Build Tools, with the same **"Desktop development with C++"** workload. + + + + +If Chocolatey is installed: + +```powershell +choco install visualstudio2022buildtools --package-parameters "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended" -y +``` + +Then open a **new** terminal. + + + + +**Verify:** There is no single `cl` command guaranteed on PATH in a normal terminal. The practical check is to install the CLI (after Node.js and the build tools are installed): + +```powershell +npm install -g @bscript/cli +bscript --version +``` + +If `npm install` fails with `gyp ERR! find VS`, the C++ workload is missing or the terminal was opened before installation finished — reinstall the workload and use a **new** terminal. + +--- + +## Git {#git} + +**What it is:** Version control used by ESP-IDF setup to clone repositories. + +**Required for:** `bscript board setup esp32` only. + + + + +1. Download [Git for Windows](https://git-scm.com/download/win). +2. Run the installer. When asked about **Adjusting your PATH environment**, choose **"Git from the command line and also from 3rd-party software"** so `git` works in PowerShell and Command Prompt. +3. Other options can stay at defaults. Open a **new** terminal after installation. + + + + +```powershell +winget install Git.Git +``` + +Open a **new** terminal. If `git` is not found, re-run the official installer and ensure PATH integration is enabled. + + + + +```powershell +choco install git -y +``` + +Open a **new** terminal. + + + + +**Verify:** + +```powershell +git --version +``` + +--- + +## Python 3 {#python-3} + +**What it is:** A programming language runtime. ESP-IDF’s Windows installer (`install.bat`) uses Python to set up its own tool environment. + +**Required for:** `bscript board setup esp32` (install **before** that command). + +**Version:** 3.8 or later; 3.11+ recommended. + + + + +1. Download Python from [python.org/downloads/windows](https://www.python.org/downloads/windows/). +2. Run the installer. At the bottom of the first screen, enable **"Add python.exe to PATH"** — this is important. +3. Choose **Install Now** (or **Customize** if you prefer). Open a **new** terminal after installation. + + + + +```powershell +winget install Python.Python.3.12 +``` + +Open a **new** terminal. + + + + +```powershell +choco install python -y +``` + +Open a **new** terminal. + + + + +**Verify:** + +```powershell +python --version +``` + +If `python` is not found, try: + +```powershell +python3 --version +``` + +BlueScript accepts either `python` or `python3` on PATH. + +--- + +## MinGW-w64 {#mingw-w64} + +**What it is:** A GCC-based C/C++ toolchain for Windows (`gcc`, `ar`, and often `mingw32-make`). + +**Required for:** [Host runtime](../guides/try-without-microcontroller.md) — `bscript board setup host` compiles native code with **MinGW’s** `gcc` and `mingw32-make`, not Visual Studio’s compiler. + +:::info Visual C++ vs MinGW +- **Visual C++ Build Environment** → needed for `npm install` (Node native modules). +- **MinGW-w64** → needed for `bscript board setup host` (BlueScript host runtime). + +They serve different purposes; host development on Windows needs **both**. +::: + + + + +[MSYS2](https://www.msys2.org/) provides a maintained MinGW-w64 environment. + +1. Download and run the MSYS2 installer from [msys2.org](https://www.msys2.org/). +2. Open **MSYS2 UCRT64** from the Start menu (not the plain "MSYS" shell). +3. Update the package database (first time only): + +```bash +pacman -Syu +``` + +Close the window when prompted, reopen **MSYS2 UCRT64**, and run `pacman -Syu` again if needed. + +4. Install the toolchain and make: + +```bash +pacman -S --needed mingw-w64-ucrt-x86_64-toolchain mingw-w64-ucrt-x86_64-make +``` + +5. Add MinGW to your **Windows** PATH (not only inside MSYS2): + + - Typical folder: `C:\msys64\ucrt64\bin` (adjust if you installed MSYS2 elsewhere). + - **Settings → System → About → Advanced system settings → Environment Variables** + - Under **User variables** or **System variables**, edit **Path**, click **New**, paste the `ucrt64\bin` path, and confirm. + +6. Open a **new** PowerShell or Command Prompt window. + + + + +```powershell +choco install mingw -y +``` + +Chocolatey usually adds MinGW’s `bin` folder to PATH. Open a **new** terminal. + +If `gcc -dumpmachine` does not contain `mingw`, use the MSYS2 method instead. + + + + +**Verify** (in PowerShell or Command Prompt, not only inside MSYS2): + +```powershell +gcc --version +ar --version +mingw32-make --version +``` + +For host setup, `gcc` must be MinGW (not another vendor). Check with: + +```powershell +gcc -dumpmachine +``` + +The output should include `mingw` (for example `x86_64-w64-mingw32`). + +--- + +## make or mingw32-make {#make-or-mingw32-make} + +**What it is:** A build automation tool. ESP-IDF uses `make` or `mingw32-make` when compiling firmware. + +**Required for:** `bscript board setup esp32` and related ESP32 builds. + +:::tip Already installed? +If you followed [MinGW-w64](#mingw-w64) via MSYS2 with `mingw-w64-ucrt-x86_64-make`, **`mingw32-make` is already available** — run the verify commands below and skip a separate make install unless `make` is also required by your workflow. +::: + +BlueScript accepts **either** `make` **or** `mingw32-make` on PATH for ESP32. + + + + +In **MSYS2 UCRT64**: + +```bash +pacman -S make +``` + +Or for `mingw32-make` only: + +```bash +pacman -S mingw-w64-ucrt-x86_64-make +``` + +Add `C:\msys64\ucrt64\bin` (and optionally `C:\msys64\usr\bin` if you installed `make` from the `make` package) to Windows PATH. Open a **new** terminal. + + + + +Install GNU make: + +```powershell +choco install make -y +``` + +Open a **new** terminal. + + + + +**Verify:** + +```powershell +make --version +``` + +If `make` is not found: + +```powershell +mingw32-make --version +``` + +At least one of these must succeed before `bscript board setup esp32`. + +--- + +## USB-to-UART drivers {#usb-to-uart-drivers} + +**When you need this:** Your ESP32 does not show up as a COM port when you run `bscript board flash-runtime esp32`. + +Many ESP32 boards use a USB–serial bridge chip. Windows needs a driver before the port appears in Device Manager. + +1. Identify the chip on your board (common types: **CP2102**, **CH340**, **FT232**). +2. Install the matching driver: + - [CP210x (Silicon Labs)](https://www.silabs.com/software-and-tools/usb-to-uart-bridge-vcp-drivers) + - [FTDI VCP](https://ftdichip.com/drivers/vcp-drivers/) + - CH340: search for "CH340 driver Windows" from your board vendor or [WCH](http://www.wch-ic.com/downloads/CH341SER_EXE.html) +3. Connect the board via USB. Open **Device Manager** (Win + X → Device Manager). +4. Under **Ports (COM & LPT)**, you should see something like **USB Serial Port (COM3)**. Note the COM number — the CLI lists it when flashing. + +If the port still does not appear, try another USB cable (some cables are charge-only) or a different USB port. + +See also [Establish Serial Connection with ESP32](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/get-started/establish-serial-connection.html) in the ESP-IDF documentation. + +--- + +## Troubleshooting + +| Problem | What to try | +| :--- | :--- | +| `'node' is not recognized` | Reinstall Node.js with PATH enabled; open a new terminal | +| `gyp ERR! find VS` during `npm install` | Install **Desktop development with C++** workload; new terminal | +| `'python' is not recognized` | Reinstall Python with **Add to PATH**; try `python3` | +| `'gcc' is not recognized` | Add MSYS2 `ucrt64\bin` to PATH; new terminal | +| `gcc` found but host setup fails | Run `gcc -dumpmachine` — must show `mingw`, not `msvc` | +| `'make' is not recognized` | Install `make` or ensure `mingw32-make` is on PATH | +| ESP32 COM port missing | Install USB-to-UART driver; check cable and Device Manager | + +--- diff --git a/website/docs/tutorial/get-started/setup-environment.md b/website/docs/tutorial/get-started/setup-environment.md index 70d00368..fbc0b488 100644 --- a/website/docs/tutorial/get-started/setup-environment.md +++ b/website/docs/tutorial/get-started/setup-environment.md @@ -1,31 +1,72 @@ +import OsTabs from '@site/src/components/OsTabs'; +import TabItem from '@theme/TabItem'; + # Set up your environment -:::danger macOS Only -Currently, BlueScript strictly requires **macOS**. Windows and Linux support is under development. +:::danger Linux not supported +BlueScript supports **macOS** and **Windows**. **Linux is not supported** at this time. ::: In this guide, we will install the BlueScript CLI and flash the runtime environment to your ESP32 microcontroller. ## Prerequisites -Before we begin, ensure you have the following: +### Hardware + +- **Host PC:** macOS or Windows +- **Microcontroller:** An ESP32 development board (e.g., ESP32-DevKitC) +- **USB cable** to connect your host PC and the microcontroller + +### Software + + + + +- [Node.js](https://nodejs.org/) v20 or later +- [Homebrew](https://brew.sh/) +- **Git** (`git`) +- **Python 3** (`python` or `python3`) +- **make** (`make`) + + + + +- [Node.js](https://nodejs.org/) v20 or later +- **Visual C++ Build Environment** (required for `npm install -g @bscript/cli`; see [Windows prerequisites](./setup-environment-windows.md#nodejs-and-visual-c-build-environment)) +- **Git** (`git`) +- **Python 3** (`python` or `python3`) +- **make** or **mingw32-make** + +For step-by-step installation instructions, see **[Windows prerequisites](./setup-environment-windows.md)**. + + + -- **Hardware:** - - **Host PC:** A laptop running **macOS** (Windows and Linux are currently **not** supported). - - **Micocontroller:** An ESP32 development board (e.g., ESP32-DevKitC) - - **USB cable** to connect your host PC and the microcontroller -- **Software:** - - [Node.js](https://nodejs.org/) (v20 or later) installed on your host PC. --- ## Step 1: Install the CLI -BlueScript provides a command-line interface (CLI) to manage projects and communicate with your device. Install it globally using npm: +BlueScript provides a command-line interface (CLI) to manage projects and communicate with your device. + + + + +```bash +npm install -g @bscript/cli +``` + + + + +Install the [Visual C++ Build Environment](./setup-environment-windows.md#nodejs-and-visual-c-build-environment) first, then: ```bash npm install -g @bscript/cli ``` + + + Verify the installation: ```bash @@ -58,7 +99,7 @@ Connect your ESP32 to your computer via USB and flash the runtime: bscript board flash-runtime esp32 ``` -The CLI will display a list of detected serial ports. Use the arrow keys to select the one corresponding to your ESP32 (e.g., /dev/tty.usbserial-xxxx). +The CLI will display a list of detected serial ports. Use the arrow keys to select the one corresponding to your ESP32 (e.g., /dev/tty.usbserial-xxxx on macOS or COMX on Windows). :::info Device not found? If your device does not appear in the list, you may need to install USB-to-UART drivers (e.g., [CP210x](https://www.silabs.com/software-and-tools/usb-to-uart-bridge-vcp-drivers) or [FTDI](https://ftdichip.com/drivers/vcp-drivers/)). diff --git a/website/docs/tutorial/guides/repl.md b/website/docs/tutorial/guides/repl.md index a47c52f2..5fdae3e5 100644 --- a/website/docs/tutorial/guides/repl.md +++ b/website/docs/tutorial/guides/repl.md @@ -6,23 +6,6 @@ sidebar_label: REPL & Notebook After your program runs on the device, you can send **more BlueScript code** without editing files and running `bscript project run` again. -The easiest way is the **Notebook**: a browser UI where you run code in cells. You can also use a **REPL** in the terminal (one line at a time). - -## Which mode should I use? - -| Mode | Command | When to use it | -| :--- | :--- | :--- | -| **Notebook** | `bscript project run --with-notebook` | You have a project and want to try code in cells (recommended) | -| **Project REPL** | `bscript project run --with-repl` | Same as above, but you prefer the terminal | -| **Global REPL** | `bscript repl -b esp32` or `bscript repl -b host` | No project yet—language syntax only (use `-d` to specify device name on ESP32) | -| **Normal run** | `bscript project run` | You are writing the full app in `index.bs` | - -**Notebook vs REPL:** The Notebook supports multi-line cells (**Shift+Enter** to run) and shows output on the side. The REPL accepts **one line per Enter**. - -Hardware libraries (e.g. GPIO) work in the Notebook and Project REPL only if you ran `bscript project install` in that project. The global REPL cannot use them. - ---- - ## Try the Notebook This walkthrough continues from [Blink LED](../get-started/blink-led.md) (GPIO package installed, LED wired). @@ -70,7 +53,7 @@ console.log("LED off"); led.write(PinLevel.Low); ``` -Compile errors appear under the cell. Press **`Ctrl+D`** in the terminal to exit. +Press **`Ctrl+D`** in the terminal to exit. --- @@ -82,7 +65,7 @@ Compile errors appear under the cell. Press **`Ctrl+D`** in the terminal to exit bscript project run --with-repl ``` -After `index.bs` runs, type one line at the `>` prompt. Installed packages (e.g. `gpio`) can be imported here. Exit with **`Ctrl+D`**. Do not combine `--with-repl` and `--with-notebook`. +After `index.bs` runs, type one line at the `>` prompt. Installed packages (e.g. `gpio`) can be imported here. Exit with **`Ctrl+D`**. ### Global REPL @@ -94,18 +77,9 @@ bscript repl -b host Use this for quick syntax checks without a project. **GPIO and other installed libraries are not available.** Exit with **`Ctrl+D`**. -:::note Host runtime -The host runtime must be set up first. See [Try Without Microcontroller](./try-without-microcontroller.md) for setup. -::: - -:::note -A global Notebook (without a project) is planned for a future release. -::: - --- ## Good to know * **Device name:** Notebook and Project REPL connect using `deviceName` in `bsconfig.json`. Global REPL uses the `-d` flag instead. The name must match what was set during `bscript board flash-runtime`. See [bsconfig.json](../../reference/bsconfig.md#esp32-fields). * Code on the device is **lost after a reboot**—run the command again to re-upload. -* Variables and functions from earlier cells or REPL lines **stay available** until you disconnect. diff --git a/website/docs/tutorial/guides/try-without-microcontroller.md b/website/docs/tutorial/guides/try-without-microcontroller.md index 011d6e70..0ea85cc6 100644 --- a/website/docs/tutorial/guides/try-without-microcontroller.md +++ b/website/docs/tutorial/guides/try-without-microcontroller.md @@ -2,6 +2,9 @@ sidebar_label: Try Without Microcontroller --- +import OsTabs from '@site/src/components/OsTabs'; +import TabItem from '@theme/TabItem'; + # Try Without Microcontroller BlueScript is designed primarily for microcontroller development (ESP32). @@ -9,14 +12,35 @@ If you do not have hardware yet—or want a faster path for language and compile For the main ESP32 workflow, see [Get Started](../get-started/introduction.md). -:::danger macOS Only -The host runtime currently requires **macOS**. Windows and Linux support is under development. +:::danger Linux not supported +The host runtime supports **macOS** and **Windows**. **Linux is not supported** at this time. ::: ## Prerequisites + + + - [Node.js](https://nodejs.org/) v20+ -- C compiler toolchain (`cc` and `make`) +- C compiler toolchain: **Xcode Command Line Tools** (`cc` and `make`) +- **Git** (`git`) + +Install Command Line Tools if needed: + +```bash +xcode-select --install +``` + + + + +- [Node.js](https://nodejs.org/) v20+ +- **Visual C++ Build Environment** (for `npm install -g @bscript/cli`; see [Windows prerequisites for ESP32](../get-started/setup-environment-windows.md#nodejs-and-visual-c-build-environment)) +- [MinGW-w64](https://www.mingw-w64.org/) on `PATH`: `gcc` and `mingw32-make` +- **Git** (`git`) + + + ## Quickstart @@ -28,6 +52,8 @@ If you have not installed the CLI yet: npm install -g @bscript/cli ``` +On Windows, install the [Visual C++ Build Environment](../get-started/setup-environment-windows.md#nodejs-and-visual-c-build-environment) before running this command. + ### 2. Set up the host runtime ```bash diff --git a/website/src/components/OsTabs/index.tsx b/website/src/components/OsTabs/index.tsx new file mode 100644 index 00000000..c5c6a331 --- /dev/null +++ b/website/src/components/OsTabs/index.tsx @@ -0,0 +1,54 @@ +import {useState} from 'react'; +import useIsomorphicLayoutEffect from '@docusaurus/useIsomorphicLayoutEffect'; +import Tabs from '@theme/Tabs'; +import type {Props as TabsProps} from '@theme/Tabs'; + +const GROUP_ID = 'os'; +const STORAGE_KEY = `docusaurus.tab.${GROUP_ID}`; + +export type OsTabValue = 'macos' | 'windows'; + +export function detectOsTabValue(): OsTabValue { + if (typeof navigator === 'undefined') { + return 'macos'; + } + + try { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === 'macos' || stored === 'windows') { + return stored; + } + } catch { + // ignore + } + + const ua = navigator.userAgent; + if (/Win/i.test(ua)) { + return 'windows'; + } + if (/Mac/i.test(ua)) { + return 'macos'; + } + + return 'macos'; +} + +type OsTabsProps = Omit; + +export default function OsTabs({children, ...props}: OsTabsProps) { + const [defaultValue, setDefaultValue] = useState(null); + + useIsomorphicLayoutEffect(() => { + setDefaultValue(detectOsTabValue()); + }, []); + + if (defaultValue === null) { + return