diff --git a/README.md b/README.md index ee846db..906f4c2 100644 --- a/README.md +++ b/README.md @@ -1,177 +1,66 @@ # CardputerZero AppBuilder -Online build system & desktop development toolkit for [M5CardputerZero](https://docs.m5stack.com/) applications. Submit any public Git repository and get a ready-to-install `.deb` package — no local toolchain required. Or develop locally with the built-in emulator. +Build system & developer toolkit for [M5CardputerZero](https://docs.m5stack.com/) +applications. Submit any public Git repository and get a ready-to-install +`.deb` package — no local toolchain required — then publish it to the +CardputerZero AppStore with the Python `czdev` CLI. -## Desktop Emulator +- **`czdev`** — a small, pure-**Python 3** CLI to authenticate with GitHub and + publish / unpublish `.deb` packages. No Rust / cargo toolchain needed. +- **CI online build** — a GitHub Actions workflow that cross-compiles any repo + to an aarch64 `.deb`. +- **Examples** — a gallery of ready-to-build apps (C/LVGL, SDL2, Qt, Python, + Rust) under [`examples/`](examples/). -The `czdev` CLI includes a desktop emulator that renders the CardputerZero 320x170 LCD inside a keyboard skin. Develop and test apps without a physical device. - -### NC2000 (文曲星 PDA Emulator) - -```bash -cargo run -p czdev --release -- run apps/nc2000 -``` - -![NC2000 Emulator](docs/nc2000_emu.png) - -### APPLauncher (Home Screen) - -```bash -cargo run -p czdev --release -- run apps/applaunch/ -``` - -![APPLauncher Emulator](docs/emu-applauncher.png) - -### Hello CardputerZero (Example App) - -```bash -cargo run -p czdev --release -- run examples/key_echo -``` - -![Hello Example](docs/emu-hello.png) - -## Quickstart — Desktop Dev +## Quickstart [中文](docs/QUICKSTART_ZH.md) | [日本語](docs/QUICKSTART_JA.md) -Get a 320x170 LVGL app running on your Mac or Linux machine in ~3 minutes — no CardputerZero device required. - -### 1. Prerequisites - -**macOS:** ```bash -brew install cmake pkg-config sdl2 sdl2_image sdl2_mixer freetype -``` - -**Linux (Debian/Ubuntu):** -```bash -sudo apt install -y build-essential cmake pkg-config \ - libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libfreetype-dev -``` - -**Windows:** MSYS2 MINGW64 shell. See [DESKTOP_DEV.md §4](docs/DESKTOP_DEV.md#4-windows-lvgl--emulator--known-issues-and-plan) for Windows-specific notes. - -You also need a recent Rust toolchain (for the emulator commands in `czdev`): -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -> **Note:** Publishing commands (`./czdev login/publish/unpublish/bump`) only require Python 3 — no Rust needed. - -### 2. Clone with submodules - -```bash -git clone --recursive git@github.com:m5stack/CardputerZero-AppBuilder.git +git clone https://github.com/CardputerZero/CardputerZero-AppBuilder.git cd CardputerZero-AppBuilder -``` -If you already cloned without `--recursive`: -```bash -git submodule update --init --recursive -``` +./czdev --help # works immediately with Python 3 +./czdev login # one-time GitHub device-flow login -### 3. Verify the environment - -```bash -cargo run -p czdev --release -- doctor +# From your app's project directory (must contain app-builder.json with a +# "store" section), after producing a .deb: +./czdev bump --deb build/my_app_1.0.0_arm64.deb # show next version +./czdev publish --deb build/my_app_1.0.1_arm64.deb # open a publish PR ``` -All required rows should be green. If anything is MISSING, the output shows the exact install command for your OS. - -### 4. Run the hello app - -```bash -cargo run -p czdev --release -- run examples/hello_cz -``` +Requirements: **Python 3**, **git**, **dpkg-deb**. -On first run this will: -1. Build the emulator (once, cached in `emulator/build/`). -2. Build the app into `.czdev/build/`. -3. Stage the resulting shared library into the emulator's `apps/` directory. -4. Launch the emulator with the app loaded via `dlopen`. +## The `czdev` CLI -### 5. Edit-run loop +`czdev` is the repo-root wrapper (`./czdev`) around the Python package in +[`scripts/czdev/`](scripts/czdev/). You can also run it as a module: ```bash -cargo run -p czdev --release -- watch examples/hello_cz +PYTHONPATH=scripts python3 -m czdev --help ``` -The watcher polls `src/`, `include/`, `assets/`, `CMakeLists.txt` and `app-builder.json`. Any change triggers a rebuild and relaunches the emulator. +| Command | What it does | +|---|---| +| `czdev login` | GitHub OAuth **device flow**; stores a token at `~/.czdev/credentials`. | +| `czdev logout` | Remove the stored GitHub credentials. | +| `czdev bump [--deb PATH]` | Print the next patch version for a package (reads the version from the `.deb`). Defaults to `./build/*.deb`. | +| `czdev publish [--deb PATH]` | Validate the `.deb` and open a publish PR against the `packages` repo. Defaults to `./build/*.deb`. | +| `czdev unpublish NAME --version V [--arch arm64]` | Open a PR that removes a published package version. | -### 6. Writing your own app +### Ownership model -Copy `examples/hello_cz/` and edit `src/hello_cz.c`. The ABI: +Package names are **first-come, first-served** by GitHub login: whoever first +publishes a package name owns it. Afterwards only that uploader (or a repo +admin) can publish new versions or unpublish it. The uploader's login is +recorded in the release manifest and enforced server-side — there is no +email-address matching. -```c -#include - -void app_main(lv_obj_t *parent) { - lv_obj_t *label = lv_label_create(parent); - lv_label_set_text(label, "your UI here"); - lv_obj_center(label); -} - -void app_event(int type, void *data) { - (void)type; (void)data; -} -``` - -The `CMakeLists.txt`: - -```cmake -cmake_minimum_required(VERSION 3.16) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../sdk/cmake") -include(CZApp) -cz_add_lvgl_app(my_app SOURCES src/my_app.c) -``` - -And the manifest (`app-builder.json`, see [docs/APP_BUILDER_JSON.md](docs/APP_BUILDER_JSON.md)): - -```json -{ - "package_name": "my_app", - "bin_name": "my_app", - "app_name": "My App", - "runtime": "lvgl-dlopen", - "lvgl_version": "9.5" -} -``` - -### 7. Shipping to a real device - -Build the aarch64 `.deb` via CI (trigger the `build-deb.yml` workflow), then deploy: - -```bash -cargo run -p czdev --release -- deploy \ - --host pi@192.168.50.150 \ - --deb path/to/my_app_arm64.deb -``` - -### 8. Publishing to the AppStore - -Publishing uses the Python-based `czdev` wrapper (no Rust toolchain needed): - -```bash -# Login to GitHub (one-time) -./czdev login - -# Check next version -./czdev bump --deb build/my_app_1.0.0_arm64.deb - -# Publish (version in deb must be newer than existing) -./czdev publish --deb build/my_app_1.0.1_arm64.deb - -# Remove your own package -./czdev unpublish my_app --version 1.0.1 -``` - -Requirements: Python 3, `git`, `git-lfs`, `dpkg-deb`. - -#### Publish Workflow +### `czdev publish` — end-to-end flow ``` ┌──────────────────────────────────────────────────────────────────────────────┐ -│ czdev publish — End-to-End Flow │ +│ czdev publish — End-to-End Flow │ └──────────────────────────────────────────────────────────────────────────────┘ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌─────────────┐ @@ -179,23 +68,24 @@ Requirements: Python 3, `git`, `git-lfs`, `dpkg-deb`. └─────────┘ └─────────┘ └──────────┘ └─────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ - czdev login czdev build czdev publish Admin merges - (GitHub OAuth or CI workflow --deb xxx.deb the PR - Device Flow) ─▶ .deb artifact ┌────────────┐ │ + czdev login CI workflow czdev publish Admin merges + (GitHub OAuth ─▶ .deb artifact --deb xxx.deb the PR + Device Flow) ┌────────────┐ │ │ │ Preflight: │ ▼ ▼ │ • .desktop │ ┌───────────┐ - Token saved │ • email ✓ │ │ RELEASE │ - ~/.config/ │ • version ✓│ └───────────┘ - czdev/token │ • size ✓ │ │ - └─────┬──────┘ ▼ - │ APT repo updated - ▼ App live in Store - Fork + Push - ─▶ PR created + Token saved │ • version ✓│ │ RELEASE │ + ~/.czdev/ │ • size ✓ │ └───────────┘ + credentials │ • no root │ │ + │ └─────┬──────┘ ▼ + ▼ │ APT repo updated + Verified emails ▼ App live in Store + (user:email) Upload .deb to + GitHub Release + ─▶ metadata PR on packages +``` -─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ - +``` Timeline: You (Developer) czdev GitHub (Remote) @@ -205,54 +95,50 @@ Requirements: Python 3, `git`, `git-lfs`, `dpkg-deb`. │ │◀── access token ───────────│ │ │ │ │── czdev publish ───────────▶│ │ - │ │── validate .deb ──────────▶│ (check ver) - │ │── fork packages repo ─────▶│ - │ │── git push (LFS) ─────────▶│ - │ │── POST /pulls ────────────▶│ - │◀── PR URL ─────────────────│ │ + │ │── validate .deb ───────────│ (version/size/root) + │ │── upload .deb to Release ──▶│ + │ │── commit metadata + PR ───▶│ + │◀── PR URL ──────────────────│ │ │ │ │ │ │ Admin reviews & merges - │ │ │ - │ │ CI rebuilds APT index + │ │ CI rebuilds the APT index │ │ │ │◀───────────────────── App available in AppStore ─────────│ │ │ ``` -## Install czdev +The `.deb` binary is uploaded to a GitHub Release; only small metadata +(`meta.json`, screenshots, icon, release manifest) is committed in the publish +PR. See [`docs/APP_BUILDER_JSON.md`](docs/APP_BUILDER_JSON.md) for the +`store` section that supplies the AppStore listing (title, summary, +screenshots, categories, …). -`czdev` is split into two parts: +## Getting a `.deb` -**Publishing commands** (`login`, `logout`, `bump`, `publish`, `unpublish`) — pure Python, no compilation needed: +You don't need a local ARM toolchain — building happens in CI. -```bash -git clone --recursive git@github.com:m5stack/CardputerZero-AppBuilder.git -cd CardputerZero-AppBuilder -./czdev --help # works immediately with Python 3 -``` - -**Emulator commands** (`doctor`, `list`, `build`, `run`, `watch`, `deploy`) — require Rust toolchain: +### Option A — online build from any repo URL -```bash -cargo build --release -p czdev -# Binary at: target/release/czdev -cargo run -p czdev --release -- run examples/hello_cz -``` - -## CI Online Build - -1. Go to **Actions** > **Build DEB Package** > **Run workflow** +1. Go to **Actions** → **Build DEB Package** → **Run workflow**. 2. Fill in the form: | Field | Required | Example | Description | |-------|----------|---------|-------------| - | **Repository URL** | Yes | `https://github.com/eggfly/M5CardputerZero-UserDemo.git` | Any public HTTP Git URL (GitHub, GitCode, Gitee, etc.) | + | **Repository URL** | Yes | `https://github.com/CardputerZero/M5CardputerZero-Launcher.git` | Any public HTTP Git URL (GitHub, GitCode, Gitee, …) | | **Branch** | No | `master` | Leave empty to use the repository's default branch | -3. The system automatically scans for `app-builder.json` files in the repo, builds each project, and packages them as `.deb` -4. Download the `.deb` from the workflow run's **Artifacts** section +3. The workflow scans for `app-builder.json` files, builds each project, and + packages them as `.deb`. +4. Download the `.deb` from the run's **Artifacts** section. + +### Option B — build the bundled examples + +Pushing to this repo runs **Build APPLaunch .deb packages** +(`.github/workflows/build-debs.yml`), which builds every app under +[`examples/`](examples/) and attaches the `.deb`s as artifacts. Prebuilt +examples are also kept under [`dist/`](dist/). -### Install on Device +### Install on device ```bash scp _arm64.deb pi@:/tmp/ @@ -261,19 +147,21 @@ ssh pi@ "sudo dpkg -i /tmp/_arm64.deb" ## Architecture -The CI pipeline runs on x86_64 and **cross-compiles** to ARM64 (aarch64) using the `aarch64-linux-gnu-` toolchain — the same approach used by the [M5Stack_Linux_Libs](https://github.com/m5stack/M5Stack_Linux_Libs) SDK. +The CI pipeline runs on x86_64 and **cross-compiles** to ARM64 (aarch64) using +the `aarch64-linux-gnu-` toolchain — the same approach used by the +[M5Stack_Linux_Libs](https://github.com/m5stack/M5Stack_Linux_Libs) SDK. ``` User Input (repo URL) │ ▼ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ - │ git clone │────▶│ discover │────▶│ scons build │────▶│ dpkg-deb │ + │ git clone │────▶│ discover │────▶│ build │────▶│ dpkg-deb │ │ --recursive │ │ app-builder │ │ (x86→arm64) │ │ packaging │ └──────────────┘ │ .json │ └──────────────┘ └──────────────┘ └──────────────┘ │ - │ ▼ - N projects N × .deb artifacts + │ ▼ + N projects N × .deb artifacts (parallel) (download) ``` @@ -288,7 +176,7 @@ Generated packages follow the [APPLaunch packaging conventions](https://github.c │ ├── postinst (enable & start systemd service) │ └── prerm (stop & disable service) ├── lib/systemd/system/ -│ └── .service +│ └── .service (runs as a non-root user; root services are rejected) └── usr/share/APPLaunch/ ├── applications/.desktop ├── bin/ @@ -300,10 +188,16 @@ Generated packages follow the [APPLaunch packaging conventions](https://github.c ## Troubleshooting -- **`emulator submodule not checked out`** — you forgot `--recursive`. Fix: `git submodule update --init --recursive`. -- **LVGL link errors about unresolved symbols** — expected in the app library; resolved at `dlopen` time by the emulator. -- **`indev_read_cb is not registered` warnings** — benign; the emulator falls back to a default keypad indev. -- **macOS: `Library not loaded: @rpath/SDL2.framework/...`** — re-run `czdev doctor` and install what it reports. +- **`czdev: python3 not found`** — install Python 3 and re-run. +- **`app-builder.json not found`** — run `czdev publish` from your app's + project directory; the file must contain a `store` section with at least one + 320×170 screenshot. +- **`not the owner of `** — that package name is already owned by + another GitHub account (first-come, first-served). Pick a different name or + ask the owner / a repo admin. +- **Publish rejected: service runs as root** — apps must not run as root. Pin + the bundled systemd service to a non-root user (`User=` in the + `[Service]` section) and rebuild the `.deb`. ## Related Projects diff --git a/czdev b/czdev new file mode 100755 index 0000000..d67b84a --- /dev/null +++ b/czdev @@ -0,0 +1,26 @@ +#!/usr/bin/env bash +# +# czdev — CardputerZero developer CLI (Python). +# +# Thin launcher for the pure-Python package in scripts/czdev. It puts the +# scripts/ directory on PYTHONPATH and runs the package as a module, so the +# package's relative imports (`from .auth import login`, …) resolve correctly. +# +# Commands: login · logout · bump · publish · unpublish +# Requirements: Python 3, git, dpkg-deb. (No Rust / cargo toolchain needed.) +# +# Usage: +# ./czdev --help +# ./czdev login +# ./czdev publish --deb build/my_app_1.0.1_arm64.deb +set -euo pipefail + +HERE="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" + +if ! command -v python3 >/dev/null 2>&1; then + echo "czdev: python3 not found on PATH. Install Python 3 and retry." >&2 + exit 1 +fi + +exec env PYTHONPATH="${HERE}/scripts${PYTHONPATH:+:${PYTHONPATH}}" \ + python3 -m czdev "$@" diff --git a/docs/APP_BUILDER_JSON.md b/docs/APP_BUILDER_JSON.md index 532fe7e..f623e4a 100644 --- a/docs/APP_BUILDER_JSON.md +++ b/docs/APP_BUILDER_JSON.md @@ -4,12 +4,16 @@ Every directory in an application repository that should be discovered, built and packaged contains one `app-builder.json` at its root. The file plays two roles: -1. **Packaging** — feeds the CI `.deb` pipeline (existing behaviour). -2. **Desktop dev loop** — tells `czdev` how to build and run the app inside the - emulator (new fields, optional). +1. **Packaging** — feeds the CI `.deb` pipeline. +2. **AppStore listing** — the optional `store` section supplies the metadata + that `czdev publish` (and the web portal at + [dev.cardputer.cc](https://dev.cardputer.cc)) put on the store page: title, + summary, screenshots, categories, icon, … -Fields added for the desktop loop are all optional; a file that only contains -the packaging fields keeps working exactly as before. +> The older desktop-emulator fields (`runtime`, `entry`, `event_entry`, +> `lvgl_version`, `caps`, `assets`) are still accepted for back-compat but are +> no longer consumed — the Rust `czdev` emulator / `czdev run` loop has been +> removed. New apps only need the packaging fields plus a `store` section. ## Full schema @@ -19,69 +23,67 @@ the packaging fields keeps working exactly as before. "package_name": "hello_cz", // Debian package name (lowercase, dash) "version": "0.1", // SemVer-ish; goes into control file "app_name": "Hello CZ", // Display name in APPLaunch - "bin_name": "hello_cz", // Shared-object basename (no lib prefix) - "description": "Desktop dev hello app", + "bin_name": "hello_cz", // executable / shared-object basename + "description": "Hello app", - // ── Desktop dev (new, all optional) ────────────────────────────── - "runtime": "lvgl-dlopen", // "lvgl-dlopen" (default) | "legacy-deb-only" - "entry": "app_main", // C symbol name, default "app_main" - "event_entry": "app_event", // optional, default "app_event" - "lvgl_version": "9.5", // host must match on major.minor - "caps": [ // capabilities the app declares it uses - "keyboard", - "audio", - "network" - ], - "assets": [ // paths relative to app dir; copied next - "assets/fonts/", // to the built library under its app dir - "assets/sprites/" - ] + // ── AppStore listing (used by `czdev publish` + web portal) ────── + "store": { + "summary": "One-line summary", // shown in lists + "description": "Longer detail-page text",// optional; falls back to summary + "categories": ["Games"], // optional; up to a few tags + "screenshots": ["screenshots/main.png"], // >=1 required; 320×170 PNG(s) + "icon": "packaging/icon.png", // optional; square PNG + "license": "MIT", // optional + "source_repo": "https://github.com/you/my_app", // optional + "author": { "github": "you" }, // optional; defaults to uploader + "permissions": [], // optional; declared permissions + "locales": {} // optional; localized title/summary + }, + + // ── Legacy desktop-emulator fields (optional, no longer consumed) ─ + "runtime": "lvgl-dlopen", // back-compat only + "entry": "app_main", + "event_entry": "app_event", + "lvgl_version": "9.5", + "caps": [], + "assets": [] } ``` -## Field reference +## Packaging fields -| Field | Required | Type | Default | Used by | +| Field | Required | Type | Default | Notes | |---|---|---|---|---| -| `package_name` | yes | string | — | deb, czdev | -| `version` | no | string | `"0.1"` | deb | -| `app_name` | no | string | same as `package_name` | deb, czdev | -| `bin_name` | yes | string | — | deb, czdev | -| `description` | no | string | `""` | deb | -| `runtime` | no | `"lvgl-dlopen"` \| `"legacy-deb-only"` | `"lvgl-dlopen"` | czdev | -| `entry` | no | string | `"app_main"` | czdev, emulator | -| `event_entry` | no | string | `"app_event"` | czdev, emulator | -| `lvgl_version` | no | string | `"9.5"` | czdev (refuses mismatch) | -| `caps` | no | string[] | `[]` | czdev (future: sandboxing) | -| `assets` | no | string[] | `[]` | czdev (stage next to lib) | - -## Runtime modes +| `package_name` | yes | string | — | Debian package name (lowercase, dash) | +| `version` | no | string | `"0.1"` | goes into the control file | +| `app_name` | no | string | same as `package_name` | display name; also the store title | +| `bin_name` | yes | string | — | executable / shared-object basename | +| `description` | no | string | `""` | control-file description | -- **`lvgl-dlopen`** — the app is a shared library that exports the `entry` and - optional `event_entry` symbols described in `cz_app.h`. `czdev run` loads it - into the emulator via `dlopen` / `LoadLibrary`. This is the default for new - apps. -- **`legacy-deb-only`** — the project is a standalone executable (Framebuffer, - SDL, Qt, Python, …). `czdev` skips it during `run`; the CI `.deb` pipeline - still produces a package. Use this for examples in - `CardputerZero-Examples/` that are not LVGL. +## Store listing — `store` -Apps that omit `runtime` are treated as `lvgl-dlopen` iff they export -`app_main`; otherwise `czdev` prints a clear error and suggests setting -`"runtime": "legacy-deb-only"`. +Read by `czdev publish` and the web portal to build the AppStore page. At +least one 320×170 screenshot is required to publish. -## Capabilities (`caps`) +| Field | Required | Type | Notes | +|---|---|---|---| +| `store.summary` | recommended | string | one-line summary shown in lists | +| `store.description` | no | string | detail-page text; falls back to `summary` | +| `store.categories` | no | string[] | category tags | +| `store.screenshots` | **yes** | string[] | ≥1 path, relative to the app dir; **320×170** PNG(s) | +| `store.icon` | no | string | square PNG path (else the deb's icon is used) | +| `store.license` | no | string | SPDX id, e.g. `MIT` | +| `store.source_repo` | no | string | public source URL | +| `store.author` | no | object | e.g. `{ "github": "you" }`; defaults to the uploader | +| `store.permissions` | no | string[] | declared permissions (metadata) | +| `store.locales` | no | object | localized `title` / `summary` per locale | -Reserved vocabulary (enforcement comes later): +The store title comes from the top-level `app_name`. -| Cap | Means | -|---|---| -| `keyboard` | Uses the 44-key physical keyboard / emulator skin | -| `audio` | Plays audio via ALSA on device, SDL_mixer in emulator | -| `network` | Reads hostname / IP / Wi-Fi state | -| `filesystem` | Writes into `/usr/share/APPLaunch` or the emu sandbox | -| `pty` | Spawns a PTY (terminal-style apps) | -| `process` | fork/exec of sub-processes | +## Legacy desktop-emulator fields -Today `caps` is metadata only; a future milestone uses it to decide which -HAL shims the emulator pre-loads. +`runtime`, `entry`, `event_entry`, `lvgl_version`, `caps`, `assets` were used by +the old Rust `czdev` emulator (`czdev run` / `watch`). That loop has been +removed, so these fields are **no longer consumed** — they are still accepted +(and passed through by CI discovery) for back-compat, but you can safely omit +them from new manifests. diff --git a/docs/DESKTOP_DEV.md b/docs/DESKTOP_DEV.md deleted file mode 100644 index 863a669..0000000 --- a/docs/DESKTOP_DEV.md +++ /dev/null @@ -1,180 +0,0 @@ -# Desktop Development for CardputerZero Apps - -This document describes how the **desktop emulator** lets you develop 320×170 -LVGL apps for M5 CardputerZero without having the physical device. It is the -contract between: - -- App authors writing `.dylib` / `.so` / `.dll` modules -- The emulator (`cardputer-zero-emu`) that loads them -- The real device (APPLaunch on aarch64 Linux) that loads the same sources - compiled natively - -The goal: **one source tree → same `.dylib/.so/.dll` on desktop, same `.so` -inside the `.deb` on the device — zero per-platform `#ifdef` in app code.** - ---- - -## 1. How LVGL is shared between emulator and app (all platforms) - -The emulator hosts a single LVGL instance. Apps are **loaded into the same -process** via `dlopen` / `LoadLibrary` and call LVGL directly — they do **not** -link their own LVGL. - -On macOS and Linux this works because: - -- `emulator.exe` links LVGL with `-Wl,-force_load` (mac) / `--whole-archive` - (Linux), so every LVGL symbol lives in the emulator's global symbol table. -- The app `.dylib/.so` is linked with `-undefined dynamic_lookup` (mac) / - `-Wl,--unresolved-symbols=ignore-all` (Linux) — LVGL references are left - unresolved at link time. -- `dlopen(RTLD_GLOBAL)` at runtime binds the app's LVGL references to the - emulator's copy. - -Windows needs a different shape (see §4) but the **result is the same**: one -LVGL instance, one display, one input group, one timer handler. - -On the real device, APPLaunch does the same thing with `dlopen` against its own -LVGL — the app `.so` behaves identically. - -### Rules for app authors - -1. **Do not call `lv_init()` or `lv_display_create()`.** The host already did. -2. **Your `lv_conf.h` must byte-match the emulator's.** v9.5, color depth 16, - same font set, same feature flags. Mismatch = segfault. Use the provided - `sdk/include/lv_conf.h` verbatim. -3. **Entry point is `app_main(lv_obj_t* parent)`.** Hang your UI off `parent`. -4. **Clean up in `app_event(CZ_EV_EXIT_REQUEST)`.** Delete widgets you own. -5. **Do not export C++ symbols across the boundary.** Only `extern "C"` - functions. C++ statics inside the library are fine. - ---- - -## 2. App ABI contract (`cz_app.h`) - -Every app exports exactly two C symbols: - -```c -// sdk/include/cz_app.h -#pragma once -#include - -#if defined(_WIN32) - #define CZ_APP_EXPORT __declspec(dllexport) -#else - #define CZ_APP_EXPORT __attribute__((visibility("default"))) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -CZ_APP_EXPORT void app_main(lv_obj_t *parent); -CZ_APP_EXPORT void app_event(int type, void *data); - -#ifdef __cplusplus -} -#endif - -enum { - CZ_EV_PAUSE = 1, // app is being backgrounded; persist state - CZ_EV_RESUME = 2, // app is foregrounded again - CZ_EV_EXIT_REQUEST = 3, // about to unload; free everything - CZ_EV_SIDE_KEY = 4, // data: (int*) side-button id (ESC/HOME/...) - // Future values are additive. Unknown types must be ignored by the app. -}; -``` - -**Why this shape:** - -- `app_main(parent)` — one UI entry point. `parent` is a full-screen container - already sized 320×170. The app never touches display / screen APIs. -- `app_event(int, void*)` — system notifications. Integer type + opaque data - keeps the ABI cheap to extend. Old apps that don't know a new type just - ignore it. -- No string-keyed events, no C++ types, no callbacks with ownership semantics. -- `lvgl_version` is pinned in `app-builder.json` — CI refuses to build if the - app's declared version and the host's version disagree on major/minor. - -Apps that only need UI provide `app_main` and leave `app_event` as a one-line -no-op. It is mandatory so the host can always call it. - ---- - -## 3. Why we use LVGL's API directly (no wrapper SDK) - -APPLaunch and UserDemo on the real device are written against LVGL v9 directly. -If the desktop emulator introduced its own wrapper, the two surfaces would -drift and every app would need double maintenance. LVGL v9's API is stable -within the 9.x line (we pin 9.5); we version-lock so that 9.6 adoption is an -explicit coordinated bump, not a silent breakage. - -We keep the host→app surface (§2) minimal precisely because LVGL already is -the SDK. - ---- - -## 4. Windows LVGL + emulator — known issues and plan - -Windows currently builds the emulator with `EMU_STATIC_APP=1` (the app is -static-linked into the exe). To reach the same "host + dlopen'd app" model as -mac/Linux we need to resolve these: - -| # | Issue | Root cause | Plan | -|---|---|---|---| -| 1 | PE requires all symbols resolved at link time | No equivalent of ELF `--unresolved-symbols=ignore-all` or Mach-O `-undefined dynamic_lookup` | Produce `lvgl.dll` + `liblvgl.dll.a` import lib; app links the import lib | -| 2 | LVGL global data not marked dllexport | v9 headers use `LV_ATTRIBUTE_EXTERN_DATA` inconsistently for fonts/styles/builtin tables | Apply a one-shot Windows export header that blankets `__declspec(dllexport/dllimport)` over the public API. Revisit on each LVGL upgrade. | -| 3 | MinGW `__attribute__((weak))` doesn't link | Known MinGW limitation | Already handled via macro in `emu_compat_win.h:9` | -| 4 | C++ runtime duplicated across DLLs | MinGW defaults to `-static-libstdc++`; EXE and app.dll each bring a copy | App ABI is `extern "C"` only (§2). LVGL is C. No C++ objects cross the boundary. | -| 5 | App can't reach emulator symbols via `RTLD_DEFAULT` | Windows hides symbols unless exported | Emulator uses `__declspec(dllexport)` whitelist for the few host-provided helpers (or `--export-all-symbols` during bring-up) | -| 6 | Risk of two `lvgl.dll` copies loaded | If app.dll and emulator.exe resolve different copies, LVGL state splits | CI packs both into one directory; `czdev run` uses `SetDllDirectory` to pin lookup | -| 7 | SDL2 / freetype DLL bundling | MinGW runtime deps | Existing `ldd | awk` logic in `emulator-build.yml:116` stays | -| 8 | Freetype disabled on Windows | Present workaround; CJK fallback differs from mac/Linux | Accept for now; re-enable in a dedicated follow-up task | - -The app source does **not** change per platform — all Windows-specific work -lives in the emulator + LVGL build. - ---- - -## 5. Explicit non-goals (this development cycle) - -These are good ideas but deliberately **not** in scope right now, to keep the -LVGL desktop-dev loop small and shippable: - -- **Python / Qt / Tkinter / raw-framebuffer examples.** They require syscall - interception (fb0 mmap, evdev, ALSA). Use the existing - `CardputerZero-Examples/scripts/dev-on-mac/` Docker path for those. -- **Rust `framebuffer` + `evdev` examples.** Same reason. -- **Subprocess + shared-memory runtime channel.** The dlopen model covers - 100% of LVGL apps. Subprocess/SHM is reserved for a later pass when we - want to cover fb/Qt/Python. -- **Hot `dlclose` / live reload.** `dlclose` is effectively a no-op on macOS - and dangerous across C++ statics. `czdev watch` will rebuild and restart - the emulator process instead. -- **Emscripten / WASM target for `czdev`.** The web playground continues to - build via the emulator's own CI; `czdev` stays desktop-only. -- **GUI IDE.** CLI first. Tauri shell is parked until the CLI loop is solid. - ---- - -## 6. Directory layout (target state) - -``` -CardputerZero-AppBuilder/ -├── czdev # Python wrapper script (login/publish/unpublish/bump) -├── emulator/ # git submodule → eggfly/M5CardputerZero-Emulator -├── crates/ -│ └── czdev/ # Rust CLI: list / build / run / watch / deploy / doctor -├── scripts/ -│ └── czdev/ # Python package: auth, publish, unpublish, bump, github_client -├── sdk/ -│ ├── include/ -│ │ ├── cz_app.h # app ABI (§2) -│ │ └── lv_conf.h # pinned LVGL config (must match emulator's) -│ └── cmake/ -│ └── CZApp.cmake # cz_add_lvgl_app() helper -└── docs/ - └── DESKTOP_DEV.md # this file -``` - -App repos consume the SDK by pointing CMake at `sdk/cmake/CZApp.cmake`; they -do not vendor LVGL. diff --git a/docs/EMULATOR.md b/docs/EMULATOR.md deleted file mode 100644 index 3b6da6d..0000000 --- a/docs/EMULATOR.md +++ /dev/null @@ -1,62 +0,0 @@ -# Emulator Usage - -The `czdev` CLI includes a desktop emulator that renders the CardputerZero 320x170 LCD inside a keyboard skin using SDL2. You can run built-in apps or your own projects without a physical device. - -## Prerequisites - -See [QUICKSTART.md](QUICKSTART.md) for toolchain installation (cmake, SDL2, Rust). - -## Running NC2000 - -NC2000 is a classic handheld PDA emulator (文曲星) ported to CardputerZero. - -```bash -cd CardputerZero-AppBuilder -cargo run -p czdev --release -- run apps/nc2000 -``` - -This will: -1. Configure and build the NC2000 shared library via CMake. -2. Stage `libnc2000.dylib` (macOS) or `libnc2000.so` (Linux) into the emulator's app directory. -3. Launch the emulator window at 640x420 (2x scaled). - -The NC2000 home screen shows the classic PDA icons (address book, calculator, schedule, etc.). Use keyboard keys to navigate. - -## Running APPLauncher - -APPLauncher is the CardputerZero home screen / app launcher with a carousel UI. - -```bash -cd CardputerZero-AppBuilder -cargo run -p czdev --release -- run apps/applaunch/ -``` - -This launches the launcher interface showing installed apps (STORE, CLI, CLAW, etc.) in a horizontally scrollable carousel. Use left/right arrow keys to navigate between apps and Enter/OK to launch. - -## Running Examples - -```bash -cargo run -p czdev --release -- run examples/hello_cz -cargo run -p czdev --release -- run examples/key_echo -``` - -## Keyboard Mapping - -| PC Key | CardputerZero | -|--------|---------------| -| A-Z, 0-9 | Direct mapping | -| F1-F12 | Function keys | -| Escape | ESC / HOME | -| Enter | OK | -| Arrow keys | Navigation | -| Shift | Aa (Caps) | -| Ctrl | ctrl | -| Alt | alt | -| Tab | tab / NEXT | - -## Troubleshooting - -- **Window doesn't appear** — ensure SDL2 is installed (`czdev doctor` checks this). -- **`inotify_init1 failed`** — benign warning on macOS, can be ignored. -- **`[Error] (0.000, +0)` lines** — LVGL debug output during init, harmless. -- **Build fails with missing NAND image** — NC2000 requires its ROM assets in the app directory; these are included in the `apps/nc2000` submodule. diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md index eb1da41..f299bc0 100644 --- a/docs/QUICKSTART.md +++ b/docs/QUICKSTART.md @@ -1,147 +1,105 @@ -# Quickstart — desktop dev for CardputerZero apps +# Quickstart — publish an app to the CardputerZero AppStore [中文](QUICKSTART_ZH.md) | [日本語](QUICKSTART_JA.md) -Get a 320×170 LVGL app running on your Mac or Linux machine in ~3 minutes — -no CardputerZero device required. +Publish a `.deb` package to the AppStore in a few minutes with the pure-Python +`czdev` CLI. No Rust / cargo toolchain and no local ARM toolchain are needed — +building happens in CI. ## 1. Prerequisites -Install the native toolchain. +- **Python 3** +- **git** +- **dpkg-deb** (from `dpkg` / `dpkg-dev`) -**macOS:** ```bash -brew install cmake pkg-config sdl2 sdl2_image sdl2_mixer freetype +# macOS +brew install dpkg +# Debian / Ubuntu +sudo apt install -y python3 git dpkg-dev ``` -**Linux (Debian/Ubuntu):** -```bash -sudo apt install -y build-essential cmake pkg-config \ - libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libfreetype-dev -``` - -**Windows:** MSYS2 MINGW64 shell. See -[DESKTOP_DEV.md §4](DESKTOP_DEV.md#4-windows-lvgl--emulator--known-issues-and-plan) -for the Windows-specific work still in progress — the mac/Linux loop below is -what's supported end-to-end today. +## 2. Clone and log in -You also need a recent Rust toolchain (for the emulator commands). If you don't have one: ```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -> **Note:** Publishing commands (`./czdev login/publish/unpublish/bump`) only -> require Python 3 — no Rust toolchain needed. - -## 2. Clone with submodules - -```bash -git clone --recursive git@github.com:m5stack/CardputerZero-AppBuilder.git +git clone https://github.com/CardputerZero/CardputerZero-AppBuilder.git cd CardputerZero-AppBuilder -``` -If you already cloned without `--recursive`: -```bash -git submodule update --init --recursive -``` - -## 3. Verify the environment - -```bash -cargo run -p czdev --release -- doctor +./czdev --help # works immediately with Python 3 +./czdev login # GitHub device flow; token saved at ~/.czdev/credentials ``` -You should see all required rows green. If anything is MISSING, the output -shows the exact install command for your OS. +`czdev login` prints a code and a URL — open the URL, enter the code, and +authorize. The token is reused by later commands. -## 4. Run the hello app +## 3. Get a `.deb` -```bash -cargo run -p czdev --release -- run examples/hello_cz -``` +You don't build ARM binaries locally. Two ways to obtain a package: -On first run this will: +- **Online build** — GitHub **Actions → Build DEB Package → Run workflow**, + paste your public repo URL, and download the `.deb` artifact. +- **Bundled examples** — pushing to this repo builds everything under + `examples/`; prebuilt ones are in `dist/`. -1. Build the emulator (once, cached in `emulator/build/`). -2. Build `examples/hello_cz` into `examples/hello_cz/.czdev/build/`. -3. Stage the resulting `libhello_cz.dylib` (or `.so`) into the emulator's - `apps/` directory. -4. Launch the emulator with the app loaded via `dlopen`. +Your project must contain an `app-builder.json` (see +[APP_BUILDER_JSON.md](APP_BUILDER_JSON.md)) for CI to discover and build it. -You should see a 320×170 LCD inside a keyboard skin, showing -`Hello, CardputerZero!`. Close the emulator window to exit. +## 4. Add a `store` section for the listing -## 5. Edit-run loop +`czdev publish` reads the AppStore listing from the `store` section of your +`app-builder.json`. At minimum you need a title and one 320×170 screenshot: -```bash -cargo run -p czdev --release -- watch examples/hello_cz +```jsonc +{ + "package_name": "my_app", + "app_name": "My App", + "bin_name": "my_app", + "version": "1.0.1", + + "store": { + "summary": "One-line description", + "description": "Longer description shown on the detail page.", + "categories": ["Games"], + "screenshots": ["screenshots/main.png"], // 320×170 PNG(s) + "icon": "packaging/icon.png" // optional + } +} ``` -The watcher polls `src/`, `include/`, `assets/`, `CMakeLists.txt` and -`app-builder.json`. Any change triggers a rebuild and relaunches the emulator. - -## 6. Writing your own app +## 5. Bump and publish -Copy `examples/hello_cz/` and edit `src/hello_cz.c`. The ABI is documented in -`sdk/include/cz_app.h`: +Run from your app's project directory (the one with `app-builder.json`): -```c -#include - -void app_main(lv_obj_t *parent) { - lv_obj_t *label = lv_label_create(parent); - lv_label_set_text(label, "your UI here"); - lv_obj_center(label); -} +```bash +# See the next patch version implied by the .deb +./czdev bump --deb build/my_app_1.0.0_arm64.deb -void app_event(int type, void *data) { - (void)type; (void)data; -} +# Publish (the .deb version must be newer than what's already published) +./czdev publish --deb build/my_app_1.0.1_arm64.deb ``` -The `CMakeLists.txt` is three lines: +`publish` runs preflight checks (`.desktop` present, version bump, size, and +**no systemd service running as root**), uploads the `.deb` to a GitHub +Release, and opens a metadata PR against the `packages` repo. An admin reviews +and merges it; CI then rebuilds the APT index and your app goes live. -```cmake -cmake_minimum_required(VERSION 3.16) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../sdk/cmake") -include(CZApp) -cz_add_lvgl_app(my_app SOURCES src/my_app.c) -``` +If `--deb` is omitted, `czdev` searches `./build/*.deb`. -And the manifest (see `docs/APP_BUILDER_JSON.md`): +## 6. Unpublish -```json -{ - "package_name": "my_app", - "bin_name": "my_app", - "app_name": "My App", - "runtime": "lvgl-dlopen", - "lvgl_version": "9.5" -} +```bash +./czdev unpublish my_app --version 1.0.1 ``` -## 7. Shipping to a real device - -Building the aarch64 `.deb` stays on CI — trigger the existing -`build-deb.yml` workflow (see the repo README). Then push to the device: +This opens a PR that removes that version. -```bash -cargo run -p czdev --release -- deploy \ - --host pi@192.168.50.150 \ - --deb path/to/my_app_arm64.deb -``` +## Notes -## Troubleshooting - -- **`emulator submodule not checked out`** — you forgot `--recursive`. Fix: - `git submodule update --init --recursive`. -- **LVGL link errors about unresolved symbols** — expected in the app - library; they're resolved at `dlopen` time by the emulator. If the linker - *fails* instead of warns, see `DESKTOP_DEV.md` for the per-platform link - flags (`CZApp.cmake` handles these automatically). -- **`indev_read_cb is not registered` warnings in the log** — benign; the - emulator falls back to a default keypad indev when the app doesn't install - its own `lv_sdl_keyboard_create`. -- **macOS: `Library not loaded: @rpath/SDL2.framework/...`** — `brew install - sdl2` puts the library in a non-framework path; re-run `czdev doctor` and - install what it reports. +- **Ownership is first-come, first-served by GitHub login.** Whoever first + publishes a package name owns it; only that account (or a repo admin) can + publish new versions or unpublish it. +- **Apps must not run as root.** If your `.deb` ships a systemd service, pin it + to a non-root user (`User=` in `[Service]`) or publishing is + rejected. +- You can also publish from the web at **https://dev.cardputer.cc** — drag a + `.deb`, fill in the store info, and submit. diff --git a/docs/QUICKSTART_JA.md b/docs/QUICKSTART_JA.md index bb00759..8dba0f2 100644 --- a/docs/QUICKSTART_JA.md +++ b/docs/QUICKSTART_JA.md @@ -1,166 +1,107 @@ -# クイックスタート — CardputerZero デスクトップ開発 (czdev CLI) +# クイックスタート — CardputerZero AppStore にアプリを公開する [English](QUICKSTART.md) | [中文](QUICKSTART_ZH.md) -CardputerZero 実機がなくても、Mac / Linux 上で約3分で 320×170 LVGL アプリを動かせます。 +純 Python の `czdev` CLI を使えば、数分で `.deb` パッケージを AppStore に公開 +できます。Rust / cargo も、ローカルの ARM ツールチェインも不要です — ビルドは +CI 側で行われます。 -## 1. 依存関係のインストール +## 1. 必要なもの -**macOS:** -```bash -brew install cmake pkg-config sdl2 sdl2_image sdl2_mixer freetype -``` +- **Python 3** +- **git** +- **dpkg-deb**(`dpkg` / `dpkg-dev` に含まれます) -**Linux (Debian/Ubuntu):** ```bash -sudo apt install -y build-essential cmake pkg-config \ - libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libfreetype-dev +# macOS +brew install dpkg +# Debian / Ubuntu +sudo apt install -y python3 git dpkg-dev ``` -**Windows:** MSYS2 MINGW64 環境が必要です。詳細は -[DESKTOP_DEV.md §4](DESKTOP_DEV.md#4-windows-lvgl--emulator--known-issues-and-plan) -を参照。現状 macOS / Linux のワークフローが完全にサポートされています。 +## 2. クローンしてログイン -Rust ツールチェーン(エミュレータコマンドに必要): ```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -> **注意:** パブリッシュコマンド(`./czdev login/publish/unpublish/bump`)は Python 3 のみで動作し、Rust は不要です。 - -## 2. リポジトリのクローン(サブモジュール含む) - -```bash -git clone --recursive git@github.com:m5stack/CardputerZero-AppBuilder.git +git clone https://github.com/CardputerZero/CardputerZero-AppBuilder.git cd CardputerZero-AppBuilder -``` - -`--recursive` を付け忘れた場合: -```bash -git submodule update --init --recursive -``` - -## 3. 開発環境の確認 - -```bash -cargo run -p czdev --release -- doctor -``` - -required の行がすべて OK なら準備完了。MISSING と表示されたら、 -出力に書かれたインストールコマンドを実行してください。 - -## 4. hello サンプルを実行 - -```bash -cargo run -p czdev --release -- run examples/hello_cz -``` - -初回実行時は: - -1. エミュレータをビルド(初回のみ、`emulator/build/` にキャッシュ) -2. `examples/hello_cz` を `.czdev/build/` にビルド -3. 生成された `libhello_cz.dylib`(または `.so`)をエミュレータの `apps/` にコピー -4. エミュレータを起動し、`dlopen` でアプリをロード - -320×170 の LCD ウィンドウ(キーボードスキン付き)に `Hello, CardputerZero!` と表示されます。 -ウィンドウを閉じれば終了。 -## 5. ホットリロード開発ループ - -```bash -cargo run -p czdev --release -- watch examples/hello_cz +./czdev --help # Python 3 があればすぐ動きます +./czdev login # GitHub デバイスフロー。トークンは ~/.czdev/credentials に保存 ``` -`watch` は `src/`、`include/`、`assets/`、`CMakeLists.txt`、 -`app-builder.json` を監視し、変更を検出すると自動で再ビルド+エミュレータ再起動します。 - -## 6. 自分のアプリを作る - -`examples/hello_cz/` をコピーして `src/hello_cz.c` を編集。ABI 定義は -`sdk/include/cz_app.h` にあります: +`czdev login` はコードと URL を表示します — URL を開いてコードを入力し、認可 +してください。以降のコマンドはこのトークンを再利用します。 -```c -#include +## 3. `.deb` を入手する -void app_main(lv_obj_t *parent) { - lv_obj_t *label = lv_label_create(parent); - lv_label_set_text(label, "あなたのUIコード"); - lv_obj_center(label); -} +ARM バイナリをローカルでビルドする必要はありません。入手方法は 2 つ: -void app_event(int type, void *data) { - (void)type; (void)data; -} -``` +- **オンラインビルド** — GitHub **Actions → Build DEB Package → Run workflow** + で公開リポジトリの URL を貼り付け、生成された `.deb` アーティファクトを + ダウンロード。 +- **同梱サンプル** — 本リポジトリへの push で `examples/` 配下がすべてビルド + されます。ビルド済みのものは `dist/` にあります。 -`CMakeLists.txt` はたった3行: +CI が発見・ビルドできるよう、プロジェクトには `app-builder.json` +([APP_BUILDER_JSON.md](APP_BUILDER_JSON.md) 参照)が必要です。 -```cmake -cmake_minimum_required(VERSION 3.16) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../sdk/cmake") -include(CZApp) -cz_add_lvgl_app(my_app SOURCES src/my_app.c) -``` +## 4. ストア情報として `store` セクションを追加 -`app-builder.json` マニフェスト(詳細は `docs/APP_BUILDER_JSON.md`): +`czdev publish` は `app-builder.json` の `store` セクションから AppStore の +掲載情報を読み取ります。最低限、タイトルと 320×170 のスクリーンショットが +1 枚必要です: -```json +```jsonc { "package_name": "my_app", - "bin_name": "my_app", - "app_name": "My App", - "runtime": "lvgl-dlopen", - "lvgl_version": "9.5" + "app_name": "My App", + "bin_name": "my_app", + "version": "1.0.1", + + "store": { + "summary": "一言での説明", + "description": "詳細ページに表示される長めの説明。", + "categories": ["Games"], + "screenshots": ["screenshots/main.png"], // 320×170 の PNG + "icon": "packaging/icon.png" // 任意 + } } ``` -## 7. 実機へのデプロイ +## 5. bump と publish -arm64 `.deb` は CI でビルドします — リポジトリの `build-deb.yml` ワークフローを -トリガーしてください。その後デバイスへ転送: +アプリのプロジェクトディレクトリ(`app-builder.json` がある場所)で実行します: ```bash -cargo run -p czdev --release -- deploy \ - --host pi@192.168.50.150 \ - --deb path/to/my_app_arm64.deb +# その .deb に対応する次のパッチバージョンを表示 +./czdev bump --deb build/my_app_1.0.0_arm64.deb + +# 公開(.deb のバージョンは公開済みより新しい必要があります) +./czdev publish --deb build/my_app_1.0.1_arm64.deb ``` -## czdev コマンド一覧 +`publish` は事前チェック(`.desktop` の有無、バージョン更新、サイズ、そして +**root で動く systemd サービスがないこと**)を行い、`.deb` を GitHub Release に +アップロードし、`packages` リポジトリへメタデータのみの PR を作成します。管理者が +レビュー・マージすると、CI が APT インデックスを再生成し、アプリが公開されます。 -| コマンド | 機能 | -|----------|------| -| `czdev doctor` | 依存関係のチェック(cmake, SDL2, freetype 等) | -| `czdev list [パス]` | ディレクトリ内の `app-builder.json` プロジェクトを一覧表示 | -| `czdev build [パス]` | アプリの共有ライブラリ(.dylib / .so)をビルド | -| `czdev run [パス]` | ビルド+エミュレータ起動でアプリをロード | -| `czdev watch [パス]` | ソース変更を監視し、自動再ビルド+再起動 | -| `czdev deploy --host --deb` | .deb を SSH でデバイスに転送+インストール | +`--deb` を省略すると、`czdev` は `./build/*.deb` を探します。 -すべてのコマンドは `cargo run -p czdev --release --` のプレフィックス付きで実行します。 -または `cargo install --path crates/czdev` でグローバルにインストールすることも可能です。 +## 6. 公開停止(unpublish) -## トラブルシューティング +```bash +./czdev unpublish my_app --version 1.0.1 +``` -- **`emulator submodule not checked out`** — `git submodule update --init --recursive` -- **LVGL 未定義シンボルのリンクエラー** — 正常です(ランタイムにエミュレータが提供)。 - linker が error(warning ではなく)を出す場合は `DESKTOP_DEV.md` を参照 -- **macOS `Library not loaded: @rpath/SDL2.framework`** — `brew install sdl2` - してから `czdev doctor` を再実行 +そのバージョンを削除する PR を作成します。 -## アーキテクチャ概要 +## 補足 -``` -┌──────────────────────────────────────────────────────────────┐ -│ cardputer-zero-emu(エミュレータ) │ -│ ┌────────────────┐ ┌────────────────────────────┐ │ -│ │ LVGL 9.5 エンジン│ ◄──── │ SDL2 ウィンドウ (320×170) │ │ -│ │ + フォント/アイコン│ │ + キーボードイベントマッピング│ │ -│ └───────┬────────┘ └────────────────────────────┘ │ -│ │ dlopen(RTLD_GLOBAL) │ -│ ▼ │ -│ ┌────────────────┐ │ -│ │ あなたの App │ ← app_main(parent) / app_event(...) │ -│ │ .dylib / .so │ │ -│ └────────────────┘ │ -└──────────────────────────────────────────────────────────────┘ -``` +- **所有権は GitHub ログインの先着順です。** あるパッケージ名を最初に公開した + アカウントが所有し、以降はそのアカウント(またはリポジトリ管理者)のみが + 新バージョンの公開・公開停止を行えます。 +- **アプリは root で実行してはいけません。** `.deb` が systemd サービスを同梱 + する場合は、非 root ユーザーに固定してください(`[Service]` に + `User=`)。さもないと公開は拒否されます。 +- Web からの公開も可能です: **https://dev.cardputer.cc** に `.deb` をドラッグ + し、ストア情報を入力して送信するだけです。 diff --git a/docs/QUICKSTART_ZH.md b/docs/QUICKSTART_ZH.md index c7e8b76..0eab576 100644 --- a/docs/QUICKSTART_ZH.md +++ b/docs/QUICKSTART_ZH.md @@ -1,164 +1,102 @@ -# 快速上手 — CardputerZero 桌面开发 (czdev CLI) +# 快速上手 — 发布应用到 CardputerZero AppStore [English](QUICKSTART.md) | [日本語](QUICKSTART_JA.md) -无需 CardputerZero 实体设备,在你的 Mac / Linux 上 3 分钟跑起来一个 320×170 LVGL 应用。 +用纯 Python 的 `czdev` CLI,几分钟内把一个 `.deb` 包发布到 AppStore。不需要 +Rust / cargo,也不需要本地 ARM 工具链——编译都在 CI 里完成。 -## 1. 安装依赖 +## 1. 前置依赖 -**macOS:** -```bash -brew install cmake pkg-config sdl2 sdl2_image sdl2_mixer freetype -``` +- **Python 3** +- **git** +- **dpkg-deb**(来自 `dpkg` / `dpkg-dev`) -**Linux (Debian/Ubuntu):** ```bash -sudo apt install -y build-essential cmake pkg-config \ - libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libfreetype-dev +# macOS +brew install dpkg +# Debian / Ubuntu +sudo apt install -y python3 git dpkg-dev ``` -**Windows:** 需要 MSYS2 MINGW64 环境,参考 -[DESKTOP_DEV.md §4](DESKTOP_DEV.md#4-windows-lvgl--emulator--known-issues-and-plan)。 -目前 macOS / Linux 流程是完整可用的。 +## 2. 克隆并登录 -还需要 Rust 工具链(用于模拟器相关命令): ```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -> **提示:** 发布命令(`./czdev login/publish/unpublish/bump`)只需要 Python 3,无需 Rust。 - -## 2. 克隆仓库(含子模块) - -```bash -git clone --recursive git@github.com:m5stack/CardputerZero-AppBuilder.git +git clone https://github.com/CardputerZero/CardputerZero-AppBuilder.git cd CardputerZero-AppBuilder -``` - -如果你已经克隆但忘了 `--recursive`: -```bash -git submodule update --init --recursive -``` - -## 3. 检查开发环境 - -```bash -cargo run -p czdev --release -- doctor -``` - -所有 required 行应该显示 OK。缺什么就按输出的提示装。 - -## 4. 跑 hello 示例 - -```bash -cargo run -p czdev --release -- run examples/hello_cz -``` - -第一次运行会: - -1. 编译模拟器(仅一次,产物缓存在 `emulator/build/`) -2. 编译 `examples/hello_cz` 到 `.czdev/build/` -3. 把生成的 `libhello_cz.dylib`(或 `.so`)复制到模拟器 `apps/` 目录 -4. 启动模拟器,通过 `dlopen` 加载你的 App - -你会看到一个 320×170 的 LCD 窗口(带键盘皮肤),显示 `Hello, CardputerZero!`。 -关闭窗口即退出。 -## 5. 热重载开发循环 - -```bash -cargo run -p czdev --release -- watch examples/hello_cz +./czdev --help # 有 Python 3 即可直接运行 +./czdev login # GitHub 设备码登录;token 存到 ~/.czdev/credentials ``` -`watch` 会监视 `src/`、`include/`、`assets/`、`CMakeLists.txt` 和 -`app-builder.json`。任何文件修改后自动重新编译并重启模拟器。 - -## 6. 写你自己的 App - -复制 `examples/hello_cz/` 然后改 `src/hello_cz.c`。ABI 定义见 -`sdk/include/cz_app.h`: +`czdev login` 会打印一个验证码和一个网址——打开网址、输入验证码并授权即可。 +后续命令会复用这个 token。 -```c -#include +## 3. 拿到 `.deb` -void app_main(lv_obj_t *parent) { - lv_obj_t *label = lv_label_create(parent); - lv_label_set_text(label, "你的界面代码"); - lv_obj_center(label); -} +你不需要在本地编译 ARM 二进制。两种拿包方式: -void app_event(int type, void *data) { - (void)type; (void)data; -} -``` +- **在线构建**——GitHub **Actions → Build DEB Package → Run workflow**, + 粘贴你的公开仓库 URL,下载生成的 `.deb` 制品。 +- **内置示例**——推送到本仓库会构建 `examples/` 下所有应用;预构建的包在 + `dist/`。 -`CMakeLists.txt` 只需三行: +你的项目里要有 `app-builder.json`(见 +[APP_BUILDER_JSON.md](APP_BUILDER_JSON.md)),CI 才能发现并构建它。 -```cmake -cmake_minimum_required(VERSION 3.16) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../sdk/cmake") -include(CZApp) -cz_add_lvgl_app(my_app SOURCES src/my_app.c) -``` +## 4. 补上 `store` 段作为商店信息 -`app-builder.json` 清单文件(详见 `docs/APP_BUILDER_JSON.md`): +`czdev publish` 会从 `app-builder.json` 的 `store` 段读取 AppStore 展示信息。 +至少需要一个标题和一张 320×170 的截图: -```json +```jsonc { "package_name": "my_app", - "bin_name": "my_app", - "app_name": "My App", - "runtime": "lvgl-dlopen", - "lvgl_version": "9.5" + "app_name": "My App", + "bin_name": "my_app", + "version": "1.0.1", + + "store": { + "summary": "一句话简介", + "description": "详情页展示的较长描述。", + "categories": ["Games"], + "screenshots": ["screenshots/main.png"], // 320×170 的 PNG + "icon": "packaging/icon.png" // 可选 + } } ``` -## 7. 部署到真机 +## 5. bump 与 publish -arm64 `.deb` 通过 CI 构建——触发仓库里的 `build-deb.yml` workflow。 -然后推送到设备: +在你应用的项目目录(含 `app-builder.json` 的那个)里运行: ```bash -cargo run -p czdev --release -- deploy \ - --host pi@192.168.50.150 \ - --deb path/to/my_app_arm64.deb +# 查看该 .deb 对应的下一个补丁版本号 +./czdev bump --deb build/my_app_1.0.0_arm64.deb + +# 发布(.deb 里的版本必须高于线上已发布的版本) +./czdev publish --deb build/my_app_1.0.1_arm64.deb ``` -## czdev 命令速查 +`publish` 会做发布前检查(存在 `.desktop`、版本已提升、体积、以及 +**没有以 root 运行的 systemd 服务**),把 `.deb` 上传到 GitHub Release,并向 +`packages` 仓库发一个只含元数据的 PR。管理员审核合并后,CI 重建 APT 索引, +你的应用就上线了。 -| 命令 | 作用 | -|------|------| -| `czdev doctor` | 检查依赖是否就绪(cmake, SDL2, freetype 等) | -| `czdev list [路径]` | 扫描目录下所有 `app-builder.json` 项目 | -| `czdev build [路径]` | 编译 App 的共享库(.dylib / .so) | -| `czdev run [路径]` | 编译 + 启动模拟器加载 App | -| `czdev watch [路径]` | 监视源码变化,自动重编译 + 重启 | -| `czdev deploy --host --deb` | 将 .deb 通过 SSH 推送到设备 | +不带 `--deb` 时,`czdev` 会在 `./build/*.deb` 里查找。 -所有命令通过 `cargo run -p czdev --release --` 前缀调用,或者你也可以先 -`cargo install --path crates/czdev` 装到全局 PATH。 +## 6. 下架 -## 常见问题 +```bash +./czdev unpublish my_app --version 1.0.1 +``` -- **`emulator submodule not checked out`** — `git submodule update --init --recursive` -- **LVGL 未定义符号链接错误** — 正常现象(运行时由模拟器提供),如果 linker 直接 - 报 error 而不是 warning,参考 `DESKTOP_DEV.md` -- **macOS `Library not loaded: @rpath/SDL2.framework`** — `brew install sdl2` - 然后重新跑 `czdev doctor` +这会发一个移除该版本的 PR。 -## 架构简图 +## 说明 -``` -┌──────────────────────────────────────────────────────────────┐ -│ cardputer-zero-emu (模拟器) │ -│ ┌────────────────┐ ┌────────────────────────────┐ │ -│ │ LVGL 9.5 引擎 │ ◄──── │ SDL2 窗口 (320×170) │ │ -│ │ + 字体/图标 │ │ + 键盘事件映射 │ │ -│ └───────┬────────┘ └────────────────────────────┘ │ -│ │ dlopen(RTLD_GLOBAL) │ -│ ▼ │ -│ ┌────────────────┐ │ -│ │ 你的 App .dylib │ ← app_main(parent) / app_event(...) │ -│ └────────────────┘ │ -└──────────────────────────────────────────────────────────────┘ -``` +- **归属按 GitHub 账号先到先得。** 谁先发布某个包名,就归属于其账号;之后只有 + 该账号(或仓库管理员)能发布新版本或下架。 +- **应用不允许以 root 运行。** 如果你的 `.deb` 带 systemd 服务,请把它固定到 + 非 root 用户(`[Service]` 段里写 `User=`),否则发布会被拒。 +- 你也可以在网页端 **https://dev.cardputer.cc** 发布——拖入 `.deb`、填写商店 + 信息、提交即可。 diff --git a/docs/architecture.md b/docs/architecture.md deleted file mode 100644 index 89dac84..0000000 --- a/docs/architecture.md +++ /dev/null @@ -1,133 +0,0 @@ -# CardputerZero AppBuilder — Desktop IDE Architecture - -## Overview - -Tauri 2 desktop app (Rust backend + TypeScript/React frontend) for building, debugging, and running M5CardputerZero applications. Targets Windows, macOS, Linux. - -## Tech Stack - -| Layer | Technology | -|-------|-----------| -| Framework | Tauri 2 (Rust + system WebView) | -| Frontend | React + TypeScript + Vite | -| UI | Tailwind CSS | -| Backend | Rust (tauri commands) | -| Build system | Invokes SCons via subprocess | -| Emulator | QEMU aarch64 user-mode (runs ARM64 ELF on host) | - -## Core Features - -### 1. Project Management -- Open/create projects based on M5Stack_Linux_Libs SDK scaffold -- Detect `app-builder.json` in project directories -- Git clone/pull from remote repositories - -### 2. One-Click Build -- Cross-compile to ARM64 via `aarch64-linux-gnu-` toolchain -- Invoke `scons -j$(nproc)` with `CardputerZero=y` + `CONFIG_REPO_AUTOMATION=y` -- Stream build output to a terminal panel in real-time -- Parse errors and show inline diagnostics -- Package `.deb` via `pack_deb.py` - -### 3. One-Click Debug -- SSH to device (`pi@`) and deploy `.deb` or binary -- Attach GDB via `gdb-multiarch` / `aarch64-linux-gnu-gdb` -- Stream stdout/stderr from device to IDE terminal - -### 4. One-Click Emulator -- Launch QEMU user-mode: `qemu-aarch64 -L ./dist/` -- SDL2 window shows the 320x170 LVGL UI on host -- Install `.deb` into a lightweight aarch64 rootfs for full-stack testing - -## Architecture Diagram - -``` -┌─────────────────────────────────────────────────────────┐ -│ Tauri Window │ -│ ┌───────────────────────────────────────────────────┐ │ -│ │ React Frontend (WebView) │ │ -│ │ │ │ -│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ -│ │ │ Build │ │ Debug │ │ Emulator │ │ │ -│ │ │ Panel │ │ Panel │ │ Panel │ │ │ -│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ -│ │ │ │ │ │ │ -│ │ ┌────┴─────────────┴─────────────┴────┐ │ │ -│ │ │ Terminal Output Panel │ │ │ -│ │ └────────────────────────────────────────┘ │ │ -│ └───────────────────────────────────────────────────┘ │ -│ │ invoke() │ events │ -│ ┌──────┴──────────────────┴──────────────────────┐ │ -│ │ Tauri Rust Backend │ │ -│ │ │ │ -│ │ ┌────────────┐ ┌────────────┐ ┌─────────────┐ │ │ -│ │ │ BuildMgr │ │ DeviceMgr │ │ EmulatorMgr │ │ │ -│ │ │ │ │ │ │ │ │ │ -│ │ │ scons │ │ SSH/SCP │ │ qemu-aarch64│ │ │ -│ │ │ dpkg-deb │ │ GDB remote │ │ rootfs mgmt │ │ │ -│ │ └────────────┘ └────────────┘ └─────────────┘ │ │ -│ └─────────────────────────────────────────────────┘ │ -└─────────────────────────────────────────────────────────┘ -``` - -## Implementation Checklist - -### Phase 1: Scaffold (MVP shell) -- [x] Tauri 2 project init with React + TypeScript + Vite -- [x] Basic window with sidebar navigation (Build / Debug / Emulator) -- [x] Rust command: `greet` smoke test - -### Phase 2: Build -- [ ] "Open Project" dialog — pick a directory with `app-builder.json` -- [ ] Rust `BuildMgr`: spawn `scons` subprocess, stream stdout via Tauri events -- [ ] Frontend terminal panel (xterm.js) for build output -- [ ] Build status indicator (idle / building / success / error) -- [ ] "Package DEB" button after successful build - -### Phase 3: Device & Debug -- [ ] Device connection settings (IP, user, password) -- [ ] Rust `DeviceMgr`: SSH deploy (SCP binary or `dpkg -i`) -- [ ] Remote run: SSH exec and stream output -- [ ] GDB attach (stretch goal) - -### Phase 4: Emulator -- [ ] Detect/install QEMU user-mode -- [ ] Rust `EmulatorMgr`: launch `qemu-aarch64` with sysroot -- [ ] SDL2 window forwarding for LVGL UI -- [ ] "Install to emulator" from built `.deb` - -### Phase 5: Polish -- [ ] Auto-detect toolchain (aarch64-linux-gnu-gcc, scons, qemu) -- [ ] Toolchain installer wizard -- [ ] Settings persistence -- [ ] Windows / Linux packaging & testing - -## Directory Structure - -``` -CardputerZero-AppBuilder/ -├── src-tauri/ # Rust backend -│ ├── src/ -│ │ ├── main.rs # Tauri entry point -│ │ ├── build_mgr.rs # Build subprocess management -│ │ ├── device_mgr.rs # SSH/SCP/GDB -│ │ └── emulator_mgr.rs # QEMU management -│ ├── Cargo.toml -│ └── tauri.conf.json -├── src/ # React frontend -│ ├── App.tsx -│ ├── components/ -│ │ ├── Sidebar.tsx -│ │ ├── BuildPanel.tsx -│ │ ├── DebugPanel.tsx -│ │ ├── EmulatorPanel.tsx -│ │ └── TerminalPanel.tsx -│ ├── main.tsx -│ └── styles/ -├── .github/workflows/ # CI (existing) -├── scripts/ # Packaging scripts (existing) -├── docs/ # This file -├── package.json -├── vite.config.ts -└── tsconfig.json -``` diff --git a/docs/emu-applauncher.png b/docs/emu-applauncher.png deleted file mode 100644 index d5bf5b3..0000000 Binary files a/docs/emu-applauncher.png and /dev/null differ diff --git a/docs/emu-hello.png b/docs/emu-hello.png deleted file mode 100644 index db6e6e2..0000000 Binary files a/docs/emu-hello.png and /dev/null differ diff --git a/docs/nc2000_emu.png b/docs/nc2000_emu.png deleted file mode 100644 index e8f4d38..0000000 Binary files a/docs/nc2000_emu.png and /dev/null differ