検証とリリース
ツアーの締めくくりです。 型チェッカー、ヘッドレス実行とゲート、リリース、そして今できないことを見ます。
型チェッカーとの併用
yokan は型スタブを同梱しているので、pyright(VS Code の Pylance)によるチェックがそのまま働きます。
スタブは実行時の形をそのまま伝えます。
@store はクラス名がそのままインスタンスなので、Settings.set_dark(True) は正しくメソッド呼び出しとして扱われ、on_change=Settings.set_dark のようなハンドラ渡しも (bool) -> None として通ります。
@model と @value はフィールドどおりのコンストラクタを持つと伝わり、Weak[Node] はチェッカーには Node | None として見えます(実際、読んだときの意味もその通りです)。
mypy には、クラスデコレータによる型の変換を適用しないという既知の制限があります。
このため mypy では @store のメソッド呼び出しが「self が渡されていない」と誤検出されます。
チェックには pyright を推奨します。
型チェッカーが見るのは Python の型です。
この形をコンパイラが受け取れるかどうかは、yokan check app.py が答えます。
アプリが import するモジュールをすべて調べ、見つかった拒否を全部 ファイル:行:列 の形で示します。
断るところがなければ何も言いません。
モジュール直下の文とビューの各行を、それぞれ独立に調べます。
一度の実行でファイル全体の答えが返るので、次の一つのために往復し直す必要はありません。
宣言そのものを断られたものを読んでいる文は、拒否ではなく「調べられなかった」として数えます。
そこで出るメッセージは、起きなかった宣言についてのものであって、それを読んでいる行についてのものではないからです。
コンパイラを起動しないので、編集しながら何度でも回せます。
check は警告も出します。
拒否ではありません。
翻訳は通っていて、それでも直したほうがよいところがある、という合図です。
$ yokan check app.py
app.py:37:9: warning — these assignments make a reference cycle: b.parent → a.kid → b. The compiled run counts references and never frees a cycle (the CPython you develop on collects it), so write the reference that points back as `Weak[...]`.
b.parent = a
^
今ある警告はメモリの話です。 循環はコンパイル後に解放されず、開発に使う CPython は回収するので、二つの実行で振る舞いが分かれます。 その差はどのダンプにも現れないので、ゲートでは見つかりません。
見つかる形は二つです。
一つはフィールドの型で、二つ以上のモデルにまたがって輪が閉じている場合です(Kid.owner → Parent.kids → Kid)。
もう一つはハンドラの中の代入で、往復を書いた場合です(a.kid = b のあとに b.parent = a)。
後者があるので、自分自身のクラスを参照するモデルも見つかります。
型だけでは、リストや木と輪の区別がつかないからです。
--strict を付けると、警告が失敗になります。
エディタ
エディタが走らせるのは yokan check です。
Yokan のほかに入れるものはありません。
file:line:col: message と、その行とキャレットを出し、方言の内側なら何も言わず、所要はおよそ 0.1 秒です。
見つかった拒否は最初の一つではなく全部を報告するので、一度の実行でファイル全体の答えが返ります。
Vim と Neovim は設定二つで、拒否が quickfix に入ります。
setlocal makeprg=yokan\ check\ %
setlocal errorformat=%f:%l:%c:\ %m
autocmd BufWritePost *.py silent make! | redraw!
:copen で一覧、:cnext で順に移動し、どの項目もファイルのその桁を開きます。
VS Code は problem matcher で同じ形を読みます。
.vscode/tasks.json に次を置きます。
{
"version": "2.0.0",
"tasks": [
{
"label": "yokan check",
"type": "shell",
"command": "yokan check ${file}",
"presentation": { "reveal": "silent" },
"problemMatcher": {
"owner": "yokan",
"fileLocation": ["autoDetect", "${workspaceFolder}"],
"pattern": {
"regexp": "^(.+?):(\\d+):(\\d+): (.+)$",
"file": 1,
"line": 2,
"column": 3,
"message": 4
}
}
}
]
}
拒否は問題パネルに、それが指す行に付いて出ます。 VS Code にはタスクを保存時に走らせる仕組みが標準では無いので、これはキー割り当てかコマンドパレットから呼ぶことになります。 保存を見張る拡張を入れれば、保存のたびに走ります。
ほかのエディタも同じです。
check がこの形を出しているのは、これがコンパイラが四十年出してきた形だからです。
チェックアウトの中では、コマンドが uv run yokan_gate.py check <ファイル> に変わるだけです。
テスト
アプリは普通の Python のモジュールなので、テストも普通の Python のテストです。
使い慣れたテストランナーが、そのまま使えます。
yokan.headless(view, state, script) はウィンドウを出さずにスクリプトを流し、画面をテキストで返します(ステップ前のダンプと、ステップ後のダンプ)。
テストはその文字列を検査します。
編集の合間には、同じ実行をコマンドラインから呼べます。 画面を画像として残すこともできます。
$ yokan show app.py --script "keydown:left,advance:33,advance:33" --frames shots/ --scale 3
Column[Canvas(160x120, scale=4, bg=#000000)[
Sprite(assets/sheet.png, 0,0 8x8 at 54,100)
PixelText(4, 4, "SCORE 0", #eeeeee)
]]
3 frames in shots/
コンパイルの前に拒否を調べるので、書き間違いは1秒で返ってきます。
ウィンドウは開かず画面を出力し、--frames を付けると各ステップのキャンバスを PNG で残します。
ffmpeg があれば --gif で1本にまとめます。
ディスプレイを使わないので、ssh 越しでも CI でも動きます。
描いているのは、ウィンドウと同じラスタライザです。
これが往復の速いほうで、遅いほうが yokan gate です。
リリースする実行と一致することを証明するのは、そちらの仕事です。
# tests/test_app.py
def test_clicking_counts(app, run):
assert "count: 2" in run(app, "click:+1,click:+1")
def test_typing_greets(app, run):
assert "Momo" in run(app, "input:Momo")
app と run は wheel に載っているプラグインが渡します。
Yokan を入れてあるテストなら、何も書かずに使えます。
app は app.py を実行せずに読み込み(run(...) は __main__ の下にあります)、run はウィンドウを出さずにスクリプトを流します。
run のたびにアプリは最初の状態から始まるので、一つのテストで何度でも動かせますし、読める数は人が見る数と同じです。
このフィクスチャは必須ではありません。
from yokan_testing import run_script は同じ仕掛けを別の名前で呼ぶ書き方で、どちらも yokan.headless(view, state, script) の上に乗っています。
実行が返すのは Transcript です。
これまでどおりの文字列そのもので(in、==、str() の振る舞いは変わりません)、実行の内訳がそこに付いています。
def test_the_middle_of_the_run(app, run):
t = run(app, "click:+1,dump,click:+10")
assert "count: 0" in t.before # 開いたときの画面
assert "count: 1" in t.dumps[0] # dump ステップが求めた画面
assert "count: 11" in t.after # 終わったときの画面
assert t.steps[0].step == "click:+1"
a11y はアクセシビリティツリーを記録に残します。
スクリーンリーダーに渡されるのと同じものです。
mem はその実行が抱えているオブジェクトの数を数えます。
def test_a_screen_reader_hears_the_count(app, run):
t = run(app, "click:+1,a11y")
assert 'label "count: 1"' in t.a11y
assert 'button "+1"' in t.a11y
画面を細かく検査するより、まるごと記録しておきたいことがあります。
それがスナップショットです。
snapshot(t) は記録を tests/__snapshots__/<テスト名>.dump と突き合わせ、--yokan-update がその内容を書き直します。
レビューの差分が、画面の変化としてそのまま読めるようになります。
ゲートもテストの一つとして書けます。
gate("click:+1") はアプリをビルドし、そのスクリプトについて両方の実行を比べ、食い違えばその内容とともに失敗します。
--yokan-gate を付けると、テストが流すすべてのスクリプトについて同じことをします。
テスト一式が、そのまま「コンパイル済みのアプリも同じことをする」証明になります。
これはコンパイルを伴うので、意図して遅いほうに置いてあります。
普段はテスト一式に gate(...) を一つ置いておき、リリース前に --yokan-gate を使うのがよい配分です。
スクリプトの語彙は、次の節にまとめてあります。
人がアプリにできることは、テストにもできます。
クリック、入力、キー、ファイルのドロップ、advance:1000 で1秒進めること、どれもです。
ハンドラもストアのメソッドも value クラスも普通の Python なので、計算だけの部分は直接呼んで確かめられます。
このテストが見ているのは開発実行、つまり CPython です。
リリースするバイナリがそれと一致するかどうかは別の話で、そちらは yokan gate が答えます(同じスクリプトを両方の実行に流し、バイト単位で比べます)。
二つは役割が違います。
テストは「アプリが正しいことをする」と言い、ゲートは「コンパイル済みのアプリが同じことをする」と言います。
check と show とゲートがどう一つの往復になり、なぜエージェントが自分で回せるのかは、エージェントと作るにあります。
ヘッドレス実行とゲート
アプリはウィンドウなしでも動かせます。 検証はここから始まります。
PIXIE_SCRIPT は環境変数です。
Yokan のアプリはどれもこれを読みます。
開発実行でも、リリースビルドのバイナリでも同じです。
指定するとアプリはウィンドウを開かず、画面をダンプし、書いたステップを再生し、もう一度画面をダンプして終了します。
yokan gate と yokan show --script が内部で設定しているのもこれなので、同じスクリプトの文字列が三か所とも同じように使えます。
名前が Yokan ではないのは、これを読む仕組みが Yokan のものではないからです。
Yokan が土台にしている pixie のもので、あちらでも同じ綴りです。
ステップの語彙は click[@n]:<ラベル>(ボタン、リンク、表の列見出し)、input[@n]:<テキスト>、submit[@n]、slide[@n]:<値>、select[@n]:<ラベル>(選択肢のある要素の項目、または表の行を先頭セルで)、key:<chord>、keydown:<キー> と keyup:<キー>、menu:<項目>、file:<path>、drop:<path>、hover[@n]:<i>(チャートの i 番目の点にポインタを載せる。hover: で外す)、advance:<ms>、theme:light|dark、a11y、mem、dump です。
@n はツリー順で n 番目の一致を選ぶので、同じラベルのボタンが並ぶ行にも届きます(0 から数えるので、click@2:削除 は三つ目)。
dump はその時点の画面を出力するので、最初と最後だけでなく途中の状態も検査できます。
テキストに含めるカンマは \, と書きます(input:hello\, world)。
a11y と mem は画面の中ではなく、画面の隣に出ます。
a11y はゲートも突き合わせます。
両方の実行が同じアクセシビリティツリーを組み立てるからです。
mem は読むためのもので、突き合わせません。
二つの実行が抱えるオブジェクトは、作りからして違うからです。
ステップの前後で、画面の内容がテキストになって標準出力に出ます。
テストからは、yokan.headless(view, state, script) が同じ文字列を返します。
ゲートは同じスクリプトを開発版とリリース版の両方で再生し、ダンプを突き合わせます。
$ yokan gate app.py --script "click:+1,input:Momo" --release
GATE OK — 2 dump lines identical in both runs
ファイルや DB に書くアプリは --fresh path/to/file.db を付けると、先に走った側の書き込みが、後に走る側の最初の読みに混ざりません。
PEP 723 の依存を持つアプリは、ゲート自体を uv run --with <dep> の下で回します。
リリース
$ yokan build app.py --release # ネイティブバイナリ(検証なし)
$ yokan build app.py --release --app # macOS の .app バンドル
$ yokan build app.py --release --bundle # @py あり: ランタイム同梱フォルダ
$ yokan build app.py --release --bundle --app # ランタイムごと .app に
$ yokan build app.py --release --onefile # 1 ファイル配布
$ yokan build app.py --release --appimage # Linux: 1 つの .AppImage
ネイティブビルドの前提は Rust ツールチェーンだけです。
コンパイル先の Rust クレート群はリポジトリに入っていますが、最初のネイティブビルドが、使っているバージョンに合うチェックアウトを ~/.cache/yokan/ に取ってきます。
チェックアウトの中で実行すればそちらを使い、PIXIE_REPO を指せば別の場所も使えます。
手順はインストールのページにまとまっています。
エスケープを使わないアプリのリリースバイナリは、それ自体が自己完結です(Python へのリンクはゼロ)。
--bundle は Python ランタイムと宣言済み依存を同梱したフォルダを、--onefile は 1 ファイル(stdlib のみ約 17 MB、numpy 込み約 21 MB。初回起動でキャッシュへ展開し、以後は約 40 ms で起動)を作ります。
ゲートは、その 1 ファイルに対してもスクリプトを再生できます。
ランタイムの隣には本物の python3 が 52 KB だけ同梱され、sys.executable はアプリではなくそれを指します。
補助プロセスを起動するライブラリ(multiprocessing は、何かがロックを取った時点でそうします)が起動するのは、アプリのもう一つのコピーではなく、このインタプリタです。
--app は dist/<タイトル>.app を作ります。
Dock に名前が出て、ダブルクリックで起動でき、Finder から Applications へそのまま置けます。
--bundle --app なら CPython ランタイムまで .app の中に収まり、外に何も要りません。
アプリのファイルの隣に <名前>.png(または .icns)を置くと、アイコンとして取り込まれます。
--onefile は 1 ファイル形式なので .app とは排他です。
Linux では、形も Linux のものになります。
--app は dist/<タイトル>.AppDir を作ります。
中身はバイナリ、AppRun、.desktop エントリ、アイコン、そしてホストにあるとは限らないライブラリです。
--appimage はそれを dist/<タイトル>-<arch>.AppImage に詰めます。
このプラットフォームで人に渡す 1 ファイルがこれです。
同梱するライブラリは一つの表が決めていて、開発中に使う wheel も同じ表を読みます。
libfontconfig や libxkbcommon を同梱すると、ホストが持っているフォント設定やキーボードのデータとぶつかります。
それが Linux のパッケージの壊れ方なので、その手のものは外して、動かす機械のものを使います。
--carry-libs はこれを逆にするフラグです。
配る先にライブラリがないかもしれないときのために、C ランタイム以外をすべて運びます。
--bundle と --onefile は CPython を Apple の流儀で運ぶものなので、ここでは名前を挙げて止まります。
本格アプリの例
demo/opsboard/ は三モジュール構成のダッシュボードです。
直和型のヘルスモデルを、ビューの match で分岐します。
ストア二つ、ラインチャート二枚、ラベル付きバーチャート、スロット付き KPI カード、grow で領域いっぱいに広がる仮想化アラートフィード、重大度フィルタ、fs によるレポート出力、テーマ切替、種付き乱数のモックテレメトリまで、この一つに入っています。
リリースビルドは 13.7 MB(strip 後 10.6 MB)で、Python へのリンクはありません。
小さな例は demo/ にひとそろいあります(counter、todo、ledger、moods、geometry、cards、styled、tryfetch、pyops など)。
辞書で状態を持つ 2 本(run(state={...}))を除いて、どれもゲートを通っています。
その 2 本は設計上の開発専用で、ギャラリーにもそう書いてあります。
今できないこと
この範囲の外にあるものは断られます。 黙って挙動が変わることはありません。 断りの文はファイル名と行と列を挙げ、該当行を引用します。
$ yokan build app.py --release
widgets.py:5:40: not in the dialect — text() does not take `weight=`
return text(label, size=12, weight=2)
^
今日の時点でできないことと、その理由です。
- 捕まえない素の
d[k]読み。 読みの形は.get(key, default)で、無いキーをどう扱うかは呼び出し側が決めます。try: v = d[k] except KeyError:と書けば、Python と同じようにキーの不在を捕まえられます。tryが捕まえるのは、名前に束ねた読みそのものです。 - ストアやモデルのメソッドの中で、フィールドと同じ名前をローカル、引数、ループ変数に付けること。
Python では
scoreとself.scoreは別の名前ですが、コンパイル済みの側はフィールドをその名前のまま読むので、同じscoreが二つの実行で別のものを指してしまいます。score_のように名前を変えれば、Python での意味は変わりません。 - 片方の分岐でしか代入していないローカルを後で読むこと。 その分岐が実行されなければ、Python でも NameError になる形だからです。 if と else の両方で代入すれば読めます。
int ** intの負の指数。 結果の型が実行時に変わるためです。 どちらかを float にすれば書けます。- 辞書 state(
run(state={...}))のコンパイル。 開発中は動きますが、コンパイルできる形は型付きのStateです。 - Protocol 束縛のヘルパをビューから呼ぶこと(ハンドラからは呼べます)。
- Value クラスのメソッドをビューから呼ぶこと(ハンドラからは呼べます。ビューはフィールドを読みます)。
- ストアとモデルのメソッドをビューから呼ぶこと。
画面を組み立てる側は状態を読むだけですが、メソッドは書き込むかもしれないからです。
読み取り専用の形は
@propertyで、ビューはフィールドと同じように読めます。 - モデルのリストをビューで直接繰り返すこと。
今日は、表示したい文字列をストア側で組み立ててから
list_viewに渡します。 - キャンバスまわり:マウス、タイルマップ、カメラの移動はありません。
座標は整数のピクセルだけで、浮動小数点数は
int(...)を書くように断ります。 倍率はアプリが宣言する数値で、ウィンドウに合わせて伸縮はしません。 伸縮させると、描かれた大きさがダンプの見えないところ(ウィンドウ)で決まってしまうからです。 スプライトの PNG はアプリの隣から探すので、見つからなければ何も描きません。 キャンバスが描いたものは、支援技術からも読めません。 1枚の画像として報告されるので、中身を伝える手段はa11y_label=です。 - ストアのフィールドを
Weakにすること。 ストアは所有する側なので、所有しない参照はモデル側(逆向きのポインタ)に置きます。 Vecなどネイティブ側が既に使っている型名。 断られるので、別名(V2など)を選びます。- モジュールのトップレベルに置いた文。
コンパイル済みのアプリはモジュールの宣言(import、
State、クラス、def、style()、型 alias、リテラル定数、every(...)のタイマー、__main__ガード)を読むだけで実行はしないため、関数の外に書いたcount.set(5)やfs.write_text(...)は断られます。 起動時の処理は def に書き、run(view, on_start=setup)で渡します。 - ハンドラからタイマーを開始すること。
タイマーは宣言です(モジュールレベルの
every(1.0, tick))。 ハンドラが変えるのは、ティックが読む状態のほうです。 taskのon_error=。 失敗の経路は、エラーユニオンが入るのを待っています。 標準ライブラリ呼び出しの失敗は、その呼び出しをtry/exceptで囲んで受けます。- 走り出した task を途中で止めること。
reportはタスクがどこまで進んだかを伝えますが、止まれと伝える手段はなく、task は最後まで走ります。 - コンポーネントの
localは呼び出し位置で識別されます。 呼び出しの順を入れ替えると、状態も入れ替わります。 - 同じ要素オブジェクトを二回置くこと。 一度置いた要素は使い切りで、二か所には置けません。
- 注釈のないローカルのリストと辞書(
out: list[str] = []、counts: dict[str, int] = {}。コンパイル側が要素の型や値の型を読めるのは、この注釈があるからです)。 - 方言に形のないものを返す str のメソッド:
.format()と.translate()(実行時に組み立てるテンプレートや表)、.casefold()(ßをssに広げる写像で、他の大小変換とは別の Unicode 表が要ります)。 使えるのは.partition()、.rpartition()、.upper()、.lower()、.title()、.capitalize()、.swapcase()、.strip()/.lstrip()/.rstrip()(文字集合の有無どちらも)、.split()、.splitlines()、.join()、.startswith()、.endswith()、.replace()、.find()、.rfind()、.index()、.rindex()、.count()、.zfill()、.ljust()、.rjust()、.center()、.expandtabs()、.removeprefix()、.removesuffix()、.encode()、.is…()の族、len(s)、s[i]、s[a:b]、inです。 - fill、align、符号、幅、
,、精度、d/f/e/%/sを超える書式指定(#、b/o/x、n、g)。 - Value クラスや Enum のコンポーネント引数、そして本体がコンテナ一つでない形(先頭の
if、複数の要素。columnでまとめます)。 コールバックと State の引数は使えます(受け取るコンポーネントは呼び出し箇所ごとのビューになります)。 set。 Python の set の反復順はコンパイル側で再現できないので、勝手な順に並べ替えるのではなく断ります。listで足ります。 タプルは入りました(タプル)。 ただし形を書き下した場合だけで、Rust crate が返すタプルはまだ越えられません。re.findallが群二つ以上を断るのは、そのためです。- スカラー、リスト、str キーの辞書、Value クラス、Optional 以外の
@pyの署名(モデル、入れ子のコンテナ)。 - Optional をそのまま文字列にすること(
pickedがT | Noneのときのf"{picked}")。 Python はNoneと書き、コンパイルされた実行は何も書きません。 先に絞り込んでから(if (v := picked) is not None:)、vを描いてください。 - Yokan 自身のモジュールでは:ファイルのコピーや改名、ストリーミングやバイナリのダウンロード。
- Python のモジュールでは:
mathの六つ(それぞれ理由を挙げて断ります)、randomのshuffle(リストをその場で並べ替えるものですが、ここではリストはStateの中にあります。random.sample(xs(), len(xs()))で新しい順序を取って書き戻します)とgauss以外の分布、int のリストに対するstatistics(返り値が値によって int か float かに変わるためです)。json.loadsも断ります。 返るものの形が実行するまで決まらないからで、読むときはjsondocのドットパスを使います。json.dumpsは、書き下したリテラルなら何段でも入れ子にできますが、アプリが保持している値は一段までです。datetimeからは、datetime.time、replace、strptime、リストや辞書に入れたdate、ヘルパの引数としてのdateを断ります。 タイムゾーンはzoneinfoから来るもので、datetime.timezoneの固定オフセットは入れません。 両方の実行がともに読めるのは、機械が名前で知っているゾーンだからです。zoneinfoからは、実行中に決まるキー(コンパイル側は翻訳しながらキーを読みます)、Stateやフィールドやリストに入れたタイムゾーン付きの値、そしてfoldを断ります。foldは時計が二度示す一時間のどちらを取るかを選ぶ引数で、この方言は Python の既定と同じく常に先のほうを取ります。strftimeが取るのは、CPython が自ら意味を定める指示子だけです。%c、%x、%X、%-dは、何を返すかが機械の都合で決まるので断ります。reからは、Match(re.searchを値として使うこと)と実行時に組み立てたパターンを断ります。 後者には、@pyを使うように案内します。 小さなモジュールからは、リストをその場で並べ替えるもの(heapq.heappush、bisect.insort。リストはStateの中にあります)と、textwrap.wrap/fill/shorten(CPython 自身の正規表現で語を分けます)を断ります。collectionsからはCounter以外を断ります。defaultdict(キーが無いときに何を返すかは、ここでは読む側が決めます)、deque(その場で書き換えるものですが、リストはStateの中にあります)、namedtuple(@valueクラスが型付きで同じことをします)、OrderedDict(ここでの辞書はすでに順序を覚えています)、ChainMapです。itertoolsからは、終わらないもの(count、cycle、repeat)、それ自体がイテレータを返すもの(groupby、tee)、関数を取るもの(starmap、takewhile、filterfalse)、最後のタプルだけ形の違うbatchedを断ります。hashlibからはsha256、sha1、md5以外を断り、hashlib.sha256(b).hexdigest()以外の書き方も断ります。 理由を挙げて断るモジュールは、pathlib、os、decimalです。 - 新しい要素の周辺。
表の列幅はドラッグで変えられず、行のキーボード操作と複数選択もありません。
チャートには凡例がありません。
selectはキーボードで操作できません。 ツールチップの表示は、スクリプトからホバーで確かめられません。 コンテキストメニューのパネルも、スクリプトからは開けません。 どちらも中身はダンプに出ます(ツールチップの文字列と、メニューの項目。項目はselect:で選べます)。 キーボードに関わるものは、ヘッドレスの検証にまだ無いステップを待っています。 ドラッグの方はもう動かせます(splitの仕切りはslide:で動きます)。 だから列幅の変更が待っているのは、検証の仕組みではなく要素の側です。 - 二つめのウィンドウ。 いまは一アプリに一ウィンドウです。 engine のウィンドウルートがビューひとつを前提に書かれていて、ヘッドレス実行のダンプもその一本のツリーだからです。 ショートカット、クリップボード、メニューバー、ファイルダイアログ、落とされたファイル、ツールチップ、複数行フィールドは入っています。
- 素のラッパを超えるデコレータの形:自分が引数を取るもの、ラッパが関数を二度呼ぶもの、関数の戻り値を使うもの。 関数をそのまま返すデコレータと、一度だけ呼ぶラッパはコンパイルされます。
- Rust crate の境界での、ペイロード付き enum と、双子へのメソッド。 スカラ、String、List、Optional、str キーの辞書、構造体(入れ子も幅付きフィールドも)、enum、Result(複合型の返りも)までは越えます。 残る二つのうち、ペイロード enum は rpi-gen 自体の残件です。 メソッドは、rpi で宣言済みの struct に実装を接ぐところが残っています。 構造体のフィールドに enum やリストを置く形もまだで、どれも、呼べば理由を挙げて断られます。
@pyを持つアプリは、Linux では Python を同梱できません。--bundleと--onefileが作るのは Apple のランタイムフォルダ(ロードコマンドの書き換えとアドホック署名)なので、Linux では名前を挙げて止まります。 エスケープを使わないアプリはどちらでも自己完結で、Linux では--appと--appimageが package します。 エスケープを使うアプリは、いまのところホスト側の Python が要ります。- Windows。
プラットフォーム自身の答えはツリーに入っています。
キャッシュとアプリ自身のディレクトリの置き場所、ビルドが作る
.exe、--appがそこに書くフォルダ(--bundle、--onefile、--appimageは名前を挙げて止まります)、そして Ctrl を指すcmdです。 Windows のランナーでビルドとテストとゲートを回す CI のジョブは、緑になりました。 ワークスペースのテスト、tier gate、両方の実行を突き合わせるデモ、そして作ったのとは別の機械で動かすパッケージ済みのフォルダまで通っています。 足りないのはウィンドウです。 そのランナーでは gpui がError creating DirectWriteTextSystemで止まるので、アプリは画面をテキストで出せても描けません。 実機の Windows で誰かがウィンドウを開くまで、入れられるのは macOS と Linux です。 いま入れられるのは macOS と Linux です。 - 実測値はすべて macOS/arm64 のものです。 ほかのプラットフォームは、まだ測っていません。
このリストは、設計が決まるたびに更新されます。 背後にある設計原則は DESIGN.md にまとまっています。