Skip to content

Commit c20edf8

Browse files
Improve ros_launch support for launch files
1 parent f8ce5eb commit c20edf8

5 files changed

Lines changed: 157 additions & 66 deletions

File tree

bazel_ros2_rules/lib/ament_index.bzl

Lines changed: 102 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -46,100 +46,140 @@ ament_index_prefixes = aspect(
4646
Recursively aggregates AmentIndex prefixes from dependencies into one provider.
4747
"""
4848

49-
def _ament_index_share_files_impl(ctx):
50-
# declare that a "package" with the given name exists
49+
def _ament_index_files_impl(ctx):
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
69-
for file in src.files.to_list():
70-
sp = file.short_path
71-
if sp.startswith(ctx.attr.strip_prefix):
72-
sp = sp[len(ctx.attr.strip_prefix):]
61+
if ctx.attr.subdirectory == "lib":
62+
exe_file = src.files_to_run.executable
7363
symlink_path = paths.join(
7464
ctx.attr.prefix,
75-
"share",
65+
"lib",
7666
ctx.attr.package_name,
77-
sp,
67+
exe_file.basename,
7868
)
79-
runfiles_symlinks[symlink_path] = file
69+
root_symlinks[symlink_path] = exe_file
70+
else:
71+
for file in src.files.to_list():
72+
sp = file.short_path
73+
if sp.startswith(ctx.attr.strip_prefix):
74+
sp = sp[len(ctx.attr.strip_prefix):]
75+
symlink_path = paths.join(
76+
ctx.attr.prefix,
77+
ctx.attr.subdirectory,
78+
ctx.attr.package_name,
79+
sp,
80+
)
81+
root_symlinks[symlink_path] = file
8082

8183
return [
8284
AmentIndex(prefix = ctx.attr.prefix),
8385
DefaultInfo(
84-
runfiles = ctx.runfiles(root_symlinks = runfiles_symlinks),
86+
runfiles = ctx.runfiles(root_symlinks = root_symlinks),
8587
),
8688
]
8789

88-
ament_index_share_files = rule(
90+
_ament_index_files = rule(
8991
attrs = dict(
9092
package_name = attr.string(mandatory = True),
9193
srcs = attr.label_list(
9294
mandatory = True,
9395
allow_empty = False,
9496
allow_files = True,
9597
),
98+
subdirectory = attr.string(default = "share"),
9699
# A prefix is required because the shim can't prepend the runfiles
97100
# root to AMENT_PREFIX_PATH
98101
prefix = attr.string(default = "ament_index_share_files"),
99102
strip_prefix = attr.string(default = ""),
100103
),
101-
implementation = _ament_index_share_files_impl,
104+
implementation = _ament_index_files_impl,
102105
output_to_genfiles = True,
103106
provides = [AmentIndex],
104107
)
105-
"""
106-
Creates an ament resource index and share/ directory for a single package.
107-
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.
114-
115-
d = ament_index_cpp::get_package_share_directory("package_name")
116-
file = join(d, "short_path of file")
117-
118-
Note that this works only when bazel creates runfiles links,
119-
and not just a *.runfiles_manifest (which it might do on an unsupported
120-
platform like Windows, or with `--nobuild_runfile_links`).
121-
122-
Note that the same `package_name` for ROS 2 packages should not be used
123-
in multiple different rules spread across multiple Bazel packages.
124-
This is called "overriding", and it's not currently supported.
125-
Currently if the two calls to `ament_index_share_files` also have
126-
the same value for "prefix", then a target depending on both may
127-
be able to find resources from both rules, but that behavior is not
128-
guaranteed.
129-
TODO(sloretz) detect and error when a target is given two ament indexes
130-
with the same package.
131-
132-
Args:
133-
package_name: name of a ROS 2 package to which these share files belong
134-
srcs: files to put into the share directory
135-
prefix: optional prefix to give to the generated runfiles.
136-
strip_prefix: optional prefix to strip from the short_path of the files
137-
138-
Provides:
139-
AmentIndex: a prefix path where the ament resource index was generated.
140-
DefaultInfo: folders and symlinked files to add to the runfiles of a
141-
dependent target.
142-
143-
See https://github.com/ament/ament_cmake/blob/master/ament_cmake_core/doc/resource_index.md
144-
for further reference.
145-
""" # noqa
108+
109+
def ament_index_share_files(name, package_name, srcs, prefix = "ament_index_share_files", strip_prefix = "", **kwargs):
110+
"""Creates an ament resource index and share/ directory for a single package.
111+
112+
Files in `srcs` will be added to `<prefix>/share/<package_name>/`.
113+
A target depending on this one will be able to access the files in the
114+
share directory using the package's share directory joined with the
115+
files they expect.
116+
117+
d = ament_index_cpp::get_package_share_directory("package_name")
118+
file = join(d, "short_path of file")
119+
120+
Note that this works only when bazel creates runfiles links,
121+
and not just a *.runfiles_manifest (which it might do on an unsupported
122+
platform like Windows, or with `--nobuild_runfile_links`).
123+
124+
Note that the same `package_name` for ROS 2 packages should not be used
125+
in multiple different rules spread across multiple Bazel packages.
126+
This is called "overriding", and it's not currently supported.
127+
Currently if the two calls to `ament_index_share_files` also have
128+
the same value for "prefix", then a target depending on both may
129+
be able to find resources from both rules, but that behavior is not
130+
guaranteed.
131+
TODO(sloretz) detect and error when a target is given two ament indexes
132+
with the same package.
133+
134+
Args:
135+
package_name: name of a ROS 2 package to which these share files belong
136+
srcs: files to put into the share directory
137+
prefix: optional prefix to give to the generated runfiles.
138+
strip_prefix: optional prefix to strip from the short_path of the files
139+
140+
Provides:
141+
AmentIndex: a prefix path where the ament resource index was generated.
142+
DefaultInfo: folders and symlinked files to add to the runfiles of a
143+
dependent target.
144+
145+
See https://github.com/ament/ament_cmake/blob/master/ament_cmake_core/doc/resource_index.md
146+
for further reference.
147+
""" # noqa
148+
_ament_index_files(
149+
name = name,
150+
package_name = package_name,
151+
srcs = srcs,
152+
subdirectory = "share",
153+
prefix = prefix,
154+
strip_prefix = strip_prefix,
155+
**kwargs
156+
)
157+
158+
def ament_index_executables(name, package_name, srcs, prefix = "ament_index_share_files", **kwargs):
159+
"""Creates an ament resource index that exposes Bazel-built executables under
160+
`<prefix>/lib/<package_name>/`, enabling launch_ros.actions.Node(package=...,
161+
executable=...) to find them without a colcon install space.
162+
163+
When added to the `data=` of a ros_launch() target, the shim injects
164+
`<prefix>` into AMENT_PREFIX_PATH so ROS 2 libexec lookup succeeds:
165+
166+
launch_ros.actions.Node(package="my_pkg", executable="my_node")
167+
# resolves to: <prefix>/lib/my_pkg/my_node ✓
168+
169+
Args:
170+
package_name: the ROS 2 package name (must match Node(package=...))
171+
srcs: Bazel labels of the executable targets to expose
172+
prefix: optional directory prefix for the generated runfiles tree
173+
174+
Provides:
175+
AmentIndex: a prefix path where the ament resource index was generated.
176+
DefaultInfo: symlinks and marker files for all executables.
177+
""" # noqa
178+
_ament_index_files(
179+
name = name,
180+
package_name = package_name,
181+
srcs = srcs,
182+
subdirectory = "lib",
183+
prefix = prefix,
184+
**kwargs
185+
)

bazel_ros2_rules/lib/private/ros_py.bzl

Lines changed: 20 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_executables",
6+
)
37
load(
48
"@bazel_ros2_rules//lib:kwargs.bzl",
59
"filter_to_only_common_kwargs",
@@ -198,6 +202,11 @@ 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+
# Optional ROS 2 package name. When set, an ament_index_executables
206+
# target is created from the data= labels, enabling
207+
# launch_ros.actions.Node(package=..., executable=...) to find
208+
# Bazel-built binaries without a colcon install space.
209+
package_name = None,
201210
**kwargs):
202211
main = "{}_roslaunch_main.py".format(name)
203212
launch_respath = _make_respath(launch_file, workspace_name)
@@ -218,6 +227,17 @@ def ros_launch(
218227
REPOSITORY_ROOT + ":ros2",
219228
],
220229
)
230+
231+
if package_name:
232+
index_target = "_{}_ament_index".format(name)
233+
ament_index_executables(
234+
name = index_target,
235+
package_name = package_name,
236+
srcs = data,
237+
visibility = ["//visibility:private"],
238+
)
239+
data = data + [":" + index_target]
240+
221241
data = data + [launch_file]
222242

223243
if "tags" not in kwargs:

ros2_example_bazel_installed/ros2_example_apps/BUILD.bazel

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,8 @@ 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.
236+
# Uses a python launch file to spawn the talker and listener using
237+
# ExecuteProcess to run the node binaries.
237238
workspace_name = "ros2_example_bazel_installed"
238239

239240
ros_launch(
@@ -242,7 +243,7 @@ ros_launch(
242243
":eg_listener",
243244
":eg_talker",
244245
],
245-
launch_file = "eg_launch.py",
246+
launch_file = "roslaunch_eg.py",
246247
workspace_name = workspace_name,
247248
)
248249

@@ -257,6 +258,19 @@ ros_launch(
257258
workspace_name = workspace_name,
258259
)
259260

261+
# Uses launch_ros.actions.Node, the natural ROS 2 pattern, by registering
262+
# the Bazel-built binaries in a fake ament prefix via package_name=.
263+
ros_launch(
264+
name = "eg_launch_with_action_node",
265+
package_name = "ros2_example_apps",
266+
data = [
267+
":eg_listener",
268+
":eg_talker",
269+
],
270+
launch_file = "eg_launch_with_action_node.py",
271+
workspace_name = workspace_name,
272+
)
273+
260274
ros_py_test(
261275
name = "roslaunch_eg_test",
262276
srcs = ["test/roslaunch_eg_test.py"],
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from launch import LaunchDescription
2+
import launch_ros.actions
3+
4+
5+
def generate_launch_description():
6+
return LaunchDescription(
7+
[
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+
),
16+
]
17+
)

ros2_example_bazel_installed/ros2_example_apps/eg_launch.py renamed to ros2_example_bazel_installed/ros2_example_apps/roslaunch_eg.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ def generate_launch_description():
88
# features and limitations.
99
r = runfiles.Create()
1010
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")
11+
talker_bin = r.Rlocation(f"{prefix}/eg_talker", source_repo="")
12+
listener_bin = r.Rlocation(f"{prefix}/eg_listener", source_repo="")
1313

1414
return LaunchDescription(
1515
[

0 commit comments

Comments
 (0)