Skip to content

Commit

Permalink
address review
Browse files Browse the repository at this point in the history
  • Loading branch information
EricCousineau-TRI committed Apr 18, 2024
1 parent afebb39 commit ef17e8f
Show file tree
Hide file tree
Showing 13 changed files with 234 additions and 125 deletions.
11 changes: 0 additions & 11 deletions bazel_ros2_rules/ros2/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,6 @@ py_library(
deps = ["@bazel_tools//tools/python/runfiles"],
)

py_library(
name = "roslaunch_util.py",
srcs = [
"tools/roslaunch_util/__init__.py",
"tools/roslaunch_util/roslaunch_util.py",
"tools/roslaunch_util/setup.py",
],
visibility = ["//visibility:public"],
deps = ["@bazel_tools//tools/python/runfiles"],
)

run_calculate_rosidl_capitalization_tests()

py_library(
Expand Down
21 changes: 20 additions & 1 deletion bazel_ros2_rules/ros2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ equivalent to the native `py_binary` and `py_test` rules, ensure these binaries
run in an environment that is tightly coupled with the underlying ROS 2
workspace install space.

To generate a Python or XML launch target, `ros_launch` is available in the
`ros_py.bzl` file. Note that there are some nuances; pelase see the Launch
Files section below.

To generate and build ROS 2 interfaces, a `rosidl_interfaces_group` rule is
available in the `rosidl.bzl` file. This rule generates C++ and Python code
for the given ROS 2 interface definition files and builds them using what is
Expand All @@ -103,6 +107,21 @@ the same file. By default, these naming conventions allow downstream
`rosidl_interfaces_group` rules to depend on upstream `rosidl_interface_group`
rules.

#### Launch Files

An example of the `ros_launch` macro can be found under `ros2_example_apps`.
Please note the following limitations:

- Exposing a Bazel package as a ROS package has not yet been done. Once that is
done, `launch_ros.actions.Node` / `<node/>` can be used.
- For Python launch files, it is best to use `Rlocation` (as shown in the example)
so that the launch file can be run via `bazel run`, `bazel test`, and
`./bazel-bin` (directly).
- For XML launch files, we need to (somehow) expose either ROS packages or
`Rlocation`. This needs to be done in a way that can be discovered by
[`Parser.load_launch_extensions`](https://github.com/ros2/launch/blob/698e979382877242621a0d633750fe96ff0c2bca/launch/launch/frontend/parser.py#L72-L87),
which may require care to do so correctly in Bazel.

### Tools

The `rmw_isolation` subpackage provides C++ and Python `isolate_rmw_by_path`
Expand Down Expand Up @@ -188,9 +207,9 @@ From the above two examples (at present), the following features are in
directly, but instead leverage the C-level interfaces, we get into an
awkward ground of mutually exclusive memory / resource management
paradigms.
- Affordances for `ros2 launch`

The other repos, however, have the following that `bazel_ros2_rules` is
missing:

- Affordances for `ros2 launch`
- Launching from containers (Docker, Apptainer)
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ def _runfiles():


def Rlocation(path):
return _runfiles().Rlocation(path)
result = _runfiles().Rlocation(path)
if result is None:
raise RuntimeError(f"Unable to find Rlocation({repr(path)})")
return result


def make_bazel_runfiles_env():
Expand Down
96 changes: 86 additions & 10 deletions bazel_ros2_rules/ros2/ros_py.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -130,24 +130,100 @@ def ros_py_binary(
)
py_binary_rule(name = name, **kwargs)

def _add_deps(existing, new):
deps = list(existing)
for dep in new:
if dep not in deps:
deps.append(dep)
return deps

def _generate_file_impl(ctx):
out = ctx.actions.declare_file(ctx.label.name)
ctx.actions.write(out, ctx.attr.content, ctx.attr.is_executable)
return [DefaultInfo(
files = depset([out]),
data_runfiles = ctx.runfiles(files = [out]),
)]

_generate_file = rule(
attrs = {
"content": attr.string(mandatory = True),
"is_executable": attr.bool(default = False),
},
output_to_genfiles = True,
implementation = _generate_file_impl,
)

_LAUNCH_PY_TEMPLATE = """
import os
import sys
from bazel_ros_env import Rlocation
assert __name__ == "__main__"
launch_file = Rlocation({launch_respath})
ros2_bin = Rlocation("ros2/ros2")
args = [ros2_bin, "launch", launch_file] + sys.argv[1:]
os.execv(ros2_bin, args)
"""

def _make_respath(relpath, workspace_name):
repo = native.repository_name()
if repo == "@":
if workspace_name == None:
fail(
"Please provide `ros_launch(*, workspace_name)` so that " +
"paths can be resolved properly",
)
repo = workspace_name
pkg = native.package_name()
if pkg != "":
pieces = [repo, pkg, relpath]
else:
pieces = [repo, relpath]
return "/".join(pieces)

def ros_launch(
name,
launch_file = None,
node_targets = []):
deps = ["@ros2//:ros2", "@bazel_ros2_rules//ros2:roslaunch_util.py"]
srcs = ["@bazel_ros2_rules//ros2:roslaunch_util.py"]
launch_file,
args = [],
data = [],
deps = [],
visibility = None,
# TODO(eric.cousineau): Remove this once Bazel provides a way to tell
# runfiles.py to use "this repository" in a way that doesn't require
# bespoke information.
workspace_name = None,
**kwargs):
main = "{}_roslaunch_main.py".format(name)
launch_respath = _make_respath(launch_file, workspace_name)

data = [launch_file]
data += node_targets
args = [launch_file]
content = _LAUNCH_PY_TEMPLATE.format(
launch_respath = repr(launch_respath),
)
_generate_file(
name = main,
content = content,
visibility = ["//visibility:private"],
)

deps = _add_deps(
deps,
[
"@ros2//:ros2",
"@ros2//resources/bazel_ros_env:bazel_ros_env_py",
],
)
data = data + [launch_file]

ros_py_binary(
name = name,
main = "@bazel_ros2_rules//ros2:roslaunch_util.py",
main = main,
deps = deps,
srcs = srcs,
srcs = [main],
data = data,
args = args,
visibility = visibility,
**kwargs
)

def ros_py_test(
Expand Down
3 changes: 0 additions & 3 deletions bazel_ros2_rules/ros2/tools/roslaunch_util/__init__.py

This file was deleted.

52 changes: 0 additions & 52 deletions bazel_ros2_rules/ros2/tools/roslaunch_util/roslaunch_util.py

This file was deleted.

10 changes: 0 additions & 10 deletions bazel_ros2_rules/ros2/tools/roslaunch_util/setup.py

This file was deleted.

37 changes: 29 additions & 8 deletions ros2_example_bazel_installed/ros2_example_apps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
# vi: set ft=python :

load("@ros2//:ros_cc.bzl", "ros_cc_binary", "ros_cc_test")
load("@ros2//:ros_py.bzl", "ros_py_binary", "ros_py_test")
load("@ros2//:ros_py.bzl", "ros_launch")
load("@ros2//:ros_py.bzl", "ros_launch", "ros_py_binary", "ros_py_test")
load("@ros2//:rosidl.bzl", "rosidl_interfaces_group")
load("//tools:cmd_test.bzl", "cmd_test")

Expand Down Expand Up @@ -206,6 +205,7 @@ ros_py_binary(
ros_py_binary(
name = "eg_talker",
srcs = ["roslaunch_eg_nodes/eg_talker.py"],
data = ["@ros2//:rmw_cyclonedds_cpp_cc"],
deps = [
"@ros2//:rclpy_py",
"@ros2//:std_msgs_py",
Expand All @@ -215,29 +215,50 @@ ros_py_binary(
ros_cc_binary(
name = "eg_listener",
srcs = ["roslaunch_eg_nodes/eg_listener.cpp"],
data = ["@ros2//:rmw_cyclonedds_cpp_cc"],
deps = [
"@ros2//:rclcpp_cc",
"@ros2//:std_msgs_cc",
],
)

# See bazel_ros2_rules/ros2/README.md, Launch Files, for notes on
# features and limitations.

# Uses a python launch file to spawn the talker and listener.
workspace_name = "ros2_example_bazel_installed"

ros_launch(
name = "roslaunch_eg_py",
launch_file = "eg_launch.py",
node_targets = [
":eg_talker",
data = [
":eg_listener",
":eg_talker",
],
launch_file = "eg_launch.py",
workspace_name = workspace_name,
)

# Uses an xml launch file to spawn the talker and listener.
ros_launch(
name = "roslaunch_eg_xml",
launch_file = "eg_launch.xml",
node_targets = [
":eg_talker",
data = [
":eg_listener",
":eg_talker",
],
launch_file = "eg_launch.xml",
workspace_name = workspace_name,
)

ros_py_test(
name = "roslaunch_eg_test",
srcs = ["test/roslaunch_eg_test.py"],
data = [
":roslaunch_eg_py",
":roslaunch_eg_xml",
],
main = "test/roslaunch_eg_test.py",
deps = [
"@ros2//resources/bazel_ros_env:bazel_ros_env_py",
],
)

Expand Down
14 changes: 11 additions & 3 deletions ros2_example_bazel_installed/ros2_example_apps/eg_launch.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
from bazel_ros_env import Rlocation
from launch import LaunchDescription
from roslaunch_util import ExecuteBazelTarget
from launch.actions import ExecuteProcess


def generate_launch_description():
# See bazel_ros2_rules/ros2/README.md, Launch Files, for notes on
# features and limitations.
prefix = "ros2_example_bazel_installed/ros2_example_apps"
talker_bin = Rlocation(f"{prefix}/eg_talker")
listener_bin = Rlocation(f"{prefix}/eg_listener")

return LaunchDescription([
# Running a talker written in python.
ExecuteBazelTarget('eg_talker'),
ExecuteProcess(cmd=[talker_bin]),
# Running a listener written in cpp.
ExecuteBazelTarget('eg_listener'),
ExecuteProcess(cmd=[listener_bin]),
])
8 changes: 6 additions & 2 deletions ros2_example_bazel_installed/ros2_example_apps/eg_launch.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
<launch>
<execute_bazel_target target="eg_talker"/>
<execute_bazel_target target="eg_listener"/>
<!--
See bazel_ros2_rules/ros2/README.md, Launch Files, for notes on
features and limitations.
-->
<executable cmd="ros2_example_apps/eg_talker" output="screen"/>
<executable cmd="ros2_example_apps/eg_listener" output="screen"/>
</launch>
Loading

0 comments on commit ef17e8f

Please sign in to comment.