Skip to content

Commit 9cdd9bf

Browse files
Improve ros_launch support for launch files
1 parent f8ce5eb commit 9cdd9bf

4 files changed

Lines changed: 77 additions & 41 deletions

File tree

bazel_ros2_rules/lib/ament_index.bzl

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,17 @@ Recursively aggregates AmentIndex prefixes from dependencies into one provider.
4747
"""
4848

4949
def _ament_index_share_files_impl(ctx):
50-
# declare that a "package" with the given name exists
5150
package_marker_path = paths.join(
5251
ctx.attr.prefix,
5352
"share/ament_index/resource_index/packages/",
5453
ctx.attr.package_name,
5554
)
5655
package_marker_out = ctx.actions.declare_file(package_marker_path)
57-
ctx.actions.write(
58-
output = package_marker_out,
59-
content = "",
60-
)
56+
ctx.actions.write(output = package_marker_out, content = "")
6157

62-
# Symlink sources into the share directory
63-
runfiles_symlinks = {
64-
package_marker_path: package_marker_out,
65-
}
58+
root_symlinks = {package_marker_path: package_marker_out}
6659

6760
for src in ctx.attr.srcs:
68-
# src is a target that could have multiple files
6961
for file in src.files.to_list():
7062
sp = file.short_path
7163
if sp.startswith(ctx.attr.strip_prefix):
@@ -76,21 +68,36 @@ def _ament_index_share_files_impl(ctx):
7668
ctx.attr.package_name,
7769
sp,
7870
)
79-
runfiles_symlinks[symlink_path] = file
71+
root_symlinks[symlink_path] = file
72+
73+
for exe in ctx.attr.executables:
74+
exe_file = exe.files_to_run.executable
75+
if exe_file == None:
76+
continue
77+
symlink_path = paths.join(
78+
ctx.attr.prefix,
79+
"lib",
80+
ctx.attr.package_name,
81+
exe_file.basename,
82+
)
83+
root_symlinks[symlink_path] = exe_file
8084

8185
return [
8286
AmentIndex(prefix = ctx.attr.prefix),
8387
DefaultInfo(
84-
runfiles = ctx.runfiles(root_symlinks = runfiles_symlinks),
88+
runfiles = ctx.runfiles(root_symlinks = root_symlinks),
8589
),
8690
]
8791

8892
ament_index_share_files = rule(
8993
attrs = dict(
9094
package_name = attr.string(mandatory = True),
9195
srcs = attr.label_list(
92-
mandatory = True,
93-
allow_empty = False,
96+
allow_empty = True,
97+
allow_files = True,
98+
),
99+
executables = attr.label_list(
100+
allow_empty = True,
94101
allow_files = True,
95102
),
96103
# A prefix is required because the shim can't prepend the runfiles
@@ -103,14 +110,17 @@ ament_index_share_files = rule(
103110
provides = [AmentIndex],
104111
)
105112
"""
106-
Creates an ament resource index and share/ directory for a single package.
113+
Creates an ament resource index for a single ROS 2 package.
114+
115+
Files in `srcs` are symlinked under `<prefix>/share/<package_name>/`,
116+
enabling ament_index lookups such as `get_package_share_directory()`.
117+
118+
Executable targets in `executables` are symlinked under
119+
`<prefix>/lib/<package_name>/`, enabling
120+
`launch_ros.actions.Node(package=..., executable=...)` to find Bazel-built
121+
binaries without a colcon install space.
107122
108-
This creates both a package marker for a package, and a share directory
109-
for that package.
110-
Files in `srcs` will be added to `share/package_name`.
111-
A target depending on this one will be able to access the files in the
112-
share directory using the package's share directory joined with the
113-
files they expect.
123+
Both can be used together in a single rule invocation.
114124
115125
d = ament_index_cpp::get_package_share_directory("package_name")
116126
file = join(d, "short_path of file")
@@ -130,10 +140,11 @@ TODO(sloretz) detect and error when a target is given two ament indexes
130140
with the same package.
131141
132142
Args:
133-
package_name: name of a ROS 2 package to which these share files belong
143+
package_name: name of a ROS 2 package
134144
srcs: files to put into the share directory
145+
executables: executable targets to expose under lib/
135146
prefix: optional prefix to give to the generated runfiles.
136-
strip_prefix: optional prefix to strip from the short_path of the files
147+
strip_prefix: optional prefix to strip from the short_path of srcs files
137148
138149
Provides:
139150
AmentIndex: a prefix path where the ament resource index was generated.

