Goのライブラリに対する再現コードを置く場所

Goのライブラリのメンテナをしていると、再現コード付きのIssue報告をもらうことがあります。

さて、そのサンプルコードをどうやって動かせばいいでしょうか?ディレクトリを作って go mod init して、、、というのは面倒ですよね。 ライブラリのコードにデバッグ用のトレースを入れたり、バグ修正コードで再現しなくなることを確認する方法も考えないといけません。

その場合、そのライブラリの中に _issue みたいなディレクトリを作って、その中に issue1234.go のようなファイルを作るのが楽です。 go build issue1234.go のようにすると、ちゃんと親ディレクトリのmodを使ってくれるので、ローカルの修正が可能です。

再現コードに外部依存が入っている場合は、仕方ないので go mod init が必要です。その場合も再現コードを置くディレクトリはライブラリのディレクトリのサブディレクトリにするか、隣のディレクトリにすると、 replace を使ってライブラリのリポジトリのコードを使うのが楽になります。

Ryeを使っていると uv venv が動かない

Ryeを使っている状態で uv venv をすると、次のようなエラーになります。

$ uv venv
  × Querying Python at `/Users/inada-n/.rye/shims/rye` failed with status exit status: 2:
  │ --- stdout:

  │ --- stderr:
  │ error: unexpected argument found
  │ ---

pythonpython3 コマンドが rye コマンドのシンボリックリンクになっていて、ryeはpythonとして起動された場合は .python-version などのルールに従って決めたバージョンのPythonを起動するようになっています。ちなみに +3.8 みたいにしてバージョンを指定することもできます。

