picoride 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.claude/launch.json +11 -0
- data/CHANGELOG.md +5 -0
- data/CLAUDE.md +1518 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/Dockerfile +76 -0
- data/LICENSE.txt +21 -0
- data/README.md +76 -0
- data/Rakefile +12 -0
- data/docker/esp32/Dockerfile +82 -0
- data/docker/pico/Dockerfile +77 -0
- data/exe/picoride +16 -0
- data/lib/picoride/background_job.rb +71 -0
- data/lib/picoride/project_store.rb +133 -0
- data/lib/picoride/r2p2_state.rb +137 -0
- data/lib/picoride/server.rb +1156 -0
- data/lib/picoride/version.rb +5 -0
- data/lib/picoride.rb +11 -0
- data/sig/picoruby/esp32/ide.rbs +8 -0
- data/web/funicular/README.md +111 -0
- data/web/funicular/ruby/components/directory_browser_dialog.rb +212 -0
- data/web/funicular/ruby/components/editor_app.rb +1560 -0
- data/web/funicular/ruby/components/log_panel.rb +102 -0
- data/web/funicular/ruby/components/menu_bar.rb +266 -0
- data/web/funicular/ruby/components/project_settings_dialog.rb +203 -0
- data/web/funicular/ruby/components/prompt_dialog.rb +76 -0
- data/web/funicular/ruby/components/toolbar.rb +30 -0
- data/web/funicular/ruby/components/tree_view.rb +112 -0
- data/web/funicular/ruby/main.rb +3 -0
- data/web/public/css/style.css +886 -0
- data/web/public/js/editor.js +257 -0
- data/web/views/index.erb +94 -0
- metadata +132 -0
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"picoride" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["youchan@gmail.com"](mailto:"youchan@gmail.com").
|
data/Dockerfile
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# ビルド専用イメージ(移行プラン フェーズ1)。
|
|
2
|
+
#
|
|
3
|
+
# 旧来の Dockerfile はESP-IDFに加えてRuby/rbenv・R2P2-ESP32本体・Sinatraアプリまで
|
|
4
|
+
# 1つのイメージに焼き込んでいたが、「Sinatraはホストでネイティブに動かし、Dockerは
|
|
5
|
+
# idf.py build / rake setup_xxx の実行専用にする」という移行方針(CLAUDE.md参照)に
|
|
6
|
+
# 沿って、ここではESP-IDFツールチェインだけを持つ最小限のイメージにしてある。
|
|
7
|
+
#
|
|
8
|
+
# R2P2-ESP32本体は焼き込まない。プロジェクトごとに独立したビルド状態(sdkconfig・
|
|
9
|
+
# build/・managed_components等)を持たせたいため、実行時にホスト側の状態ディレクトリを
|
|
10
|
+
# `docker run -v <state_dir>:/R2P2-ESP32` でbind mountして使う想定(R2P2-ESP32の
|
|
11
|
+
# git clone・build_config.rbフックのパッチ・idf_component.ymlのバージョン固定は、
|
|
12
|
+
# ホスト側のRubyコードがそのstate_dirを初めて使うときに行う。イメージビルド時の
|
|
13
|
+
# `RUN`では実行できないため)。
|
|
14
|
+
FROM espressif/idf:v5.5.1
|
|
15
|
+
|
|
16
|
+
SHELL ["/bin/bash", "-c"]
|
|
17
|
+
|
|
18
|
+
ENV LANG=C.UTF-8
|
|
19
|
+
ENV LC_ALL=C.UTF-8
|
|
20
|
+
|
|
21
|
+
# R2P2-ESP32のrakelib/setup.rake(`rake setup_xxx`)は内部で`bundle install`を
|
|
22
|
+
# 呼び、そこでracc/ffi/io-console/json等ネイティブ拡張を持つgemをビルドしようと
|
|
23
|
+
# する。ベースイメージ(espressif/idf)にはコンパイラもRubyのヘッダファイルも
|
|
24
|
+
# 無いため、`mkmf.rb can't find header files for ruby`で失敗する(実際に
|
|
25
|
+
# `rake setup_esp32`を実行して発覚した)。ビルド専用イメージなので、
|
|
26
|
+
# Rubyそのものはベースイメージのシステムruby(apt由来)をそのまま使いつつ、
|
|
27
|
+
# 拡張のビルドに必要な最小限だけ追加する。
|
|
28
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
29
|
+
build-essential \
|
|
30
|
+
ruby-dev \
|
|
31
|
+
libssl-dev \
|
|
32
|
+
libreadline-dev \
|
|
33
|
+
zlib1g-dev \
|
|
34
|
+
libyaml-dev \
|
|
35
|
+
libffi-dev \
|
|
36
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
37
|
+
|
|
38
|
+
# main/CMakeLists.txt の littlefs_create_partition_image(joltwallet/esp_littlefs、
|
|
39
|
+
# storageパーティションのイメージ作成に使う)は、ビルド時にvenvを作ってPyPIから
|
|
40
|
+
# littlefs-python をインストールする。ビルド実行時にネットワークが不安定だと
|
|
41
|
+
# ここでタイムアウトして失敗する(実際に発生した)。R2P2-ESP32側のmain/idf_component.yml
|
|
42
|
+
# で joltwallet/littlefs を 1.20.4 に固定する前提(この固定処理自体はR2P2-ESP32を
|
|
43
|
+
# プロジェクトごとにcloneするRuby側の処理に移した)で、image-building-requirements.txt
|
|
44
|
+
# の中身が littlefs-python==0.15.0 に一意に確定していることを利用し、これを
|
|
45
|
+
# イメージビルド時に取得して /opt/pip-wheels に置いておく。PIP_NO_INDEX/PIP_FIND_LINKS
|
|
46
|
+
# で「常にこのローカルディレクトリだけを見る」ようコンテナ全体のpipに指示することで、
|
|
47
|
+
# ビルド実行時はPyPIへの通信自体(バージョン解決の問い合わせも含めて)を発生させない。
|
|
48
|
+
#
|
|
49
|
+
# pip3(python3含む)はこのイメージだとentrypoint.sh(コンテナ起動時にのみ実行される)
|
|
50
|
+
# 経由でPATHに追加される、export.shが用意するESP-IDF専用のvenvにしか無い。
|
|
51
|
+
# `docker build`のRUNはentrypointを経由しない生のシェルなので、同じRUN内で
|
|
52
|
+
# 明示的にexport.shをsourceしないとpip3が見つからない(idf.py/rakeの実行時に
|
|
53
|
+
# 毎回export.shをsourceしているのと同じ理由)。
|
|
54
|
+
RUN . "${IDF_PATH}/export.sh" > /dev/null && pip3 download littlefs-python==0.15.0 -d /opt/pip-wheels
|
|
55
|
+
|
|
56
|
+
ENV PIP_NO_INDEX=1
|
|
57
|
+
ENV PIP_FIND_LINKS=/opt/pip-wheels
|
|
58
|
+
|
|
59
|
+
# bind mountしたディレクトリ(ホスト側の実UIDのまま見える)に対してgitコマンドを
|
|
60
|
+
# 実行すると、コンテナ内のユーザーとの所有者不一致でgitが「dubious ownership」
|
|
61
|
+
# エラーを出して拒否する(git 2.35.2以降のCVE-2022-24765対策)。実際に
|
|
62
|
+
# `rake setup_xxx`実行時に発生して発覚した。bind mountされたパスは実行のたびに
|
|
63
|
+
# 変わりうる(プロジェクトごとの状態ディレクトリ)ので、個別に許可するのではなく
|
|
64
|
+
# 全パスを許可しておく(このイメージはビルド専用の使い捨てコンテナ内でしか
|
|
65
|
+
# 使わないため、安全性への影響は無視できる)。
|
|
66
|
+
RUN git config --system --add safe.directory '*'
|
|
67
|
+
|
|
68
|
+
# R2P2-ESP32のrakelib/setup.rake(`rake setup_xxx`でmruby/picorubyをビルドする処理)は
|
|
69
|
+
# 内部で`bundle install`を呼ぶ。このイメージにはベースイメージ(espressif/idf)が
|
|
70
|
+
# 標準で持つRuby(bundlerはデフォルトgemとしてライブラリだけは入っているが、
|
|
71
|
+
# `bundle`コマンドの実行ファイルは生成されていない)がある。実際に`rake setup_esp32`
|
|
72
|
+
# を実行して`bundle: command not found`(exit 127)になったことで発覚した。
|
|
73
|
+
RUN gem install bundler --no-document
|
|
74
|
+
|
|
75
|
+
# R2P2-ESP32の状態ディレクトリをここへbind mountして使う想定のデフォルトWORKDIR。
|
|
76
|
+
WORKDIR /R2P2-ESP32
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 youchan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# PicoRuby/FemtoRuby IDE for ESP32 / Raspberry Pi Pico
|
|
2
|
+
|
|
3
|
+
This is PicoRuby/FemtoRuby development environment for ESP32 and Raspberry Pi Pico
|
|
4
|
+
(R2P2-ESP32 / R2P2).
|
|
5
|
+
This IDE can do as following
|
|
6
|
+
|
|
7
|
+
- Edit source codes(.c .h .rb) for mrbgem as a project
|
|
8
|
+
- Build image
|
|
9
|
+
- Install image to your device
|
|
10
|
+
|
|
11
|
+
## Setup
|
|
12
|
+
|
|
13
|
+
The app itself runs natively on your machine (Ruby). Docker is only used to run
|
|
14
|
+
the firmware builds. There is one builder image per target family; build the
|
|
15
|
+
one(s) you need once up front:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bundle install
|
|
19
|
+
docker build -t picoride-builder-esp32 docker/esp32 # ESP32 (ESP-IDF)
|
|
20
|
+
docker build -t picoride-builder-pico docker/pico # Raspberry Pi Pico (pico-sdk)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
The server picks the image from the project's target (Settings dialog):
|
|
24
|
+
`esp32*` targets use `picoride-builder-esp32`, `pico` / `pico_w` / `pico2` /
|
|
25
|
+
`pico2_w` use `picoride-builder-pico`. Image names can be overridden with
|
|
26
|
+
`PICORIDE_ESP32_BUILDER_IMAGE` / `PICORIDE_PICO_BUILDER_IMAGE`.
|
|
27
|
+
|
|
28
|
+
If PicoRuby bumps its required pico-sdk version, the Pico build stops with
|
|
29
|
+
"pico-sdk X is not checked out!". Rebuild the image with
|
|
30
|
+
`docker build --build-arg PICO_SDK_VERSION=X -t picoride-builder-pico docker/pico`.
|
|
31
|
+
|
|
32
|
+
### Raspberry Pi Pico notes
|
|
33
|
+
|
|
34
|
+
- A Pico build produces a `.uf2`. Use "UF2をダウンロード" in the menu bar, then
|
|
35
|
+
hold BOOTSEL while plugging in the Pico and copy the file to the drive that
|
|
36
|
+
appears (`RPI-RP2` / `RP2350`).
|
|
37
|
+
- Unlike ESP32, `app/` is not baked into the firmware. Transfer it with
|
|
38
|
+
"app/ をアップロード" on the Terminal tab.
|
|
39
|
+
- `pico` / `pico_w` (RP2040) support only FemtoRuby (mruby/c);
|
|
40
|
+
`pico2` / `pico2_w` support both FemtoRuby and PicoRuby (mruby).
|
|
41
|
+
|
|
42
|
+
## Run
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
./bin/server
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Open http://localhost:4567 . On first load, open a project via File > Open
|
|
49
|
+
Project (or the dropdown in the menu bar) and browse to, or create, a
|
|
50
|
+
directory on disk — it gets an `app/` and `build_config.rb` automatically.
|
|
51
|
+
Opened projects are remembered in `~/.picoride/project.toml`, so the last one
|
|
52
|
+
you had open reopens automatically on the next server start.
|
|
53
|
+
|
|
54
|
+
## Troubleshooting
|
|
55
|
+
|
|
56
|
+
### `Failed to execute 'open' on 'SerialPort': Failed to open serial port.`
|
|
57
|
+
|
|
58
|
+
This happens when connecting to the device from the Terminal tab (Web Serial
|
|
59
|
+
API). On Linux, check `chrome://device-log` right after the failed attempt to
|
|
60
|
+
see the actual OS-level error:
|
|
61
|
+
|
|
62
|
+
- **`FILE_ERROR_ACCESS_DENIED`**: your user isn't (yet) in the `dialout` group
|
|
63
|
+
with an active session, so the browser process can't open `/dev/ttyACM*` /
|
|
64
|
+
`/dev/ttyUSB*`.
|
|
65
|
+
```bash
|
|
66
|
+
sudo usermod -aG dialout $USER
|
|
67
|
+
```
|
|
68
|
+
Adding yourself to the group is not enough by itself — an already-running
|
|
69
|
+
login session (and any browser already open in it) still has the old group
|
|
70
|
+
list. **Log out and back in (or reboot)** so the new group membership takes
|
|
71
|
+
effect, then try connecting again.
|
|
72
|
+
- If the port isn't held by anything and permissions look right but it still
|
|
73
|
+
fails, check whether another process/service is holding the port open:
|
|
74
|
+
`sudo lsof /dev/ttyACM0` (or `/dev/ttyUSB0`), and whether `ModemManager` or
|
|
75
|
+
`brltty` is running and probing it (`systemctl status ModemManager brltty`).
|
|
76
|
+
|
data/Rakefile
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# ESP32(R2P2-ESP32)用のビルド専用イメージ(移行プラン フェーズ1)。
|
|
2
|
+
#
|
|
3
|
+
# docker build -t picoride-builder-esp32 docker/esp32
|
|
4
|
+
#
|
|
5
|
+
# Raspberry Pi Pico用は docker/pico/Dockerfile(別イメージ)。picorideサーバーが
|
|
6
|
+
# プロジェクト設定のターゲットに応じてどちらのイメージで docker run するかを
|
|
7
|
+
# 切り替える(lib/picoride/server.rb の BUILDER_IMAGES 参照)。
|
|
8
|
+
#
|
|
9
|
+
# 旧来の Dockerfile はESP-IDFに加えてRuby/rbenv・R2P2-ESP32本体・Sinatraアプリまで
|
|
10
|
+
# 1つのイメージに焼き込んでいたが、「Sinatraはホストでネイティブに動かし、Dockerは
|
|
11
|
+
# idf.py build / rake setup_xxx の実行専用にする」という移行方針(CLAUDE.md参照)に
|
|
12
|
+
# 沿って、ここではESP-IDFツールチェインだけを持つ最小限のイメージにしてある。
|
|
13
|
+
#
|
|
14
|
+
# R2P2-ESP32本体は焼き込まない。プロジェクトごとに独立したビルド状態(sdkconfig・
|
|
15
|
+
# build/・managed_components等)を持たせたいため、実行時にホスト側の状態ディレクトリを
|
|
16
|
+
# `docker run -v <state_dir>:/R2P2-ESP32` でbind mountして使う想定(R2P2-ESP32の
|
|
17
|
+
# git clone・build_config.rbフックのパッチ・idf_component.ymlのバージョン固定は、
|
|
18
|
+
# ホスト側のRubyコードがそのstate_dirを初めて使うときに行う。イメージビルド時の
|
|
19
|
+
# `RUN`では実行できないため)。
|
|
20
|
+
FROM espressif/idf:v5.5.1
|
|
21
|
+
|
|
22
|
+
SHELL ["/bin/bash", "-c"]
|
|
23
|
+
|
|
24
|
+
ENV LANG=C.UTF-8
|
|
25
|
+
ENV LC_ALL=C.UTF-8
|
|
26
|
+
|
|
27
|
+
# R2P2-ESP32のrakelib/setup.rake(`rake setup_xxx`)は内部で`bundle install`を
|
|
28
|
+
# 呼び、そこでracc/ffi/io-console/json等ネイティブ拡張を持つgemをビルドしようと
|
|
29
|
+
# する。ベースイメージ(espressif/idf)にはコンパイラもRubyのヘッダファイルも
|
|
30
|
+
# 無いため、`mkmf.rb can't find header files for ruby`で失敗する(実際に
|
|
31
|
+
# `rake setup_esp32`を実行して発覚した)。ビルド専用イメージなので、
|
|
32
|
+
# Rubyそのものはベースイメージのシステムruby(apt由来)をそのまま使いつつ、
|
|
33
|
+
# 拡張のビルドに必要な最小限だけ追加する。
|
|
34
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
35
|
+
build-essential \
|
|
36
|
+
ruby-dev \
|
|
37
|
+
libssl-dev \
|
|
38
|
+
libreadline-dev \
|
|
39
|
+
zlib1g-dev \
|
|
40
|
+
libyaml-dev \
|
|
41
|
+
libffi-dev \
|
|
42
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
43
|
+
|
|
44
|
+
# main/CMakeLists.txt の littlefs_create_partition_image(joltwallet/esp_littlefs、
|
|
45
|
+
# storageパーティションのイメージ作成に使う)は、ビルド時にvenvを作ってPyPIから
|
|
46
|
+
# littlefs-python をインストールする。ビルド実行時にネットワークが不安定だと
|
|
47
|
+
# ここでタイムアウトして失敗する(実際に発生した)。R2P2-ESP32側のmain/idf_component.yml
|
|
48
|
+
# で joltwallet/littlefs を 1.20.4 に固定する前提(この固定処理自体はR2P2-ESP32を
|
|
49
|
+
# プロジェクトごとにcloneするRuby側の処理に移した)で、image-building-requirements.txt
|
|
50
|
+
# の中身が littlefs-python==0.15.0 に一意に確定していることを利用し、これを
|
|
51
|
+
# イメージビルド時に取得して /opt/pip-wheels に置いておく。PIP_NO_INDEX/PIP_FIND_LINKS
|
|
52
|
+
# で「常にこのローカルディレクトリだけを見る」ようコンテナ全体のpipに指示することで、
|
|
53
|
+
# ビルド実行時はPyPIへの通信自体(バージョン解決の問い合わせも含めて)を発生させない。
|
|
54
|
+
#
|
|
55
|
+
# pip3(python3含む)はこのイメージだとentrypoint.sh(コンテナ起動時にのみ実行される)
|
|
56
|
+
# 経由でPATHに追加される、export.shが用意するESP-IDF専用のvenvにしか無い。
|
|
57
|
+
# `docker build`のRUNはentrypointを経由しない生のシェルなので、同じRUN内で
|
|
58
|
+
# 明示的にexport.shをsourceしないとpip3が見つからない(idf.py/rakeの実行時に
|
|
59
|
+
# 毎回export.shをsourceしているのと同じ理由)。
|
|
60
|
+
RUN . "${IDF_PATH}/export.sh" > /dev/null && pip3 download littlefs-python==0.15.0 -d /opt/pip-wheels
|
|
61
|
+
|
|
62
|
+
ENV PIP_NO_INDEX=1
|
|
63
|
+
ENV PIP_FIND_LINKS=/opt/pip-wheels
|
|
64
|
+
|
|
65
|
+
# bind mountしたディレクトリ(ホスト側の実UIDのまま見える)に対してgitコマンドを
|
|
66
|
+
# 実行すると、コンテナ内のユーザーとの所有者不一致でgitが「dubious ownership」
|
|
67
|
+
# エラーを出して拒否する(git 2.35.2以降のCVE-2022-24765対策)。実際に
|
|
68
|
+
# `rake setup_xxx`実行時に発生して発覚した。bind mountされたパスは実行のたびに
|
|
69
|
+
# 変わりうる(プロジェクトごとの状態ディレクトリ)ので、個別に許可するのではなく
|
|
70
|
+
# 全パスを許可しておく(このイメージはビルド専用の使い捨てコンテナ内でしか
|
|
71
|
+
# 使わないため、安全性への影響は無視できる)。
|
|
72
|
+
RUN git config --system --add safe.directory '*'
|
|
73
|
+
|
|
74
|
+
# R2P2-ESP32のrakelib/setup.rake(`rake setup_xxx`でmruby/picorubyをビルドする処理)は
|
|
75
|
+
# 内部で`bundle install`を呼ぶ。このイメージにはベースイメージ(espressif/idf)が
|
|
76
|
+
# 標準で持つRuby(bundlerはデフォルトgemとしてライブラリだけは入っているが、
|
|
77
|
+
# `bundle`コマンドの実行ファイルは生成されていない)がある。実際に`rake setup_esp32`
|
|
78
|
+
# を実行して`bundle: command not found`(exit 127)になったことで発覚した。
|
|
79
|
+
RUN gem install bundler --no-document
|
|
80
|
+
|
|
81
|
+
# R2P2-ESP32の状態ディレクトリをここへbind mountして使う想定のデフォルトWORKDIR。
|
|
82
|
+
WORKDIR /R2P2-ESP32
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Raspberry Pi Pico(R2P2)用のビルド専用イメージ。
|
|
2
|
+
#
|
|
3
|
+
# docker build -t picoride-builder-pico docker/pico
|
|
4
|
+
#
|
|
5
|
+
# ESP32用(docker/esp32/Dockerfile)と同じく、ツールチェインだけを持つ使い捨て
|
|
6
|
+
# コンテナ用のイメージ。picorideサーバーがプロジェクト設定のターゲットが
|
|
7
|
+
# pico / pico_w / pico2 / pico2_w のときにこのイメージで docker run する
|
|
8
|
+
# (lib/picoride/server.rb の BUILDER_IMAGES 参照)。
|
|
9
|
+
#
|
|
10
|
+
# R2P2(Pico版)は picoruby 本体に `picoruby-r2p2` gem として統合されているので、
|
|
11
|
+
# ビルド対象のソースは picoruby のチェックアウト。これはイメージには焼き込まず、
|
|
12
|
+
# プロジェクトごとの状態ディレクトリ(<project>/.r2p2-pico)をホスト側のRubyコードが
|
|
13
|
+
# 初回に git clone し、`docker run -v <state_dir>:/picoruby` でbind mountして使う
|
|
14
|
+
# (lib/picoride/r2p2_state.rb の ensure_pico_checkout 参照)。ビルドは
|
|
15
|
+
# `rake r2p2:{vm}:{board}:prod`(picoruby/tasks/picoruby/r2p2.rake)。
|
|
16
|
+
FROM ubuntu:24.04
|
|
17
|
+
|
|
18
|
+
SHELL ["/bin/bash", "-c"]
|
|
19
|
+
|
|
20
|
+
ENV LANG=C.UTF-8
|
|
21
|
+
ENV LC_ALL=C.UTF-8
|
|
22
|
+
ENV DEBIAN_FRONTEND=noninteractive
|
|
23
|
+
|
|
24
|
+
# - gcc-arm-none-eabi 一式: libmruby.a と R2P2 本体のクロスコンパイル
|
|
25
|
+
# - build-essential: mrbc(ホスト向け)や pioasm・picotool のビルド
|
|
26
|
+
# - cmake(>= 3.22)/ python3: pico-sdk のビルドシステム
|
|
27
|
+
# - ruby / rake: picoruby のビルド(rake)。r2p2 のビルドは `bundle exec` を使わない
|
|
28
|
+
# 素の `rake` なので Bundler / gem のインストールは不要
|
|
29
|
+
# - zip: r2p2 の cmake がリンク後に成果物(.uf2 等)を zip にまとめる。無いと
|
|
30
|
+
# .uf2 自体はできているのにビルドが失敗扱いになる(実際のビルドで確認)
|
|
31
|
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
32
|
+
build-essential \
|
|
33
|
+
ca-certificates \
|
|
34
|
+
cmake \
|
|
35
|
+
gcc-arm-none-eabi \
|
|
36
|
+
git \
|
|
37
|
+
libnewlib-arm-none-eabi \
|
|
38
|
+
libstdc++-arm-none-eabi-newlib \
|
|
39
|
+
python3 \
|
|
40
|
+
rake \
|
|
41
|
+
ruby \
|
|
42
|
+
zip \
|
|
43
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
44
|
+
|
|
45
|
+
# pico-sdk はイメージに焼き込む。picoruby の rake タスク(r2p2:check_pico_sdk)は
|
|
46
|
+
# PICO_SDK_PATH の pico-sdk が `git describe --tags --exact-match` で
|
|
47
|
+
# picoruby 側の PICO_SDK_TAG(mrbgems/picoruby-r2p2/r2p2_config.rb)と一致することを
|
|
48
|
+
# 要求する。picoruby 側が SDK のバージョンを上げたらビルドがその旨のエラーで
|
|
49
|
+
# 止まるので、そのときは --build-arg PICO_SDK_VERSION=<新しいタグ> でイメージを
|
|
50
|
+
# 作り直す。
|
|
51
|
+
ARG PICO_SDK_VERSION=2.3.0
|
|
52
|
+
RUN git clone --depth 1 --branch "${PICO_SDK_VERSION}" \
|
|
53
|
+
--recurse-submodules --shallow-submodules \
|
|
54
|
+
https://github.com/raspberrypi/pico-sdk.git /opt/pico-sdk
|
|
55
|
+
ENV PICO_SDK_PATH=/opt/pico-sdk
|
|
56
|
+
|
|
57
|
+
# picotool(.elf → .uf2 変換に使う)。pico-sdk 2.x は SDK と同じバージョンの
|
|
58
|
+
# picotool がインストールされていないと、cmake の構成時に GitHub から picotool を
|
|
59
|
+
# 取ってきてビルドしようとする(ビルドのたびにネットワークが必要になる)。
|
|
60
|
+
# それを避けるため、同じバージョンをイメージビルド時に /usr/local へ入れておく
|
|
61
|
+
# (find_package(picotool) が /usr/local/lib/cmake/picotool を見つける)。
|
|
62
|
+
# USB 経由でボードを操作する機能は使わないので libusb 無しでビルドする。
|
|
63
|
+
RUN git clone --depth 1 --branch "${PICO_SDK_VERSION}" \
|
|
64
|
+
https://github.com/raspberrypi/picotool.git /tmp/picotool \
|
|
65
|
+
&& cmake -S /tmp/picotool -B /tmp/picotool/build \
|
|
66
|
+
-DPICO_SDK_PATH=/opt/pico-sdk -DPICOTOOL_NO_LIBUSB=1 -DCMAKE_BUILD_TYPE=Release \
|
|
67
|
+
&& cmake --build /tmp/picotool/build -j"$(nproc)" \
|
|
68
|
+
&& cmake --install /tmp/picotool/build \
|
|
69
|
+
&& rm -rf /tmp/picotool
|
|
70
|
+
|
|
71
|
+
# bind mount したディレクトリ(ホスト側の実UIDのまま見える)に対して git を
|
|
72
|
+
# 実行すると「dubious ownership」で拒否される(ESP32 用イメージと同じ問題。
|
|
73
|
+
# r2p2 の cmake は `git rev-parse` でリビジョンをファイル名に埋め込む)。
|
|
74
|
+
RUN git config --system --add safe.directory '*'
|
|
75
|
+
|
|
76
|
+
# picoruby の状態ディレクトリをここへ bind mount して使う想定のデフォルト WORKDIR。
|
|
77
|
+
WORKDIR /picoruby
|
data/exe/picoride
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "optparse"
|
|
5
|
+
require "picoride"
|
|
6
|
+
require "picoride/server"
|
|
7
|
+
|
|
8
|
+
options = {}
|
|
9
|
+
OptionParser.new do |op|
|
|
10
|
+
op.banner = "Usage: picoride [options]"
|
|
11
|
+
op.on("-p port", "set the port (default is 4567)") { |val| options[:port] = Integer(val) }
|
|
12
|
+
op.on("-o addr", "set the bind address (default is localhost)") { |val| options[:bind] = val }
|
|
13
|
+
op.on("-e env", "set the environment (default is development)") { |val| options[:environment] = val.to_sym }
|
|
14
|
+
end.parse!(ARGV)
|
|
15
|
+
|
|
16
|
+
Picoride::Server.run!(options)
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Picoride
|
|
2
|
+
class BackgroundJob
|
|
3
|
+
# ブラウザ(PicoRuby.wasm)側でのJSONパース/描画が重くなりすぎないよう、
|
|
4
|
+
# API経由で返すログは末尾のみに切り詰める。全量は @state[:log] に残る。
|
|
5
|
+
LOG_TAIL_LIMIT = 8_000
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@mutex = Mutex.new
|
|
9
|
+
@state = { status: "idle", log: "", started_at: nil, finished_at: nil }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# 実行中でなければバックグラウンドスレッドでcmdを開始してtrueを返す。
|
|
13
|
+
# 実行中ならなにもせずfalseを返す(呼び出し側で409にする)。
|
|
14
|
+
def start(cmd)
|
|
15
|
+
started = @mutex.synchronize do
|
|
16
|
+
break false if @state[:status] == "running"
|
|
17
|
+
|
|
18
|
+
@state[:status] = "running"
|
|
19
|
+
@state[:log] = ""
|
|
20
|
+
@state[:started_at] = Time.now.to_i
|
|
21
|
+
@state[:finished_at] = nil
|
|
22
|
+
true
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Thread.new { run(cmd) } if started
|
|
26
|
+
started
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def running?
|
|
30
|
+
@mutex.synchronize { @state[:status] == "running" }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def to_response_json
|
|
34
|
+
@mutex.synchronize do
|
|
35
|
+
full_log = @state[:log]
|
|
36
|
+
truncated = full_log.length > LOG_TAIL_LIMIT
|
|
37
|
+
|
|
38
|
+
{
|
|
39
|
+
status: @state[:status],
|
|
40
|
+
log: truncated ? full_log[-LOG_TAIL_LIMIT..-1] : full_log,
|
|
41
|
+
log_truncated: truncated,
|
|
42
|
+
started_at: @state[:started_at],
|
|
43
|
+
finished_at: @state[:finished_at]
|
|
44
|
+
}.to_json
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def run(cmd)
|
|
51
|
+
Open3.popen2e("bash", "-c", cmd) do |stdin, stdout_and_stderr, wait_thread|
|
|
52
|
+
stdin.close
|
|
53
|
+
stdout_and_stderr.each_line do |line|
|
|
54
|
+
@mutex.synchronize { @state[:log] << line }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
success = wait_thread.value.success?
|
|
58
|
+
@mutex.synchronize do
|
|
59
|
+
@state[:status] = success ? "success" : "failed"
|
|
60
|
+
@state[:finished_at] = Time.now.to_i
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
@mutex.synchronize do
|
|
65
|
+
@state[:status] = "failed"
|
|
66
|
+
@state[:log] << "\n[#{e.class}] #{e.message}\n"
|
|
67
|
+
@state[:finished_at] = Time.now.to_i
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
require "perfect_toml"
|
|
3
|
+
|
|
4
|
+
module Picoride
|
|
5
|
+
# ~/.picoride/project.toml への永続化。
|
|
6
|
+
#
|
|
7
|
+
# 「一度開いたら覚えておいてほしい、サーバーを再起動しても開かれている状態で
|
|
8
|
+
# 起動してほしい」という要望を受けて追加した。保存先はプロジェクトディレクトリの
|
|
9
|
+
# 外(ホームディレクトリ配下の`.picoride/`。r2p2_state.rbの状態ディレクトリと
|
|
10
|
+
# 同じ「プロジェクト本体の外に置く」発想)。
|
|
11
|
+
#
|
|
12
|
+
# その後「設定(ターゲット・VM・USB Console)はプロジェクト単位で持たせてほしい」
|
|
13
|
+
# というフィードバックを受けて、各プロジェクトの設定も同じファイルに持たせる
|
|
14
|
+
# ようにした(`platform`/`vm`/`usb_console`。server.rbのread_project_config/
|
|
15
|
+
# project_settings参照)。
|
|
16
|
+
#
|
|
17
|
+
# なお、このファイルはかつて「ワークスペース」(複数プロジェクトを束ねる
|
|
18
|
+
# 親ディレクトリ)を保存する`workspace.toml`という名前だった。「ワークスペース=
|
|
19
|
+
# プロジェクトだった」というフィードバックにより、プロジェクトを束ねる上位概念
|
|
20
|
+
# 自体が不要と判明したため、`project.toml`へ改名し、内容もプロジェクト直接を
|
|
21
|
+
# 指す形に変わっている(旧workspace.tomlの自動移行は行わない)。
|
|
22
|
+
#
|
|
23
|
+
# TOMLの読み書きは自前実装せず、PerfectTOML(mame/perfect_toml、単一ファイル・
|
|
24
|
+
# 無依存でTOML v1.0.0に完全準拠するパーサ/ジェネレータ)を使う。ファイルは
|
|
25
|
+
# active = "最後にアクティブだったプロジェクトの絶対パス"
|
|
26
|
+
#
|
|
27
|
+
# [名前]
|
|
28
|
+
# path = "絶対パス"
|
|
29
|
+
# platform = "esp32" # 未設定なら書かない(TOMLにnullは無いため)
|
|
30
|
+
# vm = "femtoruby" # 同上
|
|
31
|
+
# usb_console = false
|
|
32
|
+
# という形(nameはPerfectTOML.generateがHashのキーから自動でTOMLのテーブル名に
|
|
33
|
+
# 変換してくれる。空白を含む名前なども自動でクォートされる)。
|
|
34
|
+
module ProjectStore
|
|
35
|
+
DIR = File.join(Dir.home, ".picoride")
|
|
36
|
+
PATH = File.join(DIR, "project.toml")
|
|
37
|
+
|
|
38
|
+
module_function
|
|
39
|
+
|
|
40
|
+
# 保存されているプロジェクト一覧(存在しなくなったディレクトリは除外)、
|
|
41
|
+
# 最後にアクティブだったプロジェクトの絶対パス、各プロジェクトの設定を返す。
|
|
42
|
+
# 戻り値: {
|
|
43
|
+
# active: "絶対パス" または nil,
|
|
44
|
+
# paths: [絶対パスの配列(出現順)],
|
|
45
|
+
# settings: { "絶対パス" => {"platform"=>, "vm"=>, "usb_console"=>}, ... }
|
|
46
|
+
# }
|
|
47
|
+
def load
|
|
48
|
+
data =
|
|
49
|
+
begin
|
|
50
|
+
PerfectTOML.load_file(PATH)
|
|
51
|
+
rescue Errno::ENOENT, PerfectTOML::ParseError
|
|
52
|
+
# ファイルが無い(初回起動)、あるいはユーザーが手で編集して壊した
|
|
53
|
+
# 場合でも、ここで落ちずに「何も保存されていない」扱いにする。
|
|
54
|
+
{}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
active = data["active"]
|
|
58
|
+
paths = []
|
|
59
|
+
settings = {}
|
|
60
|
+
|
|
61
|
+
data.each do |name, value|
|
|
62
|
+
next if name == "active" || !value.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
path = value["path"]
|
|
65
|
+
next unless path.is_a?(String) && File.directory?(path)
|
|
66
|
+
|
|
67
|
+
paths << path
|
|
68
|
+
settings[path] = {
|
|
69
|
+
"platform" => value["platform"],
|
|
70
|
+
"vm" => value["vm"],
|
|
71
|
+
"usb_console" => value["usb_console"] == true
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
active = nil unless active.is_a?(String) && paths.include?(active)
|
|
76
|
+
|
|
77
|
+
{ active: active, paths: paths, settings: settings }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# 現在の状態をまるごと書き込む(差分更新ではなく毎回全体を作り直す。
|
|
81
|
+
# プロジェクトの開閉・設定変更はそう頻繁な操作ではないので、素朴な
|
|
82
|
+
# 全体書き直しで十分という判断)。
|
|
83
|
+
#
|
|
84
|
+
# settingsはキーワード必須にしてある(デフォルト値を用意して省略を許すと、
|
|
85
|
+
# 「プロジェクトを開くだけ」の呼び出し箇所で既存の設定を空のHashで
|
|
86
|
+
# 上書きしてしまうミスを構造的に防げないため。実際に一度このミスを
|
|
87
|
+
# 作りかけた)。
|
|
88
|
+
def save(active:, paths:, settings:)
|
|
89
|
+
FileUtils.mkdir_p(DIR)
|
|
90
|
+
|
|
91
|
+
data = {}
|
|
92
|
+
data["active"] = active if active
|
|
93
|
+
|
|
94
|
+
used_names = {}
|
|
95
|
+
paths.each do |path|
|
|
96
|
+
entry = { "path" => path }
|
|
97
|
+
s = settings[path]
|
|
98
|
+
if s
|
|
99
|
+
# TOMLにnullは無いため、未設定(nil)のキーは書かない。
|
|
100
|
+
entry["platform"] = s["platform"] if s["platform"]
|
|
101
|
+
entry["vm"] = s["vm"] if s["vm"]
|
|
102
|
+
entry["usb_console"] = true if s["usb_console"] == true
|
|
103
|
+
end
|
|
104
|
+
data[unique_name(path, used_names)] = entry
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
PerfectTOML.save_file(PATH, data)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# プロジェクト名は既定でディレクトリ名(パスの最後のセグメント)にする
|
|
111
|
+
# (menu_bar.rbのproject_labelと同じロジック。フロントとサーバで共有
|
|
112
|
+
# できないので両側に同じものを持たせてある)。
|
|
113
|
+
def display_name(path)
|
|
114
|
+
name = path.to_s.split("/").last
|
|
115
|
+
name.nil? || name.empty? ? path.to_s : name
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# 別のパスのプロジェクトが同じディレクトリ名(例: 違う場所にある2つの
|
|
119
|
+
# "projects"ディレクトリ)だった場合に備えて、TOMLのテーブル名として
|
|
120
|
+
# 一意になるよう連番を振る。
|
|
121
|
+
def unique_name(path, used_names)
|
|
122
|
+
base = display_name(path)
|
|
123
|
+
name = base
|
|
124
|
+
counter = 2
|
|
125
|
+
while used_names.key?(name)
|
|
126
|
+
name = "#{base}_#{counter}"
|
|
127
|
+
counter += 1
|
|
128
|
+
end
|
|
129
|
+
used_names[name] = true
|
|
130
|
+
name
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|