feat(PLAN63): base イメージの日本語の描画を直し、文書を扱う軽量の道具を足す (#161, #160) - #238
Conversation
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
`/etc/fonts/local.conf` を 1 つ置き、総称ファミリ (sans-serif / sans / serif / monospace) と、イメージに無い書体名を Noto CJK の JP フェイスへ向ける。中国語・ 韓国語の規則は総称ファミリを名指ししたときだけ効かせる (`<test name="family">`)。 この test を省くと `Arial:lang=zh-cn` から Liberation Sans を奪い、様式も崩れる。 conf.d/99-*.conf へ置くと `<alias><prefer>` が効かない。local.conf は conf.d/51-local.conf 経由で読まれ、sans-serif を中国語フェイスへ向けている 64 / 65 より「先」になるためで、理由と実測は fonts-local.conf の先頭に残した。 あわせて 6 パッケージを 1 つ目の RUN の 1 回目の apt-get install へ足す。 poppler-utils / python3-pil / python3-defusedxml / python3-lxml と、欧文の metric 互換の fonts-crosextra-carlito / fonts-crosextra-caladea である。 LibreOffice と pip は入れず、fonts-wqy-zenhei も消さない。 回帰テストは 2 段。Dockerfile と fonts-local.conf の形は Docker なしで、 fc-match の解決先は建てたイメージの中で固定する。後者はイメージが無い・古い ときは skip する。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
|
構造改善(/ndf:cross-refactoring)のテスト整備ラウンドで挙がった 5 件は、いずれもこの PR が 1 行も触っていない |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | kiro | APPROVE
重点観点(言語規則 8 つの <test name="family"> 有無 / skip 条件の緩さ / Dockerfile の COPY・fc-cache の位置 / テストが設計決定を固定しているか / 本文の実測の裏取り / ドキュメント整合)を確認し、修正を要する指摘は無し。
- 言語規則 8 件はすべて
<test name="family">を伴い、test_every_language_rule_also_tests_the_generic_familyがArial:lang=zh-cn退行を辞書一致で固定している(手元で string 系 20 件 pass)。 - skip は 4 段(docker 無し / daemon 不通 / イメージ無し /
/etc/fonts/local.conf欠如=旧イメージ)で、緩すぎない。手元では既存イメージが検査対象を満たし image 系 39 件が実際に pass(=「常に skip」ではない)。 COPY fonts-local.confとRUN sudo fc-cache -fは Playwright の--with-depsRUN より後に位置し、テストで固定済み。- 変更範囲は impl の修正対象 7 ファイルと一致し、
.cross_review等の混入なし。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | agy | APPROVE
設計PR #221 の決定事項(fonts-local.confの構造・テスト要件・Dockerfileの配置順序)および受け入れ条件の実測整合性を確認し、修正が必要な問題はありません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | kiro | APPROVE
承認済みの設計(前提 2/7/8・決定 1/2/5/8)を確認したうえで、実装・テスト・ドキュメントの整合を検証した。修正を要する点は見つからなかった。
fonts-local.conf: 言語の規則 8 つはいずれも<test name="family">を伴い、Arial:lang=zh-cnの退行はない。Dockerfile:COPY fonts-local.confとRUN sudo fc-cache -fはいずれも Playwright のRUN(--with-deps)より後に置かれている。test_base_image_font_matching.py: skip は docker 不在・イメージ不在・イメージが古い(/etc/fonts/local.conf無し=exit 90)の 3 条件のみで、常時 skip にはならない。test_base_dockerfile_fonts.py: XML 構造をgot == LANG_RULES/got == GENERIC_TO_JPと binding/mode/compare で固定しており、文字列存在だけの検査ではなく退行を捕まえられる。- 差分は範囲内(7 ファイル)で、
.cross_review等の混入なし。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | codex | REQUEST_CHANGES
検証処理のタイムアウトを skip に変換する 1 件の修正をお願いします。
docker run を包む except (SubprocessError, OSError) が TimeoutExpired まで pytest.skip にしていたため、Docker もイメージもある状態で probe が 300 秒で タイムアウトすると 39 件すべてが skip になり pytest が成功終了していた。 skip してよいのは Docker が使えない・イメージが無い (_docker_unavailable) と イメージが古い (STALE_IMAGE_EXIT) の 3 つだけなので、docker run の try/except を 外して例外のまま失敗させる。意図はコメントで残す。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 3 | codex | APPROVE
修正を要する新規指摘はありません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 3 | agy | APPROVE
修正を要する指摘はありません。設計および受け入れ条件を満たしており、テスト・ドキュメントともに整合しています。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 4 | kiro | COMMENT
設計 PR #221 で承認済みの事項(fonts-wqy-zenhei 残置・ENV LANG 非設定・言語規則の <test name="family"> 併用・総称ファミリの JP 向け・LibreOffice/pip 非導入)は動かさない前提で実装のみを確認した。手元で devbase-base:latest(ID c1b1285cbc66、PR 本文の「変更後」と一致)に対し test_base_image_font_matching.py を実行し 39 件すべて通ること、test_base_dockerfile_fonts.py 20 件が通ること、fonts-local.conf の言語規則 8 つがすべて <test name="family"> を伴うこと、COPY/fc-cache -f が Playwright の RUN より後にあることを確認した。
指摘は 1 件(minor / テスト)。test_base_dockerfile_fonts.py::test_no_extra_run_is_added_for_the_six_packages の RUN ブロック検査が退行を捕まえられない点をインラインに残した。ブロッキングではない。
`RUN ` で始まる行だけを集めていたため、各要素は `RUN set -eux; \` のような 1 行目だけになり、パッケージ名が入る継続行を見ていなかった。 `poppler-utils` / `fonts-crosextra` が無いという 2 つの assert は常に真で、 「6 パッケージのために RUN を足していない」という意図を固定できていなかった。 `_run_blocks()` を足して Dockerfile を RUN ブロック (行継続を含む 1 命令分) へ 分け、`_first_run_block()` はその 1 つ目を返す形に寄せた。検査は 1 つ目以外の 各ブロックの全文に対して行う。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011Vu8hNTZKeDeXVLg8hYuK8
🔧 /ndf:fix サマリ(最終スイープ)対応件数: critical=0 / major=0 / minor=1 / nit=0 (合計 1 件) 詳細
検証
|
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 5 | codex | APPROVE
新規の修正指摘はありません。対象テストは 59 passed / 0 skipped。CI は実行なし。変更前イメージが手元に無く、サイズ増分は再測定していません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 5 | kiro | APPROVE
実装・テスト・ドキュメント・CHANGELOG を確認した。承認済みの設計前提(fonts-wqy-zenhei 非削除 / ENV LANG 非設定 / 言語規則は <test name="family"> を伴う / 総称は Noto CJK JP / LibreOffice・pip 非導入)はいずれも守られている。重点確認項目も問題なし: fonts-local.conf の言語 <match> 8 つは全て <test name="family"> を伴い、COPY fonts-local.conf と RUN sudo fc-cache -f は Playwright の RUN より後にある。test_base_dockerfile_fonts.py は XML パースによる構造検査で設計決定を固定しており、文字列存在チェックの退行は捕まる。test_base_image_font_matching.py の skip は Docker 無し / イメージ無し / local.conf 無し(古い)の 3 条件に限定され「常に skip」にはならず、probe の起動失敗は失敗として通知される設計になっている。手元で test_base_dockerfile_fonts.py(20 passed)を確認した。修正提案なし。
Summary
base イメージの 2 つの課題を 1 本で閉じる。どちらも
containers/base/Dockerfileの同じapt / COPY の区画を触るため、分けると同じ箇所で競合する(#160 の本文も「1 本の PR に
まとめる」と書いている)。
WenQuanYi Zen Hei)で描かれる。containers/base/fonts-local.confを/etc/fonts/local.confへ置き、総称ファミリをNoto CJK の JP フェイスへ向ける。パッケージの追加は無い(サイズ 0)
1 つ目の
RUNの 1 回目のapt-get installの一覧へ足す。LibreOffice とpipは入れない要求と受け入れ条件:
issues/PLAN63_base-image-rendering.md設計:
issues/PLAN63_base-image-rendering-design.md実装の分解:
issues/PLAN63_base-image-rendering-impl.md設計 Pull Request: #221 / release Pull Request: #212
中国語・韓国語の規則は、総称ファミリを名指ししたときだけ効かせる(設計の決定 2)
#161 の issue 本文にある形(
langだけを見て前置する)は採っていない。binding="strong"の前置は名指しの書体よりも強いため、
langだけを条件にするとArial:lang=zh-cnからLiberation Sans を奪い、
serif:lang=zh-cnが sans のフェイスに、monospace:lang=zh-cnが等幅でなくなる。そこで
<test name="family">を伴う 総称ファミリ 4 つ × 言語 2 つ = 8 つの<match>と、未導入の書体の受け皿 1 つで構成した。置き場所は
/etc/fonts/local.confから動かせない(設計の決定 1)conf.d/99-*.confへ置くと<alias><prefer>が効かない。local.confはconf.d/51-local.conf経由で読まれ、sans-serifを中国語フェイスへ向けている64-wqy-zenhei.conf/65-nonlatin.confより「先」になるためである。理由と実測はcontainers/base/fonts-local.confの先頭のコメントに残してある(受け入れ条件 8)。実測
CI はこの Pull Request では 1 件も動かない(#216。
release/v3.7.0を base にしているため)。証跡は手元で採った。
gh pr checksのno checks reportedは「通った」ことを意味しない。比較は 2 つのイメージを同じ日に
--no-cacheで建てて行った。手元にあった 4 日前のdevbase-base:latest(6e67731618ab/ 7.09GB)は上流のパッケージが動いており、そのままではこの変更の増分と混ざるため、
release/v3.7.0のcontainers/baseをdevbase-base-plan63-before:latestとして建て直して基準にした。release/v3.7.0のcontainers/basecd4ac2330051containers/basec1b1285cbc66解決先の表(受け入れ条件 1〜6・11)
docker run --rm --entrypoint /bin/bash <image> -c 'fc-match <指定>'。太字が変わった行。sans-serifsans-serif:lang=jasansserifmonospaceNoto Sans JPMeiryoYu GothicMS PGothicZen Kaku Gothic NewArialTimes New RomanCourier NewCalibriCambriasans-serif:lang=zh-cnsans:lang=zh-cnserif:lang=zh-cnmonospace:lang=zh-cnsans-serif:lang=kosans:lang=koserif:lang=komonospace:lang=koArial:lang=zh-cnTimes New Roman:lang=zh-cnArial:lang=koWenQuanYi Zen HeiIPAPGothicfc-match -s sans-serif:lang=jaの並び(受け入れ条件 2):文書を扱う道具(受け入れ条件 9・10・12)
pdftoppm/pdfinfo/pdffonts/pdftocairo/usr/bin/にあるpython3 -c "import PIL, defusedxml, lxml"ModuleNotFoundError: No module named 'PIL'exit=0(PIL 12.1.1 / defusedxml 0.7.1 / lxml 6.0.2)soffice/libreoffice/pip/pip3uv/home/ubuntu/.local/bin/uvfonts-wqy-zenheiイメージのサイズ(受け入れ条件 13)
docker image inspect --format '{{.Size}}'(層の合計)docker imagesの表示合否のライン(40 MB 以下)に対して +10.0 MB で収まる。 層の合計の方を合否に使った。
docker imagesの値は GB 単位に丸まるうえ、containerd の保管では圧縮前後の両方を数えるため、この規模の差を判定できない。
依存を含めて新規に入ったパッケージは 26(315 → 341。設計の見積もりは 24)。
増えた 2 つは
fonts-crosextra-*が引くfonts-dejavu-core/fonts-dejavu-monoである。消えたパッケージは 0。
派生イメージ(受け入れ条件 16)
containers/general(FROM devbase-base:latestの 7 行)を使い捨てのタグで建てて確かめた(
devbase-general:latestは置き換えていない。確認後に削除した)。Test plan
uv run --locked pytest tests/ -q→exit=0test_base_image_font_matching.py。イメージに
/etc/fonts/local.confが無いため。古いイメージを持つ人が赤くならない)devbase build base --no-cache(作業ツリーのcontainers/baseを文脈にして実行)→exit=0uv run --locked pytest tests/containers/test_base_image_font_matching.py -q→
exit=0(39 passed)uv run --locked pytest tests/containers/test_base_dockerfile_fonts.py -q→exit=0(20 passed)Meiryo/Yu Gothic/MS PGothicの行を 3 つへ開いた)回帰テストの構成(設計の決定 6)
tests/containers/test_base_dockerfile_fonts.pyapt-get installにあること。COPYの宛先が/etc/fonts/local.confでconf.d/ではないこと。fc-cache -fが 1 度だけCOPYより後かつ Playwright のRUNより後にあること。fonts-local.confが 4 つの<alias>と 9 つの<match>を持ち、言語の規則が必ず<test name="family">を伴うこと。先頭のコメントが置き場所の理由に触れていること。LibreOffice /pipを入れずfonts-wqy-zenheiを消さないことtests/containers/test_base_image_font_matching.pydocker runはセッションで 1 回。Docker が無い / イメージが無い / イメージに/etc/fonts/local.confが無い(古い) ときは skip し、文言にdevbase build base --no-cacheを出す利用者への影響
建て直すまで変わらない。
devbase build base --no-cacheが要り、devbase upだけでは反映されない。派生イメージを使っているプロジェクトはその派生イメージも、稼働中のコンテナは
devbase down→devbase upで作り直す(devbase rebuildは使えない)。言語を明示しない中国語の文書は日本語の字形で描かれるようになる(設計の決定 8。承認済み)。
やらないこと(範囲外)
containers/docsの新設 → feat: Office 文書を画像へ描画する経路を base の外に置く(containers/docs の新設・案 A/B/C の比較) #219fonts-wqy-zenheiの削除 /ENV LANGの設定 /pipの追加(要求の前提 2・7・8)containers/lfm(FROM nvidia/cuda:...で base 由来ではない。同じ問題を抱えるかは未調査)containers/baseのシェル(entrypoint.sh/tmux-clean/tmux-first)の異常系と分岐のテストの不足 → test: containers/base のシェル (entrypoint.sh / tmux-clean / tmux-first) の異常系と分岐が固定されていない #239(構造改善で挙がったが、この Pull Request の差分の外)
検査(構造改善 → 実装レビュー → 完了判定)
実装の後に検査の持ち場を通した。この節が最新の証跡である(上の「実測」と「Test plan」は
5821883の時点のもので、下の 2 つのコミットはテストだけを直している)。構造改善(
/ndf:cross-refactoring)--scope containers/base tests/containers --severity-threshold major --max-outer-rounds 1で起動した。テスト整備ラウンドで 3 者が 6 件を挙げ、統合して 5 件になったが、5 件とも
この Pull Request が 1 行も触っていない
containers/base/entrypoint.sh/tmux-clean/tmux-firstを指していた。差分の外なので全件を却下し、構造改善の変更はこのPull Request に 1 つも取り込んでいない。挙がった中身は #239 として残した。
実装レビュー(
/ndf:cross-review)ホストを除く 3 者(codex / agy / kiro)の輪番で 5 ラウンド。最終ステータス
approved、未解決のスレッド 0 件(GitHub 側で確認済み)。
267474180cc84dラウンド 1 で止めなかったのは、3 者輪番の 1 ラウンド目では外れた担当(codex)が差分を
見ていないためである。 実際に codex はラウンド 2 で major を 1 件挙げた。同じ理由で、
80cc84dを入れた後にもう 1 ラウンド(5)回してある。直した 2 件
2674741tests/containers/test_base_image_font_matching.pydocker runを包むexcept (SubprocessError, OSError)がTimeoutExpiredまでpytest.skipにしていた。probe が 300 秒で時間切れになると 39 件すべてが skip になり pytest が成功で終わる。try/exceptを外し、probe の実行中の異常は失敗として上げるようにした。skip は「docker が無い」「daemon / イメージが無い」「古いイメージ(STALE_IMAGE_EXIT)」の 3 つだけに限定される80cc84dtests/containers/test_base_dockerfile_fonts.pytest_no_extra_run_is_added_for_the_six_packagesのfor run in runs[1:]がRUNで始まる行だけを見ており、パッケージ名は必ず継続行にあるため 2 つの assert が常に真だった。_run_blocks()を足して RUN ブロック単位(行継続を含む 1 命令分)に分け、各ブロックの全文を検査するようにした。退行版の Dockerfile(2 つ目の RUN の継続行へpoppler-utilsを足したもの)を一時ディレクトリに作り、旧実装は見逃し、新実装はAssertionErrorで落ちることを確かめてある完了判定(
/ndf:quality-gates、モードstandard)CI はこの Pull Request では 1 件も動かない(#216)。 手元で CI と同じ検査を行った。
uv run --locked pytest tests/containers/ -qtests/containers/uv run --locked pytest tests/ -qruff check --select=E9,F63,F7,F82 liblibpython3 -m compileall -q lib binlibbinfc-match21 行と道具 9 件を実測containers/goskip が 0 件であることが重要である。 Docker が要る 39 件は、この Pull Request の
Dockerfile で建て直した
devbase-base:latest(c1b1285/ 1,612,709,587 B)に対して実際に走っている。イメージの
/etc/fonts/local.confの md5(46ca7156…)はリポジトリのcontainers/base/fonts-local.confと一致し、5821883より後の 2 コミットはcontainers/baseを触っていないので、この実測は現在の先端に対するものである。派生イメージの確認は、
FROM1 行だけのcontainers/generalでは「同じ層を指し直すだけ」で中身が無いため、自前の
RUNを持つcontainers/goで行った。使い捨てのタグで建てて確認後に削除してあり、
devbase-general:latestなどの正規のタグと稼働中のコンテナには触れていない。
shellcheckは手元に無いため実行していない(この差分にシェルの変更は無い)。受け入れ条件 16 個の判定
test_base_image_font_matching.py(建て直したイメージに対して 39 passed)/etc/fonts/local.confへ置きconf.d/には置かない。fc-cache -fが 1 度test_base_dockerfile_fonts.pyimport PIL, defusedxml, lxml/ LibreOffice と pip が無いtest_base_image_font_matching.pyCalibri→Carlito、Cambria→Caladeadocker image inspectの層の合計で +10.0 MBpytest tests/ -qが exit=0devbase build base --no-cacheが arm64 で成功dpkg -lが表と一致containers/goを使い捨てのタグで建てて実測受け入れ条件 13 の注記: 合否のライン(40 MB 以下)は満たすが、条件の文面にある
「依存を含めて新規に入るのは 24 パッケージ」は実測 26 で一致しない。増えた 2 つは
fonts-crosextra-*が引くfonts-dejavu-core/fonts-dejavu-monoである。設計の見積もりのずれであって、ラインを外したわけではない。イメージのサイズとパッケージの数は、どの
テストも固定していない(受け入れ条件 15・16 も同じで、CI がイメージを建てないため)。
Closes #161
Closes #160