Technical Document for make_mini_tklib.py
プログラムの動作
make_mini_tklib.py は、Pythonプログラムのエントリーポイントとなるスクリプトから静的にインポートされるモジュールを解析し、特定のPythonパッケージのサブセットを生成することを目的としたツールです。具体的には、指定されたエントリースクリプトがインポートするPythonファイルのうち、指定されたパッケージ (pkg_name) のルートディレクトリ (pkg_root) 配下にあるものを再帰的に収集し、出力ディレクトリ (out) へ元の相対パス構造を保ってコピーします。
このプログラムの主な機能は以下の通りです。
静的依存解析:
astモジュールを用いてPythonソースコードを解析し、importおよびfrom ... import ...ステートメントを検出します。パッケージサブセット生成: 解析で検出されたインポートのうち、
pkg_root配下に解決できるファイルのみを選別し、出力ディレクトリへ複製します。これにより、特定のアプリケーションに必要な最小限のパッケージ構造を構築できます。__init__.pyファイルの自動追加: コピーされるPythonファイルを含むディレクトリ階層において、__init__.pyファイルが存在すれば、それらも自動的にコピー対象に含めます。オプション機能:
--report-unused:pkg_root配下に存在するものの、依存解析によって選択されなかったPythonファイルをリストアップし、レポートとして出力します。--dot-out: 収集された依存関係をGraphvizのDOT形式で出力し、視覚化を可能にします。--ignore-if-imports:ifブロック内に記述されたインポート文を無視することで、より厳密な最小サブセットを生成できます。
ドライランモード:
--dry-runオプションを使用すると、実際にファイルをコピーせずに、どのファイルがコピーされるかを事前に確認できます。
このツールは、大規模なPythonパッケージの中から、特定の用途に合わせた最小限の依存関係を持つサブパッケージを作成したい場合に有用です。これにより、配布物のサイズを削減し、デプロイメントを簡素化できます。
原理
make_mini_tklib.py は、Pythonの標準ライブラリである ast モジュールを利用した静的解析に基づいて動作します。
エントリーポイントからの探索: プログラムは、指定されたエントリースクリプト (
--entry) を起点として、依存関係グラフを幅優先探索 (BFS) に近い形で構築します。インポートの抽出: 各Pythonファイルは
ast.parse()によって抽象構文木 (AST) に変換されます。その後、カスタムのast.NodeVisitorがこのASTを走査し、ast.Importとast.ImportFromノードを抽出します。--ignore-if-importsオプションが有効な場合、ast.Ifノードの内部にあるインポートは無視されます。
モジュールの解決: 抽出されたインポート文は
ImportRefオブジェクトとして表現されます。これには、モジュール名、インポートされる名前(from X import a, bのa, b)、および相対インポートのレベルが含まれます。絶対インポートの解決:
import foo.barやfrom foo.bar import bazのような絶対インポートは、pkg_nameとpkg_rootを基準にしてファイルパスに解決されます。例えば、pkg_name="tklib"、pkg_root=/path/to/tklib、module="tklib.sub.mod"の場合、候補パスとして/path/to/tklib/sub/mod.pyや/path/to/tklib/sub/mod/__init__.pyが生成されます。相対インポートの解決:
from . import modやfrom ..sub import modのような相対インポートは、現在のファイル (current_file) のパスとインポートレベル (level) を基に、相対的なディレクトリを特定し、そこからモジュールパスを解決します。
pkg_root内のフィルタリング: 解決されたファイルパスのうち、pkg_rootディレクトリの配下に存在するファイルのみが「選択されたファイル」としてキューに追加され、さらなる解析対象となります。is_within()関数がこの判定に使用されます。__init__.pyの追加: 任意のPythonファイルが選択された場合、そのファイルの親ディレクトリからpkg_rootまでの間の全てのディレクトリに__init__.pyが存在すれば、それらも自動的に選択済みファイルセットに追加されます。これは、Pythonパッケージとして機能するために必要な構造を維持するためです。コピー: すべての依存関係が解決され、必要なファイルが
selectedセットに収集された後、これらのファイルはpkg_rootからの相対パス構造を保ったままout_rootディレクトリへコピーされます。
プログラムは、以下のようなパス解決ヘルパー関数群とデータ構造を使用します。
norm(p: Path) -> Path: パスを正規化し、絶対パスに解決します。is_within(child: Path, parent: Path) -> bool:childパスがparentパスの配下にあるかを判定します。find_pkg_root_upward(entry: Path, pkg_name: str) -> Optional[Path]: エントリースクリプトの親ディレクトリを遡ってpkg_nameディレクトリを探します。default_pkg_root_from_env_or_fallback() -> Optional[Path]: 環境変数tkProg_Rootからデフォルトのpkg_rootを探し、見つからなければハードコードされたパスを試します。ImportRef: インポート文の情報を保持するdataclassです。extract_imports(): ファイルからImportRefリストを抽出します。module_to_candidate_paths_under_root(): モジュール名からpkg_root配下の候補ファイルパスを生成します。resolve_import_ref_to_files():ImportRefを具体的なファイルパスに解決します。
必要な非標準ライブラリとインストール方法
make_mini_tklib.py は、Pythonの標準ライブラリのみを使用しており、追加の非標準ライブラリのインストールは不要です。
ただし、--dot-out オプションで生成されるGraphviz DOTファイルを画像としてレンダリングするには、別途Graphvizツールキットをインストールする必要があります。GraphvizはPythonライブラリではなく、システムにインストールするツールです。
Graphvizツールのインストール方法(例:Ubuntu/Debian系Linux):
sudo apt update
sudo apt install graphviz
Graphvizツールのインストール方法(例:macOS):
brew install graphviz
Graphvizツールのインストール方法(例:Windows):
Graphvizの公式ウェブサイトからインストーラーをダウンロードし、指示に従ってインストールしてください。 Graphviz Download
DOTファイルをレンダリングするコマンドの例:
dot -Tpng deps.dot -o deps.png
必要な入力ファイル
エントリーPythonスクリプト:
--entryオプションでパスを指定します。プログラムは、このスクリプトから始まる依存関係を解析します。
例:
Ne-T_fit.py
ターゲットパッケージのPythonファイル群:
--pkg-rootオプションで指定されたディレクトリ、または自動検出されたディレクトリ配下にある、Pythonソースファイル (.py) とパッケージ初期化ファイル (__init__.py) です。これらのファイルは、エントリースクリプトからインポートされる可能性があります。
パッケージ名:
--pkg-nameオプションで指定します(デフォルトはtklib)。この名前は、絶対インポートの解決や、
pkg_rootの検出に使用されます。
生成される出力ファイル
ミニパッケージのファイル群:
--outオプションで指定されたディレクトリ(デフォルトはtklib_sub)の直下に、pkg_rootからの相対パスを保ったPythonファイルがコピーされます。これには、
__init__.pyファイルも含まれる場合があります。
未使用ファイルレポート (オプション):
--report-unusedオプションが指定された場合、出力ディレクトリの直下にunused_files.txtというファイルが生成されます。このファイルには、
pkg_root配下に存在するものの、エントリースクリプトから直接的または間接的にインポートされなかったすべてのPythonファイルのリストが、pkg_rootからの相対パスで記述されます。
依存関係グラフのDOTファイル (オプション):
--dot-out <FILE>オプションが指定された場合、指定されたファイルパスにGraphviz DOT形式のテキストファイルが生成されます。このファイルは、
make_mini_tklib.pyが解析しコピーしたPythonファイル間のインポート依存関係を記述したもので、Graphvizツールを使用して視覚化できます。ノードはファイルパス(pkg_rootからの相対パス)、エッジはインポート関係(インポート元 -> インポート先)を示します。
コマンドラインでの使用例 (Usage)
make_mini_tklib.py の基本的な使用法は以下の通りです。
python make_mini_tklib.py --entry <entry_script_path> [OPTIONS]
必須引数:
--entry ENTRY: 解析を開始するPythonスクリプトへのパス(例:Ne-T_fit.py)。
オプション引数:
--pkg-name PKG_NAME: ターゲットとなるパッケージの名前(デフォルト:"tklib")。--pkg-root PKG_ROOT: パッケージのルートディレクトリへのパス。省略した場合、環境変数tkProg_Root、ハードコードされたパス、またはエントリースクリプトからの上位ディレクトリ探索によって自動検出されます。--out OUT: 生成されるミニパッケージが出力されるフォルダ(デフォルト:tklib_sub)。--dry-run: 実際にファイルを書き込まずに、コピーされるファイルを表示します。--verbose: スキャン中の詳細なログを出力します。
最小化オプション:
--ignore-if-imports:ifブロック内にあるインポート文を無視し、より最小限のサブセットを作成します。
レポートオプション:
--report-unused:pkg_root配下で選択されなかったPythonファイルをunused_files.txtとして--outディレクトリに出力します。--dot-out FILE: 選択されたファイル間の依存関係をGraphviz DOT形式で指定されたファイルに出力します(例:deps.dot)。
コマンドラインでの具体的な使用例
以下の例では、make_mini_tklib.py の様々な使用シナリオと、それぞれのコマンドライン引数がどのような結果をもたらすかを説明します。
前提:
Ne-T_fit.pyとoptimize_mup.pyは、ターゲットパッケージtklibのモジュールをインポートするエントリースクリプトと仮定します。D:\path\to\tklibまたはD:\git\tkProg\tklib\python\tklibがtklibパッケージのルートディレクトリであると仮定します。
1. デフォルトの tklib パッケージを検索し、ミニパッケージを生成する
この例では、pkg_root を明示せずに、make_mini_tklib.py がデフォルトの検索ロジック(環境変数、ハードコードパス、上位ディレクトリ探索)を使って tklib パッケージのルートを特定します。
python make_mini_tklib.py --entry Ne-T_fit.py --out tklib_sub --verbose
説明:
Ne-T_fit.pyをエントリーポイントとして、そのスクリプトが依存するtklibパッケージ内のモジュールを収集します。収集されたファイルはtklib_subディレクトリにコピーされ、詳細なスキャンログが表示されます。
2. tklib パッケージの場所を明示する場合
tklib パッケージのルートディレクトリが特定の場所にあることを明示的に指定します。
python make_mini_tklib.py --entry Ne-T_fit.py --pkg-root D:\path\to\tklib --out tklib_sub --verbose
説明:
D:\path\to\tklibをtklibパッケージのルートとして使用し、Ne-T_fit.pyからの依存関係を解析します。結果はtklib_subに出力されます。これにより、pkg_rootの自動検出ロジックに依存しない、確実なパス指定が可能です。
3. パッケージ名と場所を明示する場合
pkg_name (この例では tklib) と pkg_root の両方を明示的に指定します。エントリーポイントが optimize_mup.py の場合。
python make_mini_tklib.py --entry optimize_mup.py --pkg-name tklib --pkg-root D:\git\tkProg\tklib\python\tklib --out tklib_sub --verbose
説明:
optimize_mup.pyをエントリーポイントとし、パッケージ名をtklib、ルートディレクトリをD:\git\tkProg\tklib\python\tklibとして解析を行います。これは、複数のパッケージが存在する場合や、デフォルトのパッケージ名以外を使用したい場合に有効です。
4. 何がコピーされるかだけを確認する(ドライラン)
実際にファイルをコピーする前に、どのファイルが選択され、どこにコピーされるかを確認します。
python make_mini_tklib.py --entry optimize_mup.py --pkg-name tklib --pkg-root D:\git\tkProg\tklib\python\tklib --out tklib_sub --dry-run --verbose
説明:
--dry-runオプションにより、ファイルは実際にコピーされません。代わりに、[DRY]プレフィックス付きで、コピーされる予定のソースパスとターゲットパスが標準出力に表示されます。これにより、意図しないファイルがコピーされるのを防ぎ、結果を事前に検証できます。
5. if ブロック内のインポートをコピーしない
動的な条件に基づいてインポートされるモジュールを無視し、より厳密な最小サブセットを作成します。
python make_mini_tklib.py --entry optimize_mup.py --pkg-name tklib --pkg-root D:\git\tkProg\tklib\python\tklib --out tklib_sub --ignore-if-imports --verbose
説明:
--ignore-if-importsオプションが指定されているため、optimize_mup.pyおよびその依存関係のPythonファイル内で、ifステートメントのブロック内にあるインポート文は解析対象から除外されます。これにより、実行時に常に必要とされないモジュールをサブセットから除外できます。
6. 未使用ライブラリを報告する
pkg_root 配下のどのファイルがミニパッケージに含まれなかったかをレポートとして出力します。
python make_mini_tklib.py --entry Ne-T_fit.py --out tklib_sub --report-unused
説明:
Ne-T_fit.pyからの依存関係解析後、tklib_subディレクトリにunused_files.txtが生成されます。このファイルには、pkg_rootディレクトリ(自動検出または明示的に指定されたもの)内に存在する全ての.pyファイルのうち、生成されたミニパッケージに含まれなかったもののリストが含まれます。
7. 依存関係を可視化するDOTファイルを出力する
解析された依存関係をGraphviz形式で出力し、後でグラフとしてレンダリングできるようにします。
python make_mini_tklib.py --entry optimize_mup.py --pkg-name tklib --pkg-root D:\git\tkProg\tklib\python\tklib --out tklib_sub --dot-out deps.dot
説明: 実行後、
deps.dotというファイルが生成されます。このファイルはGraphvizツールで処理できる形式で、optimize_mup.pyとそこからインポートされたtklib内のモジュール間の依存関係が記述されています。この
deps.dotファイルをPNG画像に変換する例:dot -Tpng deps.dot -o deps.png