bazel_ros2_rules/lib/private/ros_py.bzl

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# -*- python -*-
22

3+
load(
4+
"@bazel_ros2_rules//lib:ament_index.bzl",
5+
"ament_index_share_files",
6+
)
37
load(
48
"@bazel_ros2_rules//lib:kwargs.bzl",
59
"filter_to_only_common_kwargs",
@@ -198,6 +202,16 @@ def ros_launch(
198202
# runfiles.py to use "this repository" in a way that doesn't require
199203
# bespoke information.
200204
workspace_name = None,
205+
# dict mapping ROS 2 package name to a list of executable
206+
# targets to expose via the ament resource index, enabling
207+
# launch_ros.actions.Node(package=..., executable=...) to find
208+
# Bazel-built binaries without a colcon install space.
209+
# Example:
210+
# executables = {
211+
# "my_pkg": [":talker", ":listener"],
212+
# "other_pkg": ["//other_pkg:some_node"],
213+
# }
214+
executables = {},
201215
**kwargs):
202216
main = "{}_roslaunch_main.py".format(name)
203217
launch_respath = _make_respath(launch_file, workspace_name)
@@ -218,6 +232,17 @@ def ros_launch(
218232
REPOSITORY_ROOT + ":ros2",
219233
],
220234
)
235+
236+
for pkg_name, pkg_executables in executables.items():
237+
index_target = "_{}_ament_index_{}".format(name, pkg_name)
238+
ament_index_share_files(
239+
name = index_target,
240+
package_name = pkg_name,
241+
executables = pkg_executables,
242+
visibility = ["//visibility:private"],
243+
)
244+
data = data + pkg_executables + [":" + index_target]
245+
221246
data = data + [launch_file]
222247

223248
if "tags" not in kwargs:

ros2_example_bazel_installed/ros2_example_apps/BUILD.bazel

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -233,15 +233,19 @@ ros_cc_binary(
233233
# See bazel_ros2_rules/ros2/README.md, Launch Files, for notes on
234234
# features and limitations.
235235

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

238+
# Uses a python launch file to spawn the talker and listener.
239+
# Note: it also uses launch_ros.actions.Node, the natural ROS 2 pattern,
240+
# by registering the Bazel-built binaries in a fake ament prefix.
239241
ros_launch(
240242
name = "roslaunch_eg_py",
241-
data = [
242-
":eg_listener",
243-
":eg_talker",
244-
],
243+
executables = {
244+
"ros2_example_apps": [
245+
":eg_listener",
246+
":eg_talker",
247+
],
248+
},
245249
launch_file = "eg_launch.py",
246250
workspace_name = workspace_name,
247251
)
Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,17 @@
11
from launch import LaunchDescription
2-
from launch.actions import ExecuteProcess
3-
from python.runfiles import runfiles
2+
import launch_ros.actions
43

54

65
def generate_launch_description():
7-
# See bazel_ros2_rules/ros2/README.md, Launch Files, for notes on
8-
# features and limitations.
9-
r = runfiles.Create()
10-
prefix = "ros2_example_bazel_installed/ros2_example_apps"
11-
talker_bin = r.Rlocation(f"{prefix}/eg_talker")
12-
listener_bin = r.Rlocation(f"{prefix}/eg_listener")
13-
146
return LaunchDescription(
157
[
16-
# Running a talker written in python.
17-
ExecuteProcess(cmd=[talker_bin]),
18-
# Running a listener written in cpp.
19-
ExecuteProcess(cmd=[listener_bin]),
8+
launch_ros.actions.Node(
9+
package="ros2_example_apps",
10+
executable="eg_talker",
11+
),
12+
launch_ros.actions.Node(
13+
package="ros2_example_apps",
14+
executable="eg_listener",
15+
),
2016
]
2117
)

0 commit comments

Comments
 (0)