Claude Media
Claude CodeでROS 2パッケージを作る — colcon buildとlaunch_testの反復

Claude CodeでROS 2パッケージを作る — colcon buildとlaunch_testの反復

Claude CodeにROS 2パッケージを書かせるとき、colcon buildとlaunch_testを完了条件にするCLAUDE.mdとpermissionsの設定例、sourceの扱いをまとめます。

ROS 2のパッケージをClaude Codeに書かせるなら、完了の合図は「colcon build が通り、colcon test が全件パスした」に固定するのが近道です。ビルドの成否もテストの成否もコマンドの出力と終了コードに出るので、Claude Codeが結果を読んで直す反復に向いています。

つまずきやすいのは別の場所にあります。ROS 2は source install/setup.bash で環境を作ってからでないと ros2 run も launch_test も動きません。しかもClaude Codeの許可ルールは、&& でつないだコマンドを1つずつ照合します。ワークスペースの構成、source、権限、テストの4点を CLAUDE.md と設定に先に書いておくと、反復が止まりにくくなります。

手順はLinuxとROS 2 Jazzyを前提にします。例はROS 2チュートリアルの構成に沿った書き方で、パッケージ名などは仮のものです。

ワークスペースの構成をClaude Codeに伝える

colconはソースツリーの外にビルド成果物を出す「out-of-source」ビルドです。ワークスペース直下の src と並んで、build、install、log の3ディレクトリが作られます。build は中間ファイル、install はパッケージごとの配置先、log は各実行のログです。catkinにあった devel ディレクトリはありません。

この構造は CLAUDE.md に書く価値があります。Claude Codeが編集してよいのは src 配下だけで、残り3つは生成物だからです。

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws
git clone https://github.com/ros2/examples src/examples -b jazzy

ワークスペースは、既存のROS 2インストール(underlay)の上に重なるoverlayとして作ります。チュートリアルは、少数のパッケージを反復開発するならoverlayを使い、全パッケージを1つのワークスペースに入れないよう勧めています。underlayをsourceしていないと依存関係が解決できないので、ビルドの前提として CLAUDE.md に書きます。

次の CLAUDE.md は、書き方の一例です。

# ROS 2 ワークスペース
 
- ディストリビューション: Jazzy。underlay は /opt/ros/jazzy
- 編集してよいのは src/ 配下のみ。build/ install/ log/ は生成物なので触らない
- ビルド: colcon build --symlink-install --packages-select <pkg>
- テスト: colcon test --packages-select <pkg> の後に
  colcon test-result --verbose で失敗したテスト名を読む
- ros2 run / launch_test の前に source install/setup.bash を同じコマンド行で実行する
- --cmake-args -DBUILD_TESTING=0 は付けない(テストがビルドされなくなる)
- 完了条件: 対象パッケージの colcon test-result が失敗 0 件

Claude Codeのメモリのページは、指示を「検証できるほど具体的に」書くこと、1ファイルは200行以内を目安にすることを挙げています。「テストを通す」ではなく、コマンドと成功条件まで書くのはそのためです。

新しいパッケージの作り方を決めておく

colconは package.xml の仕様(REP 149)に従います。推奨のビルドタイプは ament_cmake と ament_python の2つで、純粋な cmake パッケージも扱えます。雛形は ros2 pkg create で作れます。

Claude Codeにパッケージを新規作成させるなら、ビルドタイプを先に決めて CLAUDE.md に書きます。C++ノードなら ament_cmake、Pythonだけなら ament_python と書いておくと、--symlink-install の効き方も予測できます。colcon build --mixin debug のように、よく使うビルドオプションをmixinに登録しておく方法もあります。登録は colcon mixin add と colcon mixin update で行います。

反復の回転数を決めるのはビルド時間です。colcon build の --symlink-install を付けると、Pythonファイルなどコンパイルが要らないリソースは、src 側を直すだけで install 側に反映されます。ament_cmake のパッケージはインストールが必須で devel の概念がないため、このオプションが用意されている、とチュートリアルは説明しています。

