5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ROS2 runコマンドをいじってみた

Posted at

せしまるです。

本日はros2 runコマンドをちょっとだけいじった話を書いていきたいと思います。

いじった経緯

仕事内で、あるルールが決められました。
それは、実行ファイルをinstall/pkgname/に移動させるというもの

簡単に書くと、以下のように変更されます。

// 今までのインストール先.
install/pkgname/lib/pkgname/実行ファイル
// install/pkgname下はこんな感じ.
$ ls install/pkgname
lib  share

// 変更後のインストール先.
install/pkgname/実行ファイル
// install/pkgname下はこんな感じ.
$ ls install/pkgname
実行ファイル  share

変更する点

まず、CMakeLists.txtをいじりました。
具体的に言うとinstall部分の変更です。

CMakeLists.txt
// before.
install(TARGETS
  xxxx
  DESTINATION lib/xxxx
  )

↓に変更

CMAkeLists.txt
// after.
install(TARGETS
  xxxx
  DESTINATION .
  )

これをビルドすると、install/pkgname/に実行ファイルが置かれます。
とりあえず実行してみるかと思い、local_setup.bashを読み込んでからros2 runコマンドを叩きます。

console
$ source install/local_setup.bash
$ ros2 run xxxx xxxx
No executable found

あれ、実行ファイルが見つからないだと・・・?

ROS2 runの仕様

なんでだろうと思い、とりあえず調べましたが納得のいくものは見つからず。
仕方ないのでros2 runの処理部分を見てみる。
(公式マニュアルの通りにROS2がインストールされていると仮定します)

~/ros2_ws/install/ros2run/lib/python3.5/site-packages/ros2run/command/run.py
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from argparse import REMAINDER
import shlex

from ros2cli.command import CommandExtension
from ros2pkg.api import package_name_completer
from ros2pkg.api import PackageNotFound
from ros2run.api import ExecutableNameCompleter
from ros2run.api import get_executable_path
from ros2run.api import MultipleExecutables
from ros2run.api import run_executable


class RunCommand(CommandExtension):
    """Run a package specific executable."""

    def add_arguments(self, parser, cli_name):
        arg = parser.add_argument(
            '--prefix',
            help='Prefix command, which should go before the executable. '
                 'Command must be wrapped in quotes if it contains spaces '
                 "(e.g. --prefix 'gdb -ex run --args').")
        try:
            from argcomplete.completers import SuppressCompleter
        except ImportError:
            pass
        else:
            arg.completer = SuppressCompleter()
        arg = parser.add_argument(
            'package_name',
            help='Name of the ROS package')
        arg.completer = package_name_completer
        arg = parser.add_argument(
            'executable_name',
            help='Name of the executable')
        arg.completer = ExecutableNameCompleter(
            package_name_key='package_name')
        parser.add_argument(
            'argv', nargs=REMAINDER,
            help='Pass arbitrary arguments to the executable')

    def main(self, *, parser, args):
        try:
            path = get_executable_path(
                package_name=args.package_name,
                executable_name=args.executable_name)
        except PackageNotFound:
            raise RuntimeError(
                "Package '{args.package_name}' not found"
                .format_map(locals()))
        except MultipleExecutables as e:
            msg = 'Multiple executables found:'
            for p in e.paths:
                msg += '\n- {p}'.format_map(locals())
            raise RuntimeError(msg)
        if path is None:
            return 'No executable found'
        prefix = shlex.split(args.prefix) if args.prefix is not None else None
        return run_executable(path=path, argv=args.argv, prefix=prefix)

お、mainの最初のほうでget_executable_pathとかいうの呼んでるじゃん。
これはros2runのapiの方に定義されているみたいなのでそちらも見てみる。

~/ros2_ws/install/ros2run/lib/python3.5/site-packages/ros2run/api/__init__.py
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os
import subprocess
import sys

from ros2pkg.api import get_executable_paths
from ros2pkg.api import PackageNotFound


class MultipleExecutables(Exception):

    def __init__(self, paths):
        self.paths = paths


def get_executable_path(*, package_name, executable_name):
    paths = get_executable_paths(package_name=package_name)
    paths2base = {}
    for p in paths:
        basename = os.path.basename(p)
        if basename == executable_name:
            # pick exact match
            paths2base[p] = basename
        elif sys.platform == 'win32':
            # check extensions listed in PATHEXT for match without extension
            pathext = os.environ.get('PATHEXT', '').lower().split(os.pathsep)
            ext = os.path.splitext(basename)[1].lower()
            if ext in pathext and basename[:-len(ext)] == executable_name:
                # pick match because of known extension
                paths2base[p] = basename
    if not paths2base:
        return None
    if len(paths2base) > 1:
        raise MultipleExecutables(paths2base.keys())
    return list(paths2base.keys())[0]


