uv の更新後に WeasyPrint が Pango を読み込めなくなった — macOS の uv run は DYLD_* 変数を削除する
uv の更新後、macOS の uv run が DYLD_FALLBACK_LIBRARY_PATH を Python に渡さなくなりました。ライブラリのパスを別の環境変数で渡し、スクリプトに自分自身を再実行させると、WeasyPrint がふたたび Pango を読み込めます。

目次
はじめに
macOS の WeasyPrint が Homebrew の Pango を見つけられるのは、DYLD_FALLBACK_LIBRARY_PATH が /opt/homebrew/lib を指しているときだけです。Markdown から PDF を生成する記事では、Makefile の uv run の前でこの変数を設定していました。ところが uv を 0.13.5 に更新すると、make docs-pdf がライブラリを見つけられないエラーでふたたび失敗するようになりました。
OSError: cannot load library 'libgobject-2.0-0': dlopen(libgobject-2.0-0, 0x0002): tried: 'libgobject-2.0-0' (no such file)Pango はインストールされたままで、Makefile も変えていません。調べてみると、uv run が DYLD_* 変数をすべて削除した状態で Python を起動していました。
修正では、ライブラリのディレクトリを別の環境変数で渡します。dyld(macOS の動的リンカ)はこの変数には手を付けません。スクリプトはライブラリのディレクトリを DYLD_FALLBACK_LIBRARY_PATH にコピーし直し、WeasyPrint を import する前に自分自身を再実行します。Makefile の uv run は 1 行のままで、Homebrew があってもなくても同じようにビルドできます。
uv run で起動した Python に DYLD_FALLBACK_LIBRARY_PATH が届かない理由
変数は設定されていましたが、Python が起動する前に uv が削除していました。エラーの全文では、dyld が探索したディレクトリをすべて列挙していますが、その中に /opt/homebrew/lib はありません。
OSError: cannot load library 'libgobject-2.0-0': dlopen(libgobject-2.0-0, 0x0002): tried: 'libgobject-2.0-0' (no such file), '/System/Volumes/Preboot/Cryptexes/OSlibgobject-2.0-0' (no such file), '/Users/<user>/.local/share/uv/python/cpython-3.14.9-macos-aarch64-none/lib/libgobject-2.0-0' (no such file), '/usr/lib/libgobject-2.0-0' (no such file, not in dyld cache), 'libgobject-2.0-0' (no such file).変数を設定したうえで、私が Python を直接起動してコマンドを実行すると、Python から変数が見えます。
入力DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib: ~/.local/share/uv/python/cpython-3.14-macos-aarch64-none/bin/python3 -c "import os; print(repr(os.environ.get('DYLD_FALLBACK_LIBRARY_PATH')))"出力'/opt/homebrew/lib:'同じく変数を設定したまま、uv run 経由で同じコマンドを実行すると、変数は消えています。
入力DYLD_FALLBACK_LIBRARY_PATH=/opt/homebrew/lib: uv run --no-project python -c "import os; print(repr(os.environ.get('DYLD_FALLBACK_LIBRARY_PATH')))"出力None違いは、uv バイナリが Hardened Runtime で署名されていることです。Hardened Runtime は、バイナリが com.apple.security.cs.allow-dyld-environment-variables エンタイトルメントを持っていない限り、プロセスから DYLD_* 変数を削除します。uv 0.13.5 を codesign で見ると runtime フラグが立っていて、エンタイトルメントは 1 つも付いていません。
入力codesign -dv ~/.local/bin/uv 2>&1 | grep flags出力CodeDirectory v=20500 size=54552 flags=0x10000(runtime) hashes=1699+2 location=embedded/bin/sh は uv を起動するときに変数を設定しますが、Hardened Runtime の uv バイナリが動き出した時点で変数は消えています。Python は uv の環境を引き継ぐので、この変数を受け取れません。Hardened Runtime が uv の起動時に DYLD_FALLBACK_LIBRARY_PATH を削除するので、uv はそもそもこの変数を受け取っておらず、Python に渡しようがありません。uv のどのリリースからこの署名方式になったのかは分かりません。分かっているのは、更新前は Makefile が動いていたことと、0.13.5 では DYLD_* 変数が削除されることだけです。
uv の issue で、同じ問題 (astral-sh/uv#6971)がすでに報告されています。報告で使われているのは uvx(uv tool run の短縮形)ですが、uv のメンテナーはこの issue を対応予定なし(not planned)としてクローズしました。そのため、Makefile とスクリプトの側でこの制限を回避するしかありません。
Python の中で変数を設定して再実行する
Python 自体は Hardened Runtime で署名されていないので、名前が DYLD_ で始まらない環境変数でディレクトリを Python まで届ければ十分です。Makefile はディレクトリを DOCS_PDF_LIB_DIR として渡し、スクリプトは WeasyPrint を import する前にその値を DYLD_FALLBACK_LIBRARY_PATH に戻し、dyld が Pango を見つけられるようにします。
BREW_PREFIX := $(shell brew --prefix 2>/dev/null)
docs-pdf: DOCS_PDF_LIB_DIR="$(if $(BREW_PREFIX),$(BREW_PREFIX)/lib,)" \ uv run --with markdown --with weasyprint python scripts/docs/build_docs_pdf.pyos.environ を書き換えるだけでは足りません。dyld が DYLD_FALLBACK_LIBRARY_PATH を読むのはプロセスの起動時の 1 回だけなので、実行中のインタープリタに書き込んだ値は、そのインタープリタがあとから起動するプロセスにしか届きません。そこでスクリプトは、同じインタープリタを起動し直して自分自身と置き換えます。起動し直したインタープリタには、最初から変数が設定されています。
import osimport sys
def _reexec_with_lib_dir() -> None: lib_dir = os.environ.get("DOCS_PDF_LIB_DIR") if sys.platform != "darwin" or not lib_dir or os.environ.get("_DOCS_PDF_REEXECED"): return current = os.environ.get("DYLD_FALLBACK_LIBRARY_PATH", "") os.environ["DYLD_FALLBACK_LIBRARY_PATH"] = f"{lib_dir}:{current}" if current else lib_dir os.environ["_DOCS_PDF_REEXECED"] = "1" os.execv(sys.executable, [sys.executable, *sys.argv])
_reexec_with_lib_dir()
import markdown # noqa: E402from weasyprint import CSS, HTML # noqa: E402os.execv はプロセス ID を変えません。uv から見ると、起動したプロセスはまだ終了していないので、一時環境は削除されません。そのため、起動し直したインタープリタも uv run --with の一時環境から WeasyPrint を読み込めます。
/bin/sh が設定した DOCS_PDF_LIB_DIR は uv を通過します。Python は同じプロセスの中で os.execv によって自分自身を置き換え、DYLD_FALLBACK_LIBRARY_PATH が設定された状態で起動し直します。uv はこのプロセスを待ち続けているので一時環境は残り、新しい Python はその一時環境から WeasyPrint を読み込みます。Python が同じプロセスの中で os.execv によって自分自身を置き換えて再実行すると、スクリプトは先頭から動き、_reexec_with_lib_dir() をもう一度呼びます。目印がなければ、スクリプトは変数を設定して再実行することを延々と繰り返します。そこで関数は os.execv を呼ぶ前に _DOCS_PDF_REEXECED=1 を設定し、起動し直したスクリプトはこの変数を見てすぐに戻ります。
この処理が必要なのは macOS だけです。Linux では sys.platform が darwin ではないので、関数はすぐに戻ります。Homebrew のない Mac では Makefile の brew --prefix が何も返さず、DOCS_PDF_LIB_DIR が空になるので、関数はやはり何も変更せずに戻ります。そのため、同じ Makefile を OS の異なるマシンで使えます。
Claude Code が、シェルに DYLD_* 変数を設定しない状態で、uv run 経由の再実行を試しました。起動し直したインタープリタは、/opt/homebrew/lib から libgobject-2.0.0.dylib を読み込みました。そのあと、私のマシンでも make docs-pdf で PDF をビルドできるようになりました。
まとめ
uv の更新後、macOS で uv run の前に設定した DYLD_FALLBACK_LIBRARY_PATH が Python に届かなくなり、WeasyPrint はふたたび Pango を読み込めなくなりました。uv 0.13.5 は Hardened Runtime で署名されていて、Hardened Runtime は DYLD_* 変数を削除します。
現在はライブラリのディレクトリを DOCS_PDF_LIB_DIR で渡し、スクリプトが DYLD_FALLBACK_LIBRARY_PATH を設定してから、WeasyPrint を import する前に自分自身を再実行します。Makefile の uv run は 1 行のままで、同じビルドが Linux でも Homebrew のない Mac でも、手を加えずに動きます。
依存関係がプロジェクト環境にあるなら、変数を設定して .venv/bin/python を直接起動するほうが簡単です。
参考リンク
- macOS で
uvxから起動したスクリプトにDYLD_FALLBACK_LIBRARY_PATHが渡らない問題 (astral-sh/uv#6971) - ライブラリが見つからないときの WeasyPrint のトラブルシューティング(macOS の
DYLD_FALLBACK_LIBRARY_PATHを含む) - Python プロセスを変数を設定した状態で起動するよう案内する WeasyPrint メンテナーのコメント (Kozea/WeasyPrint#2427)
- Hardened Runtime が原因で Python.org の macOS ビルドが
DYLD_LIBRARY_PATHを無視する問題 - Hardened Runtime のヘルパーツールには Allow DYLD Environment Variables エンタイトルメントが必要だとする Apple DTS の回答
- Apple の Allow DYLD Environment Variables エンタイトルメントの説明(
DYLD_*変数でコードを注入できることを含む)




