🐍

ROS 2 Jazzyで外部Pythonパッケージを管理する

に公開

はじめに

ROS 2 JazzyでPythonの外部パッケージを使おうと思ったのですが、pip installrosdep installが弾かれるようになっていました。本記事ではこれを解決します。

$ pip install numpy
error: externally-managed-environment

× This environment is externally managed
╰─> To install Python packages system-wide, try apt install
    python3-xyz, where xyz is the package you are trying to
    install.
    
    If you wish to install a non-Debian-packaged Python package,
    create a virtual environment using python3 -m venv path/to/venv.
    Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make
    sure you have python3-full installed.
    
    If you wish to install a non-Debian packaged Python application,
    it may be easiest to use pipx install xyz, which will manage a
    virtual environment for you. Make sure you have pipx installed.
    
    See /usr/share/doc/python3.12/README.venv for more information.

note: If you believe this is a mistake, please contact your Python installation or OS distribution provider. You can override this, at the risk of breaking your Python installation or OS, by passing --break-system-packages.
hint: See PEP 668 for the detailed specification.

これはROS 2ではなくpip側の更新による影響です。どうやらUbuntu 24.04での環境では、「生の環境に入れるなんて野蛮なことをするな!仮想環境を使え!」とのこと。
日本語の情報としては、以下の記事が参考になります。

https://qiita.com/keith_campbell/items/e7dc4afc44527b802f6e

https://blog.jp.square-enix.com/iteng-blog/posts/00043-play-with-the-pep668/

この問題についてはROS Discourseでも議論されていました。

https://discourse.ros.org/t/rosdep-for-pip-is-broken-on-jazzy/38981

これらの記事で解決するかと思いきやキレイにやろうとすると意外とハマりポイントがあったため、その知見を共有します。

お手軽な方法

/etc/pip.confというファイルを作って以下のように記述します。

[install]
break-system-packages = true

これを一行で行うコマンドは以下です。

echo -e "[install]\nbreak-system-packages = true" | sudo tee /etc/pip.conf > /dev/null

以上でpip installrosdep installも可能となりました。
いわゆる野蛮なことをしています。

もう少しちゃんとやる

上の方法はお手軽ですが、break-systemとか言われてしまうとギョッとしてしまいますよね。お行儀よくやるには少々面倒ですが以下のような方法があります。

sudo apt update
sudo apt install -y python3-venv
python3 -m venv --prompt ros2_ws ~/ros2_ws/.venv
source ~/ros2_ws/.venv/bin/activate
export PYTHONPATH=$(python -c 'import site; print(site.getsitepackages()[0])'):$PYTHONPATH

この例では~/ros2_wsというROS 2のワークスペースとするディレクトリに.venvというPythonの仮想環境の置き場を作り、それをactivateしています。注意として、activateしただけでは$PYTHONPATHが追加されません。これはpython3で実行したときは動作するがros2 runで実行したときにはimportができないという状況になります。そのため、最後のコマンドで$PYTHONPATHにこの仮想環境のパスを手動で追加しています。

activateするとターミナルの頭に(ros2_ws)という表示がつきます。その後、各ROS 2パッケージで必要なPythonライブラリをpip installなりrosdep installなりで入れるという手順になります。

さらに細かい解説

python3 -m venv --prompt ros2_ws ~/ros2_ws/.venvで、--prompt ros2_wsというオプションを付けることで、ターミナルの表示を(ros2_ws)に変更しています。このオプションを抜くと表示が(.venv)となります。複数のワークスペースを持っている場合には、今どの仮想環境を動かしているかわかりにくくなるため、ワークスペース名と同じ名前で設定しておくとよいでしょう。

Pythonを用いたROS 2パッケージはsetuptoolsというPythonパッケージで管理されていますが、仮想環境にはこれが入っていませんでした。そのため、手始めにsetuptoolsをinstallしましょう。

pip install setuptools

その後、使用するROSパッケージで必要なPythonパッケージをインストールしていきましょう。

各ROSパッケージがrequirements.txtを持っている場合

もし、各ROSパッケージがrequirements.txtを持っていれば、以下のように強引にすべて読み込む方法もあります。

find . -name "requirements.txt" -exec pip install -r {} \;

ただし、これはインストール順番やパッケージの重複などは加味されないため、以下のように1つに統合して整えてから実行すると良いと思います。

find . -name "requirements.txt" -exec cat {} \; > all-requirements.txt
sort all-requirements.txt -o sort-requirements.txt

requirements.txtを確認・編集した後

pip install -r sort-requirements.txt

とすることで、整合性が取れたインストールができるかと思います。

また、作業中は気にすることはないかと思いますが、Pythonの仮想環境を抜けたい場合には、ターミナルにdeactivateとコマンド入力してください。(ros2_ws)という表示が取れていれば完了です。再度仮想環境に入りたければsource ~/ros2_ws/.venv/bin/activateとすれば入れます。

しかしながらこの設定をもってしても、rosdep installに関しては仮想環境ではなくシステム側のpipを使ってくるため、結局のところ前章と同じ状況にするしかありません。

PIP_BREAK_SYSTEM_PACKAGES=1 rosdep install -riy --from-paths src

ここでPIP_BREAK_SYSTEM_PACKAGES=1を頭につけることで、後ろのコマンドに限りbreak-system-packagesの設定を有効にしています。ここについてよりよい方法が思いつく方が居りましたらご提案いただけますと幸いです。

別の方法

Pythonパッケージ管理のモダンなやり方として、uvを使用するという方法もあるでしょう。

https://qiita.com/GesonAnko/items/510eeade1f8ada302b9b

こちらの記事では単体のROSパッケージに対してPythonパッケージを管理しているものであり(というかこれが本来あるべき姿だと思うのですが)、複数のROSパッケージを使用するために、雑にROSワークスペース単位でPythonパッケージを管理したい場合には、前章で示したROSワークスペース下にvenvを作る方法が楽かと思います。ROSワークスペース単位でuvするというのも良さそうですね。

またROS 2パッケージ単位でパッケージ管理をできるツールとしてament_virtualenvというものもあるようです。

https://github.com/locusrobotics/ament_virtualenv

自作のROSパッケージに関してはこれで整備するということもアリかと思いますが、これで作られていないパッケージ(たとえば人が作ったもの)が1つでも混ざると、いずれにしても前章までのような手段を取る必要があります。

おわりに

以下の方法を紹介しました。

  • break-system-packagesを許可
    • お手軽だが野蛮
  • venvを構築
    • ros2 runなどからも読めるように$PYTHONPATHを追加
    • pip installで丁寧に入れる必要あり
    • rosdep installするならいずれにしてもbreak-system-packagesをオン
  • パッケージ単位で管理
    • 特有の書き方や実行の仕方が必要
    • 使いたいROSパッケージがすべて管理されているとは限らない

よりよい方法がありましたら、ぜひ教えてください。

GitHubで編集を提案

Discussion