くらべる

どこまで再ビルドが要るか

symlink あり

ament_python のノード

Pythonファイルを直すだけで install 側に反映されます。再ビルドなしで再実行できます。

再ビルド

C++ のノード

コンパイルが要るので、直したら colcon build を回し直します。

Claude Codeに対象を絞らせるなら --packages-select を使います。CPUやメモリが厳しい環境(Raspberry Piなど)では画面やマウスが固まることがあり、チュートリアルは --executor sequential を挙げています。ビルドしたくないパッケージは、そのディレクトリに空の COLCON_IGNORE ファイルを置けば索引から外れます。

colcon testとcolcon test-result — 失敗を読ませる

テストは colcon test で走り、結果は別の動詞 colcon test-result で見ます。

colcon test --packages-select my_pkg
colcon test-result --all
colcon test-result --all --verbose

--verbose を付けると、失敗したテストケースが個別に出ます。Claude Codeには「colcon test の後に colcon test-result --verbose を読んで、失敗したテスト名から直す」と指示すると、件数だけを見て終わる事態を避けられます。

テストの出力が見えないときは、Pythonテストの解説にある --event-handlers console_cohesion+ が使えます。pytestの出力がコンソールにそのまま出ます。1つのテストだけを回したいときは、次のように絞ります。

# CMake パッケージ(ctest)の1テストだけ
colcon test --packages-select my_pkg --ctest-args -R test_name
 
# Python パッケージ(pytest)の1テストだけ
colcon test --packages-select my_pkg --pytest-args -k test_name

CLIテストの解説によると、colcon test の前にworkspaceをsourceする必要はありません。colconが、テストに正しい環境と依存関係を用意するためです。sourceが要るのは、次のlaunch_testを単体で動かすときや ros2 run のときです。

launch_testを完了条件に入れる

ノードを実際に起動して確かめる統合テストは launch_testing のフレームワークで書きます。launch_testingのREADMEによると、起動したプロセスの終了コードと標準出力がテストから読め、プロセスが予期せず落ちたらテストを失敗にできます。

テストファイルには generate_test_description 関数を置き、launch_testing.actions.ReadyToTest() を含む LaunchDescription を返します。unittest.TestCase を継承した通常のクラスは起動中のプロセスと並行して走り、post_shutdown_test で修飾したクラスは終了後に走ります。次は、その構造に沿った例です(コマンド名は仮)。

import unittest
 
import launch
import launch_testing
import launch_testing.actions
import launch_testing.asserts
 
 
def generate_test_description():
    return launch.LaunchDescription([
        launch.actions.ExecuteProcess(cmd=['ros2', 'run', 'my_pkg', 'my_node']),
        launch_testing.actions.ReadyToTest(),
    ])
 
 
class TestRunning(unittest.TestCase):
    def test_prints_ready(self, proc_output):
        proc_output.assertWaitFor('ready', timeout=10)
 
 
@launch_testing.post_shutdown_test()
class TestShutdown(unittest.TestCase):
    def test_exit_code(self, proc_info):
        launch_testing.asserts.assertExitCodes(proc_info)

assertWaitFor の timeout は秒単位の待機で、既定は10です。待ち時間を超えると AssertionError になります。

colcon test の中で動かすには、CMakeLists.txt に登録します。統合テストの解説は、BUILD_TESTING の中で launch_testing_ament_cmake を探し、add_launch_test を呼ぶ構成を示しています。

if(BUILD_TESTING)
  find_package(launch_testing_ament_cmake REQUIRED)
  add_launch_test(test/test_integration.py)
endif()

package.xml には <test_depend>launch_testing</test_depend> と <test_depend>launch_testing_ament_cmake</test_depend> も要ります。Claude Codeがテストを足したのに実行されない場合は、この登録と依存の漏れをまず疑います。登録前のテストファイルは、launch_test test/test_integration.py で単体実行して中身を確かめられます。

source install/setup.bashとpermissions