def run_executable(*, path, argv, prefix=None):
    cmd = [path] + argv

    # on Windows Python scripts are invokable through the interpreter
    if os.name == 'nt' and path.endswith('.py'):
        cmd.insert(0, sys.executable)

    if prefix is not None:
        cmd = prefix + cmd

    process = subprocess.Popen(cmd)
    while process.returncode is None:
        try:
            process.communicate()
        except KeyboardInterrupt:
            # the subprocess will also receive the signal and should shut down
            # therefore we continue here until the process has finished
            pass
    return process.returncode


class ExecutableNameCompleter:
    """Callable returning a list of executable names within a package."""

    def __init__(self, *, package_name_key=None):
        self.package_name_key = package_name_key

    def __call__(self, prefix, parsed_args, **kwargs):
        package_name = getattr(parsed_args, self.package_name_key)
        try:
            paths = get_executable_paths(package_name=package_name)
        except PackageNotFound:
            return []
        return [os.path.basename(p) for p in paths]

get_executable_pathは見つけたけど、その中でさらにget_executable_pathsを呼んでいる。
get_executable_pathsはros2pkgの方にあるらしい。

~/ros2_ws/install/ros2pkg/lib/python3.5/site-packages/ros2pkg/api/__init__.py
# Copyright 2017 Open Source Robotics Foundation, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

from ament_index_python import get_package_prefix
from ament_index_python import get_packages_with_prefixes
from ament_index_python import PackageNotFoundError


class PackageNotFound(Exception):

    def __init__(self, package_name):
        self.package_name = package_name


def get_package_names():
    return get_packages_with_prefixes().keys()


def get_prefix_path(package_name):
    try:
        prefix_path = get_package_prefix(package_name)
    except PackageNotFoundError:
        return None
    return prefix_path


def get_executable_paths(*, package_name):
    prefix_path = get_prefix_path(package_name)
    if prefix_path is None:
        raise PackageNotFound(package_name)
    base_path = os.path.join(prefix_path, 'lib', package_name)
    executable_paths = []
    for dirpath, dirnames, filenames in os.walk(base_path):
        # ignore folder starting with .
        dirnames[:] = [d for d in dirnames if d[0] not in ['.']]
        dirnames.sort()
        # select executable files
        for filename in sorted(filenames):
            path = os.path.join(dirpath, filename)
            if os.access(path, os.X_OK):
                executable_paths.append(path)
    return executable_paths


def package_name_completer(**kwargs):
    """Callable returning a list of packages names."""
    return get_package_names()

見てみると、44行目にそれらしきものが(os.path.joinのところ)。
というかべた書きでlibって書いてあるしw

ちなみにos.path.joinの引数になっている変数の中身はこんな感じ。
・prefix_path:install/pkgnameまでのパス
・'lib':libという名前のディレクトリ(べた)
・package_name:パッケージ名。これはlib下のパッケージ名を指す

なるほどなるほど。
今の実装だとlib/pkgnameはいらないから、こうすればいいかな。
編集には管理者権限がいるのでsudoしましょう。

~/ros2_ws/install/ros2pkg/lib/python3.5/site-packages/ros2pkg/api/__init__.py
# 44行目
base_path = prefix_path
# 一応コメントアウト
#base_path = os.path.join(prefix_path, 'lib', package_name)

変更を保存したら、とりあえず再起動(怖いので)

実行してみる

再起動後、作業ディレクトリに移動し、local_setup.bashを読み込む。
そしてros2 runコマンドを実行・・・の前にちゃんとros2が動いてるか確認してみる。

console
$ ros2 daemon status
The daemon is not running

動いてはいるな…。
よし、実行だ!

console
$ ros2 run xxxx xxxx
xxxx start

うごいたー!やったぜ。

余談

怖くなったのでその他のros2関連コマンドを確認しましたが、どれも問題なく動いている感じはします。
ただ、この方法はルールを無視しようという考えのものやってみただけなので、実際にやる場合は自己責任でお願いします(遠い目)

というわけで今回はここまでです。
ありがとうございました。

5
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?