Lazy Diary @ Hatena Blog

PowerShell / Java / miscellaneous things about software development, Tips & Gochas. CC BY-SA 4.0/Apache License 2.0

Pythonプログラム中からimport文レベルの依存関係ベースで不要なファイルを除外する

動機

  • ソースコード調査ツールの中には、調査対象となるソースコードのサイズで価格が決まるものがあります*1*2。そういったツールの利用価格を低く抑えるには、調査対象のソースツリーから不要なファイルはできるだけ除外したいところです。
  • 米国大統領令でSBOMの開示がガイドラインに入った*3ので、開発したソフトウェアに不要な依存関係が入っているのは避けたいです。

概要

たとえばPythonであれば、requirements.txtに記載されているパッケージだけをvirtualenvにインストールすれば、不要なパッケージが調査対象に含まれることは避けられます。ですが、これで分かるのはpipレベルの依存関係であって、たとえば実行環境には不要なテストケースがパッケージに含まれているとか、機能としては持っているけれど実際には使われていないファイルがあるとかの場合は除外できません。

プログラム実行時の依存関係ベースで不要なファイルを除外するには、C0 100%になるようなテストケースを実行したうえで、カバレッジが0%でないソースコードのみを残すというのがよさそうです。ただ、C0 100%になるテストケースが用意できない場合もあるでしょう。

本稿ではimport文の依存関係ベースで不要なファイルを除外する方法を試してみました。。import文レベルでの依存関係を調査するツールとしてはpydeps*4を使用しました。

手順

  1. pydepsをpip install pydepsでインストールする。
  2. virtualenvPythonの仮想環境を作成する。その中で調査対象のプログラムが動作できるようpipで必要なパッケージをインストールする。
  3. 調査対象のプログラムのエントリポイント(ここではhello.py)に対してpydeps hello.py --max-bacon=0 --show-deps --noshow > hello.dotのように実行する。
    • --max-bacon=0を指定しているのは、デフォルトだと限られた範囲の依存関係しか調査されないため。たとえばFlaskを使っているアプリケーションだと、Flaskへの依存関係しか出力されず、jinja2等への依存関係は表示されない。
    • --show-depsを指定しているのは、依存関係のグラフに含まれるファイル名を抽出しやすくするため。
    • --noshowを指定しているのは、WSL2環境ではxdg-utilsによるSVGファイルの表示ができなかったため。
  4. hello.dotに出力されているPythonファイル名を調べる。出力されているファイルは残し、出力されていないファイルはリネームするなどしてアプリケーションから参照できないようにする。
  5. 仮想環境に残ったファイルのみを使って、アプリケーションが実行可能か確認する。

たとえば https://methane.github.io/flask-handson/start.html#hello-world にあるようなごく単純なFlaskアプリケーションであれば、/lib/python3.8/site-packages/flask/views.pyには直接の依存関係がないことが分かります。virtualenvで作った環境からviews.py(と、対応する.pyc)を削除しても問題なく動作することが確認できました

問題

pydepsはソースコード中のimportをもとに依存関係を調べるので、実行時に使われないパスを除外できないことの他にも、以下のような問題があると思われます。

  • evalで評価される文の中の依存関係を追えない(必要なソースを除外してしまう)
  • ワイルドカードインポートが使われていると依存関係の調査範囲が分からない(不要なソースが含まれてしまう)