ここがROS 2特有の摩擦点です。Claude Codeの権限ルールは、&&、;、| などで区切られた各サブコマンドを個別に照合します。権限のページの例では、Bash(safe-cmd *) を許可しても safe-cmd && other-cmd は通りません。つまり source install/setup.bash && ros2 run my_pkg my_node は、source側と ros2 run 側の両方に許可ルールが要ります。

反復で使うコマンドは、次のように許可しておけます。

{
  "permissions": {
    "allow": [
      "Bash(colcon build *)",
      "Bash(colcon test *)",
      "Bash(colcon test-result *)",
      "Bash(source install/setup.bash)",
      "Bash(launch_test *)"
    ],
    "deny": [
      "Edit(/install/**)",
      "Edit(/build/**)",
      "Edit(/log/**)"
    ]
  }
}

ルールの書き方には注意点が2つあります。末尾の * の前の空白はルールの一部で、Bash(colcon test *) は colcon test-result に一致しません。そのため test-result は別の行で許可しています。もう1つは、ルールがClaudeの書いたコマンド文字列に対する照合なので、/usr/bin/colcon のようにパスを付けた呼び出しは一致しない点です。

ros2 run や ros2 launch は、ノードを起動するコマンドです。長く動き続けるものを許可する前に、何が走るかを確かめてください。複数のコマンドを1つのルールに頼らず通したい場合の整理は、Bashの許可ルールと複合コマンドで扱っています。ルールの確認と管理には/permissionsコマンドが使えます。

underlayのsourceを毎回書かない方法

Claude Codeには、シェルの状態を次のコマンドへ持ち越す手段として CLAUDE_ENV_FILE があります。環境変数のページによると、これはBashコマンドの実行前に毎回実行されるシェルスクリプトのパスで、virtualenvやcondaの有効化を引き継ぐために使います。SessionStartフックはこのパスを受け取れるので、underlayのsourceをそこに書けます。

#!/bin/bash
# SessionStart フックのスクリプト例
if [ -n "$CLAUDE_ENV_FILE" ]; then
  echo 'source /opt/ros/jazzy/setup.bash' >> "$CLAUDE_ENV_FILE"
fi
exit 0

overlayの install/setup.bash はビルドのたびに内容が変わるため、この方法に入れず、ros2を使うコマンドの直前でsourceする形にとどめています。

反復の流れをフックで締める

ここまでの設定で、1回の反復は「編集 → ビルド → テスト → test-result を読む → 直す」になります。Claude Codeが「できました」と返す前にテストを回させたい場合は、Stopフックを使います。PostToolUseとStopのどちらに置くかは、テストを自動実行するhooksで比較しています。

ZigやC++ など別言語で同じ設計をした例は、Claude CodeでZigを書くにあります。完了条件のコマンドを CLAUDE.md、permissions、フックの3層で固定する考え方は、ROS 2でもそのまま使えます。

ROS 2で起きやすいつまずき

  • sourceを忘れる: ros2 run が「パッケージが見つからない」系の失敗をするとき、新しいシェルでoverlayをsourceしていないことが多い。CLAUDE.md に「同じコマンド行でsource」と書いてあれば、Claude Codeは自分で気づけます
  • underlayが未source: ビルド依存が解決できず、colcon build が最初のパッケージで止まります
  • BUILD_TESTING=0: ビルド時間は縮みますが、テストがビルドされず、colcon test が実質的に何も確かめません。完了条件が空になるので、CLAUDE.md で禁止しておきます
  • launch_testのタイムアウト: ノードの起動が遅いと assertWaitFor が AssertionError で落ちます。timeout を延ばす前に、ノードが出力する文字列が変わっていないか確かめます

Windowsでは事情が異なります。colconのチュートリアルは、Visual Studioの環境が必要なこと、--merge-install を使うこと、シンボリックリンクの作成に管理者権限か開発者モードが要ることを挙げています。ここまでの設定はLinuxの話です。

この記事を共有:XはてブLinkedIn