$ python3 +3.8 -msite
sys.path = [
    '/Users/inada-n/tmp',
    '/Users/inada-n/.rye/py/cpython@3.8.18/install/lib/python38.zip',
    '/Users/inada-n/.rye/py/cpython@3.8.18/install/lib/python3.8',
    '/Users/inada-n/.rye/py/cpython@3.8.18/install/lib/python3.8/lib-dynload',
    '/Users/inada-n/.rye/py/cpython@3.8.18/install/lib/python3.8/site-packages',
]
USER_BASE: '/Users/inada-n/.local' (exists)
USER_SITE: '/Users/inada-n/.local/lib/python3.8/site-packages' (doesn't exist)
ENABLE_USER_SITE: True

しかし、 uv venv はデフォルトのPythonを利用するときに python3 コマンドを PATH から見つけた後、シンボリックリンクを解決してから実行してしまいます。python3のシンボリックリンクを解決するとryeになるので、ryeをryeとして起動してしまい、pythonを実行することができません。

UV venv doesn't work on MacOS when Rye is installed · Issue #1791 · astral-sh/uv · GitHub

将来的には解決されるでしょうが、とりあえずの回避策を紹介しておきます。 rye は自身のテストを実行するときに複数バージョンのPythonをあるディレクトリ以下にダウンロードして使っていて、そのディレクトリを UV_TEST_PYTHON_PATH という環境変数で指定しています。

これを利用して、 $HOME/pythons というディレクトリに rye が持っているPythonのpython3.xコマンドへのシンボリックリンクを集めて、UV_TEST_PYTHON_PATHにそのディレクトリを設定してやります。そしてデフォルトで使いたいバージョンに python3 というシンボリックリンクも作っておきます。

$ mkdir pythons
$ cd pythons
$ for i in ~/.rye/py/*/install/bin/python3.*; do ln -s $i; done
$ ln -s python3.12 python3
$ ln -s python3.12 python
$ cd

$ export UV_TEST_PYTHON_PATH=$HOME/pythons

$ uv venv hoge
Using Python 3.12.1 interpreter at /Users/inada-n/.rye/py/cpython@3.12.1/install/bin/python3.12
Creating virtualenv at: hoge

$ uv venv -p3.8 hoge
Using Python 3.8.18 interpreter at /Users/inada-n/pythons/python3.8
Creating virtualenv at: hoge

このように uv venv が動くようになるだけでなく、 uv venv -p3.x で rye が管理しているインタープリタを利用できるようになりました。 上のコマンドはすごく雑なやり方で、 3.12.03.12.1 のようにマイクロバージョン違いのバージョンが複数ある場合はどれが利用されるかわからないので、その場合は手作業で修正してください。

Ryeの中のuvを使う

uvにはまだ self update の機能がありません。 Homebrewにはすでにuvが追加されて頻繁にアップデートされているので、Homebrewユーザーは brew install uv がおすすめです。

Homebrewを使ってない環境でuvをインストールし手軽にアップデートするには 、rye install pipxbrew install pipx した上で pipx install uv するのがおすすめです。 Rye自体にも rye install コマンドはありますが、まだpipxほどは使い勝手がよくありません。 (pipx upgrade-all 相当ができない)

裏技として、Ryeでuvを有効にする(rye config --set-bool behavior.use-uv=true)とRyeの内部にuvもインストールされるので、それを直接使うこともできます。Ryeはuvほどではないものの頻繁にアップデートされ、self update機能(rye self update)もあるので、ryeのおまけでuvがアップデートされるのは便利です。rye fmt/rye lintが使っているruffにも同じテクニックが使えます。

$HOME/.rye/self というディレクトリが Ryeが内部に持っているPythonのvenvになっているので、その中のコマンドへPATHが通っているディレクトリからsymlinkを作ってやります。次の例では $HOME/.local/bin を使っています。

cd ~/.local/bin
ln ~/.rye/self/bin/uv
ln ~/.rye/self/bin/ruff

ryeの中にuvがインストールされるのはuvを有効にしたあと、一度でもrye addなどのコマンドを実行する必要があるのに注意が必要です。 試しにすでに0.25がインストールされてる状態で rye config --set-bool behavior.use-uv=true; rye self update してみたのですが、 uv はインストールされていませんでした。実際にryeのバージョンが上がるタイミングでは uv がインストールされるかもしれませんが、未確認です。

uvとRye

先週にRuffを開発しているAstralがuvを発表しました。

astral.sh

uvは現在のところはvenv, pip, pip-toolsの基本的な機能を提供していますが、将来は"Cargo for Python"になることを目標にしています。

一見すると乱立しているPythonのパッケージ管理ツールにもう一つ加わったように見えますが、Ryeの開発者のArminとuvの開発チームは連携していて、同時に次のような発表をしています。

uv: Python packaging in Rust

Rye Grows With UV | Armin Ronacher's Thoughts and Writings

  • Ryeはもともとより良いパッケージツールがどうあるべきかの実証のために作られていて、中身は既存のツールのツギハギだった
  • Ryeがpip-toolsやvirtualenvの代わりにuvを使うオプション rye config --set-bool behavior.use-uv=true を提供する
  • いつかuvがRyeの機能に追いつくまでRyeの開発は当面続く
  • RyeはAstralが引き継ぎ、Arminも当面Ryeの開発に参加する

ということで、Ryeを使っている人はuvを直接使う必要はなく、試してみたい場合は上記の設定をするのがよさそうです。

私の場合はIssue報告を再現するためにパッケージをインストールしてスクリプトを実行するのにはまだvenvとpipの方が簡単なので、直接uvを使う機会も多いです。その場合の使用感を書いておきます。

venvとuv venvの比較

  • venv の時は alias mkvenv='python3 -m venv --prompt . .venv' というエイリアスを使っていた
  • uv venv はターゲットパスを指定しないと ".venv" を使うので一手間少ない
  • --prompt オプションがなかったのでuvにプルリクエストを送って取り込んでもらった。今朝リリースされた0.1.6に入っている。
  • --prompt . がデフォルトなので、上記エイリアス--prompt . も不要で、 uv venv だけでOK。もうエイリアスいらない。
  • ただし、 rye pin 3.12 してから mkvenv したら rye 管理のPython 3.12を使ってくれるのに、uv venvは which python3.12 で見つからないとエラーになるのでPATHを通しておくなどの手間が必要になる

pipとuv pipの比較

  • pipはvirtualenvを有効にしないとホストのPythonにライブラリをインストールしようとしてしまうが、uv pipは .venv ディレクトリがあるとそっちにインストールしてくれる。
    • activateの手間がない
    • venvの中にpip/uvをインストールしておく必要もないのでvenvの中がより軽量になる
  • とにかくすごく速くて気持ちいい

resticとRustとCACHEDIR.TAG

古いHDDをUSB外付けケースに入れてバックアップ用に利用しているのですが、rsyncを使ったバックアップやmacのTimeMachineが遅くなるので、最近はrestic を使っています。

resticを使っていて感じたメリットをいくつか紹介しておきます。

  • 効率性
    • ファイルをそのままコピーするのではなくgitのようにパックして圧縮して保存するので小さいファイルを大量に作らないしディスクスペースも節約できる。
    • ファイルの更新日時のようなメタデータを利用しているので、差分バックアップするときに遅いHDDからの読み込みを最小限で済ませられるし、mac側の変化があったファイルのスキャンも高速
  • クロスプラットフォーム
    • リポジトリを暗号化するので、exFATの非暗号化ディスクを使えば、macで取得したバックアップの中にあるファイルをWindowsLinuxから救出できる

S3をストレージに使えるくらいなので、HDDの速度が初回バックアップの書き込み時間以外には全く問題にならないくらい高速です。

さて、最近Uvが話題になって触っていたのでRustのプロジェクトを何度かビルドしていたのですが、resticを動かすと差分バックアップでtargetディレクトリがバックアップされているのに気づきました。そして何かいい除外パターンが無いかなとtargetディレクトリを眺めたときに見つけたのが CACHEDIR.TAG ファイルです。調べてみたところ、まさにバックアップから除外するディレクトリを指定するためのファイルのようです。 ~/.cargo/git~/.cargo/registry にも CACHEDIR.TAG が入っているので、Rustの世界では広く普及しているみたいです。

Cache Directory Tagging Specification – Bryan Ford's Home Page

そしてResticもbackupコマンドの --exclude-caches オプションを指定したら自動でこのファイルを見つけてバックアップから除外してくれていました。

Backing up — restic 0.16.4 documentation

そして uv も2日前にvenv作成時にvenvディレクトリ内に CACHEDIR.TAG を作るようになっていました。

Tag created venvs as a cache dir to avoid most of in-project venv downsides · Issue #1648 · astral-sh/uv · GitHub

私はすでに .venv~/.cargo もバックアップ除外パターンに入れてしまっていたのですが、除外パターンというのは設定をミスると必要なファイルのバックアップが取れなくなったりするものなので、CACHEDIR.TAGはどんどん活用していこうと思います。

このブログに乗せているコードは引用を除き CC0 1.0 で提供します。