諸事情 により ROS/ROS2 で使われているメタビルドシステムである colcon について調べていた。あまりドキュメントがなくなかなか苦戦していたのだが、折角なのでその時まとめていたメモを公開します。
ちなみに https://github.com/nonanonno/colcon_d を作ってました。(修正しなきゃ
Q. 誰向けの記事?
A. colcon を開発したい皆さん
とは言え、Getting Started 的なものではない。colcon の概要は把握したが結局何ができるのかを調べたい、あるいは、なんとなく colcon の拡張機能を作る準備はできた(↓のツイートみたいに)が、どこを開発したら良さそうなのかわからないといった時に参考にできるかもしれない。私自身が開発していて気になった部分をコードを見ながら(コードを見るしかないのである!)解析していたメモが含まれている。力尽きている箇所があったりするがそこはご了承願いたい。
— nonno() (@nonanonno) February 11, 2021
では、本編。
colcon
以降では D言語向けビルドツール権パッケージ管理ツールである DuB 向けの拡張を開発するものとします。
概要
複数のパッケージ間の依存関係を解決しつつ各パッケージのビルドやテストを行うメタビルドシステム。やるのは依存関係解決とビルドやテストなどのキックまでで、実際にビルドを行うのは CMake など、各パッケージに対応するツールになる。
colcon は Python(>=3.5)で作られており、拡張可能である。拡張ポイントは以下の通り。
Point | Who use | Description |
---|---|---|
Verb | colcon |
colcon <verb> を作成 |
PackageDiscovery | Verb | package を探すルール(デフォルト./ とか?) |
PackageIdentification | Discovery | package の種類を解釈(cmake とか ament_cmake とか) |
PackageAugmentation | package 識別した後の Verb | 任意の情報を package descriptor に追加(追加の依存先など) |
PackageSelection | package 識別し追加情報を与えた後の Verb |
--packages-select とかのあれ |
Task | package識別し追加情報を与えた後のVerb | verb と package type の組み合わせに対応する処理を定義(build と cmake など) |
Executor | some verb extensions |
--parallel-workers とかのあれ |
Shell | some task extensions | bash や zsh など、それぞれ向けの環境変数設定スクリプトを作るためのもの |
Environment | some task extensions | environment や environment hook を作るもの? |
EventHandler |
--event-handlers とかのあれ |
|
AugmentParser | undocumented | |
PrefixPath | undocumented |
ここのフォルダがそれぞれの拡張ポイントに対応していそう。
また、インストールされている拡張は colcon extensions
で確認することができる。
詳細
新たなビルドツール向けの拡張を作る際に必要になりそうな拡張ポイントについて少し詳細に書いている。
Verb
colcon <verb>
を作成するための拡張ポイント。colcon-core では build
と test
が最初から定義されている。
PackageDiscovery
パッケージを探す場所に関するルール。colcon build
や colcon test
など、各 Verb によって使用される。colcon build --help
で見つけることができる Discovery arguments
セクションのオプションを提供する。探索位置を指定するオプションを提供したり、ディレクトリを再帰的に探す機能の提供を行っている。
PackageIdentification
概要
パッケージの種類を解釈するための拡張ポイント。PackageDiscovery によって得られたディレクトリが何らかのパッケージであるか、それが何の種類なのかを解釈する。setup.py
と setup.cfg
が Python, CMakeLists.txt
があれば cmake プロジェクト、など。
使い方
colcon_core.package_identification.PackageIdentificationExtensionPoint を継承し、identify
を override する。
from colcon_core.package_identification import PackageIdentificationExtensionPoint
class DubPackageIdentificationExtensionPoint(PackageIdentificationExtensionPoint):
PRIORITY = 100
def identify(self,desc: PackageDescriptor) -> None:
...
-
PRIORITY
: 識別のための優先度- 数字が高い程優先度が高い
- 複数の PackageIdentification にマッチした時にどれを選択するかをする
-
CMakeLists.txt
とpackage.xml
がある時、cmake 向けではなく ros 向けを優先する、といった
-
- 同じ優先度で複数の PackageIdentification がマッチしたら警告とともにそのパッケージは無視される
-
identify
: 識別処理を行う- desc: PackageDescriptor が渡される
- フィールド
-
path
: パッケージパス -
type
: パッケージの種類 (cmake とか ros.ament_cmake とか) -
name
: パッケージ名 -
dependencies
:defaultdict(set)
-
hooks
: Each item in 'hooks' must be a relative path in the installation space. -
matadata
: any additional information
-
- パッケージのユニーク性は
path
とtype
とname
の triplet で表現される-
path
は、文字列として違っていても同じ絶対パスを指していれば同じと見なす -
identify
関数でこの3つに値を入れれば、識別した、と解釈される -
path
には最初から値が入っている
-
-
dependencies
は依存パッケージのリストではない。DependencyDescriptor
あるいは文字列で表現されるカテゴリをキーとして、その値として依存パッケージのリストが入っている-
colcon_ros では
run
とtest
とbuild
がキーとして設定される。それぞれ以下のように対応している-
run
:<build_export_depend>
,<buildtool_export_depend>
,<exec_depend>
-
test
:<test_depend>
-
build
:<build_depend>
,<buildtool_depend>
-
-
colcon_ros では
-
hooks
の PackageIdentification での使い方はよくわからなかった -
matadata
は好きに使えば良いと思う。colcon_ros ではversion
とget_python_setup_options
を設定している。
- フィールド
- desc: PackageDescriptor が渡される
他の PackageIdentification
の identify
を呼ぶ、といった使い方も可能ではある。例えば、colcon_ros のを使うと package.xml
を解釈した。 PackageDescriptor が得られるなど。(_get_package を呼べば良い気もする)
PackageArgumentation
PackageDiscovery でフォルダを得て、PackageIdentification で何のパッケージであるかを、例えばPython と解釈し、その後 Python パッケージ向けに追加の依存を設定する、といったのがこの拡張ポイントの役割。
Python では PackageDescriptor
に対して metadata['version']
の設定と、setup.cfg
に書かれている依存パッケージをdependencies['setup_requires|install_requires|test_requires']
に設定するのをやっている。
あるいは、package_information ではパッケージ同士のバージョン整合性の確認に利用しているようだ。
パッケージを識別した後、依存関係を計算してそれぞれ処理する前に何かをやりたい時に利用する、といったところか。
PackageSelection
--packages-select
などを提供するための拡張ポイント。タイミングは PackageAugmentation の後。
Task
verb
と package type
の組み合わせで処理を行うためのポイント、verb
の部分については、ここを見る限り setup.cfg
で設定することになっている。例えば以下のようなセクションを書く。
[options.entry_points]
colcon_core.task.build =
dub = colcon_dub.task.dub.build:DubBuildTask
使い方
colcon_core.task.TaskExtensionPoint を継承する。
-
add_arguments(self, *, parser: ArgumentParser)
: コマンドライン引数を追加する。必要なら override -
<verb>
: タスク名のメンバ関数を定義する(getattr(self, self.TASK_NAME)
が呼ばれる)- 引数は、
(self, *, hoge=None, fuga=0)
といった形でデフォルト値付きで設定する。 -
verb
自体は引数なしで呼び出すが、他の拡張のタスクを呼ぶ時に伝搬してもらう必要があるものを引数として定義する -
async
である必要あり
- 引数は、
-
情報には
self.context
経由でアクセスする- フィールド
-
pkg
:PackageDescriptor
-
args
:<verb?
毎に違うらしい build の場合、 BuildPackageArguments -
dependencies
: 再帰的に解決されたタスクに必要最終的な依存パッケージ
-
- フィールド
-
何をやる必要があるのかは、
<verb>
次第(次節)
Task.build
colcon build
で実行されるタスクを定義する。TaskExtensionPoint
を継承し、build
関数を定義する。
-
self.context.dependencies
には自身が直接依存しているパッケージ +run
カテゴリの依存パッケージが再帰的に入っている - インストールまで実装する必要がある
- 必要に応じて create_environment_script を呼ぶ
- 自身に依存する別パッケージをビルドする際に必要となる環境変数(例えば、
CMAKE_PREFIX_PATH
)を設定するためのスクリプトを作るため
- 自身に依存する別パッケージをビルドする際に必要となる環境変数(例えば、
Task.test
colcon test
で実行されるタスクを定義する。TaskExtensionPoint
を継承し、test
を定義する。
-
self.context.dependencies
には build のと同様のに加えて自分自身も入っている。- テストコードをビルドするのに自分自身が必要だからか
Executor
Job 実行の仕組みを提供しているはず。--parallel-workers
と --executor
を提供しているあれ。
Shell
bash や zsh など、各shell 向けの shell script や hook を作るための拡張ポイント。必要な機能を持つ(例えば setup.bash といった)スクリプトを作るためのルールを作る。
Environment
environment hook を定義する。実際の environment hooks script は shell extension を使って作られる。
これはビルドタイプとは関係ない。build
タスクなどが create_environment_scripts
を呼んだ時に全ての Environment 拡張が実行される。
使い方
colcon_core.environment.EnvironmentExtensionPoint を継承し、create_environment_hooks(self, prefix_path, pkg_name) -> OrderedDict
を override する。
create_environment_hooks
内で必要に応じて必要な環境変数を設定する。
EventHandler
--event-handlers
のあれ。