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.
@@ -0,0 +1,137 @@
1
+ # プロジェクトごとに独立したR2P2-ESP32のビルド状態(sdkconfig・build/・
2
+ # managed_components等)を管理する。
3
+ #
4
+ # 以前は1つのR2P2-ESP32チェックアウトを全プロジェクトで共有していたが、
5
+ # ターゲットやVMが異なるプロジェクトを切り替えるたびに実質フルリビルドに
6
+ # なってしまう問題があったため、プロジェクトごとに独立したチェックアウトを
7
+ # 持たせる方式に変えた(詳細はCLAUDE.md参照)。
8
+ #
9
+ # 状態ディレクトリはプロジェクト本体の外(既定 ~/.picoride/r2p2-esp32/)に
10
+ # 置く。R2P2-ESP32はサイズが大きく、ユーザーのプロジェクト(gitリポジトリかもしれない)
11
+ # に混ざるべきではないため。
12
+ #
13
+ # 以前はDockerfileの`RUN`でイメージビルド時に1回だけ実行していたR2P2-ESP32の
14
+ # git clone・build_config.rbフックのパッチ・littlefsバージョン固定を、ここでは
15
+ # 「プロジェクトが初めてこの状態ディレクトリを使うとき」に動的に実行する処理として
16
+ # 素のRubyで書き直してある。シェルのクォート/エスケープに起因するバグ
17
+ # (このセッションで実際に2回踏んだ`\&\&`混入)を構造的に避けられるのが利点。
18
+ require "fileutils"
19
+ require "open3"
20
+
21
+ module Picoride
22
+ module R2P2State
23
+ R2P2_ESP32_REPO_URL = "https://github.com/picoruby/R2P2-ESP32.git"
24
+
25
+ # components/picoruby-esp32/build_config/*.rb の `do |conf| ... end` ブロック末尾に
26
+ # 差し込むフック。PROJECT_BUILD_CONFIG環境変数(未設定なら何もしない)が指す
27
+ # Rubyファイルをconfのコンテキストで評価するだけで、本家の内容には一切手を
28
+ # 加えない(詳細はapp.rbの generated_build_config_content 参照)。
29
+ BUILD_CONFIG_HOOK_LINE =
30
+ 'conf.instance_eval(File.read(ENV["PROJECT_BUILD_CONFIG"]), ENV["PROJECT_BUILD_CONFIG"]) ' \
31
+ 'if ENV["PROJECT_BUILD_CONFIG"] && File.exist?(ENV["PROJECT_BUILD_CONFIG"])'
32
+
33
+ # main/idf_component.yml は joltwallet/littlefs を "~=1.20.0"(レンジ指定)で
34
+ # 依存させているため、実際に解決されるパッチバージョンが変わりうる
35
+ # (1.20.0〜1.20.4の間でimage-building-requirements.txtの中身、つまり
36
+ # littlefs-pythonのバージョンが変わることを確認済み)。バージョン解決そのものを
37
+ # 不要にするため、実在を確認できた最新パッチ版に固定する
38
+ # (Dockerfile側でこのバージョンのlittlefs-pythonをオフラインインストールできる
39
+ # よう準備してあるので、揃える必要がある)。
40
+ LITTLEFS_VERSION_FROM = 'joltwallet/littlefs: "~=1.20.0"'
41
+ LITTLEFS_VERSION_TO = 'joltwallet/littlefs: "1.20.4"'
42
+
43
+ module_function
44
+
45
+ # 状態ディレクトリが無ければgit cloneし、必要なパッチを当てる。
46
+ # 既にcloneされているディレクトリに対しては何もしない(パッチも初回のみ)。
47
+ def ensure_checkout(state_dir)
48
+ return if File.directory?(File.join(state_dir, ".git"))
49
+
50
+ FileUtils.mkdir_p(File.dirname(state_dir))
51
+ run!("git", "clone", "--depth", "1", R2P2_ESP32_REPO_URL, state_dir)
52
+ run!("git", "-C", state_dir, "submodule", "update", "--init", "--recursive")
53
+
54
+ patch_build_config_hooks(state_dir)
55
+ pin_littlefs_version(state_dir)
56
+ end
57
+
58
+ def patch_build_config_hooks(state_dir)
59
+ glob = File.join(state_dir, "components", "picoruby-esp32", "build_config", "*.rb")
60
+ Dir.glob(glob).each { |path| patch_build_config_hook(path) }
61
+ end
62
+
63
+ def patch_build_config_hook(path)
64
+ content = File.read(path)
65
+ return if content.include?("PROJECT_BUILD_CONFIG") # 冪等性: 既にパッチ済みなら何もしない
66
+
67
+ stripped = content.rstrip
68
+ raise "unexpected build_config format (no trailing 'end'): #{path}" unless stripped.end_with?("end")
69
+
70
+ body = stripped[0..-4].rstrip # 末尾の "end" を取り除く
71
+ File.write(path, "#{body}\n\n #{BUILD_CONFIG_HOOK_LINE}\nend\n")
72
+ end
73
+
74
+ def pin_littlefs_version(state_dir)
75
+ path = File.join(state_dir, "main", "idf_component.yml")
76
+ content = File.read(path)
77
+ patched = content.sub(LITTLEFS_VERSION_FROM, LITTLEFS_VERSION_TO)
78
+ File.write(path, patched) if patched != content
79
+ end
80
+
81
+ def run!(*cmd)
82
+ system(*cmd, exception: true)
83
+ end
84
+
85
+ # --- Raspberry Pi Pico(R2P2)用 -----------------------------------------
86
+ #
87
+ # R2P2(Raspberry Pi Pico版)は以前は独立リポジトリ(picoruby/R2P2)だったが、
88
+ # picoruby本体に`picoruby-r2p2` gemとして統合された(旧リポジトリはアーカイブ扱い)。
89
+ # そのため状態ディレクトリにはpicoruby本体をcloneし、ビルドはpicorubyの
90
+ # `rake r2p2:{vm}:{board}:{mode}`(tasks/picoruby/r2p2.rake)で行う。
91
+ #
92
+ # pico-sdkはイメージ側(docker/pico/Dockerfile、/opt/pico-sdk)に焼き込んである
93
+ # (picotoolのバージョンをSDKと揃える必要があり、両方イメージで固定したほうが
94
+ # 確実なため)。picoruby側のサブモジュールのpico-sdk(と、エミュレータ用で
95
+ # ビルドには不要なrp2040js)はcloneしない。
96
+ PICORUBY_REPO_URL = "https://github.com/picoruby/picoruby.git"
97
+ PICO_SKIPPED_SUBMODULES = %w[
98
+ mrbgems/picoruby-r2p2/lib/pico-sdk
99
+ mrbgems/picoruby-r2p2/lib/rp2040js
100
+ ].freeze
101
+
102
+ # cloneとサブモジュール取得・フックのパッチがすべて終わったことを示す目印。
103
+ # ESP32側は「.gitがあれば完了扱い」だが、cloneは成功してサブモジュール取得で
104
+ # 失敗した場合に二度と再試行されなくなるので、Pico側は完了マーカーで判定する。
105
+ PICO_READY_MARKER = ".picoride-ready"
106
+
107
+ def ensure_pico_checkout(state_dir)
108
+ return if File.file?(File.join(state_dir, PICO_READY_MARKER))
109
+
110
+ unless File.directory?(File.join(state_dir, ".git"))
111
+ FileUtils.mkdir_p(File.dirname(state_dir))
112
+ run!("git", "clone", "--depth", "1", PICORUBY_REPO_URL, state_dir)
113
+ end
114
+
115
+ submodules = pico_submodule_paths(state_dir) - PICO_SKIPPED_SUBMODULES
116
+ run!("git", "-C", state_dir, "submodule", "update", "--init", "--recursive", "--", *submodules)
117
+
118
+ patch_pico_build_config_hooks(state_dir)
119
+ File.write(File.join(state_dir, PICO_READY_MARKER), "")
120
+ end
121
+
122
+ def pico_submodule_paths(state_dir)
123
+ out, status = Open3.capture2(
124
+ "git", "-C", state_dir, "config", "-f", ".gitmodules", "--get-regexp", '\.path$'
125
+ )
126
+ raise "failed to read .gitmodules in #{state_dir}" unless status.success?
127
+
128
+ out.lines.map { |line| line.split(" ", 2)[1].to_s.strip }.reject(&:empty?)
129
+ end
130
+
131
+ # picoruby/build_config/r2p2-{vm}-{board}.rb も R2P2-ESP32 と同じく
132
+ # `MRuby::CrossBuild.new(...) do |conf| ... end` の形なので、同じフックを差し込む。
133
+ def patch_pico_build_config_hooks(state_dir)
134
+ Dir.glob(File.join(state_dir, "build_config", "r2p2-*.rb")).each { |path| patch_build_config_hook(path) }
135
+ end
136
+ end
137
+ end