Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 33 additions & 22 deletions bazel_ros2_rules/lib/ament_index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,15 @@ Recursively aggregates AmentIndex prefixes from dependencies into one provider.
"""

def _ament_index_share_files_impl(ctx):
# declare that a "package" with the given name exists
package_marker_path = paths.join(
ctx.attr.prefix,
"share/ament_index/resource_index/packages/",
ctx.attr.package_name,
)
package_marker_out = ctx.actions.declare_file(package_marker_path)
ctx.actions.write(
output = package_marker_out,
content = "",
)
ctx.actions.write(output = package_marker_out, content = "")

# Symlink sources into the share directory
runfiles_symlinks = {
package_marker_path: package_marker_out,
}
root_symlinks = {package_marker_path: package_marker_out}

for src in ctx.attr.srcs:
for file in depset(transitive = [
Expand All @@ -78,23 +71,37 @@ def _ament_index_share_files_impl(ctx):
ctx.attr.package_name,
sp,
)
runfiles_symlinks[symlink_path] = file
root_symlinks[symlink_path] = file

for executable in ctx.attr.executables:
if executable.files_to_run.executable == None:
fail("{} is not an executable target".format(executable.label))
symlink_path = paths.join(
ctx.attr.prefix,
"lib",
ctx.attr.package_name,
executable.files_to_run.executable.basename,
)
root_symlinks[symlink_path] = executable.files_to_run.executable

return [
AmentIndex(prefix = ctx.attr.prefix),
DefaultInfo(
runfiles = ctx.runfiles(root_symlinks = runfiles_symlinks),
runfiles = ctx.runfiles(root_symlinks = root_symlinks),
),
]

ament_index_share_files = rule(
attrs = dict(
package_name = attr.string(mandatory = True),
srcs = attr.label_list(
mandatory = True,
allow_empty = False,
allow_empty = True,
allow_files = True,
),
executables = attr.label_list(
allow_empty = True,
allow_files = False,
),
# A prefix is required because the shim can't prepend the runfiles
# root to AMENT_PREFIX_PATH
prefix = attr.string(default = "ament_index_share_files"),
Expand All @@ -105,14 +112,17 @@ ament_index_share_files = rule(
provides = [AmentIndex],
)
"""
Creates an ament resource index and share/ directory for a single package.
Creates an ament resource index for a single ROS 2 package.

Files in `srcs` are symlinked under `<prefix>/share/<package_name>/`,
enabling ament_index lookups such as `get_package_share_directory()`.

Executable targets in `executables` are symlinked under
`<prefix>/lib/<package_name>/`, enabling
`launch_ros.actions.Node(package=..., executable=...)` to find Bazel-built
binaries without a colcon install space.

This creates both a package marker for a package, and a share directory
for that package.
Files in `srcs` will be added to `share/package_name`.
A target depending on this one will be able to access the files in the
share directory using the package's share directory joined with the
files they expect.
Both can be used together in a single rule invocation.

d = ament_index_cpp::get_package_share_directory("package_name")
file = join(d, "short_path of file")
Expand All @@ -132,11 +142,12 @@ TODO(sloretz) detect and error when a target is given two ament indexes
with the same package.

Args:
package_name: name of a ROS 2 package to which these share files belong
package_name: name of a ROS 2 package
srcs: targets whose files are placed into the share directory. Both
direct files and transitive runfiles are included.
executables: executable targets to expose under lib/.
prefix: optional prefix to give to the generated runfiles.
strip_prefix: optional prefix to strip from the short_path of the files
strip_prefix: optional prefix to strip from the short_path of srcs files.

Provides:
AmentIndex: a prefix path where the ament resource index was generated.
Expand Down
42 changes: 33 additions & 9 deletions bazel_ros2_rules/lib/private/ros_py.bzl
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# -*- python -*-

load(
"@bazel_ros2_rules//lib:ament_index.bzl",
"ament_index_share_files",
)
load(
"@bazel_ros2_rules//lib:kwargs.bzl",
"filter_to_only_common_kwargs",
Expand Down Expand Up @@ -174,12 +178,7 @@ 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
repo = workspace_name if workspace_name != None else native.module_name()
pkg = native.package_name()
if pkg != "":
pieces = [repo, pkg, relpath]
Expand All @@ -194,10 +193,22 @@ def ros_launch(
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.
# Optional: overrides the Bzlmod module name as the ament package name.
# Only needed when the desired package name differs from the module name.
workspace_name = None,
# Executable targets to expose via the ament resource index under
# lib/<package_name>/, enabling
# launch_ros.actions.Node(package=<package_name>, executable=...) to
# find Bazel-built binaries without a colcon install space.
# Example:
# executables = [":talker", ":listener"],
executables = [],
# Data file targets to expose via the ament resource index under
# share/<package_name>/, enabling FindPackageShare(<package_name>)
# and get_package_share_directory(<package_name>) to locate them.
# Example:
# share = ["//my_pkg:config_files"],
share = [],
**kwargs):
main = "{}_roslaunch_main.py".format(name)
launch_respath = _make_respath(launch_file, workspace_name)
Expand All @@ -218,6 +229,19 @@ def ros_launch(
REPOSITORY_ROOT + ":ros2",
],
)

if executables or share:
package_name = workspace_name if workspace_name != None else native.module_name()
index_target = "_{}_ament_index".format(name)
ament_index_share_files(
name = index_target,
package_name = package_name,
executables = executables,
srcs = share,
visibility = ["//visibility:private"],
)
data = data + executables + share + [":" + index_target]

data = data + [launch_file]

if "tags" not in kwargs:
Expand Down
8 changes: 5 additions & 3 deletions ros2_example_bazel_installed/ros2_example_apps/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,14 @@ ros_cc_binary(
# 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"

# Uses a python launch file to spawn the talker and listener.
# Note: it also uses launch_ros.actions.Node, the natural ROS 2 pattern,
# by registering the Bazel-built binaries in a fake ament prefix.
ros_launch(
name = "roslaunch_eg_py",
data = [
executables = [
":eg_listener",
":eg_talker",
],
Expand All @@ -249,7 +251,7 @@ ros_launch(
# Uses an xml launch file to spawn the talker and listener.
ros_launch(
name = "roslaunch_eg_xml",
data = [
executables = [
":eg_listener",
":eg_talker",
],
Expand Down
22 changes: 9 additions & 13 deletions ros2_example_bazel_installed/ros2_example_apps/eg_launch.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from python.runfiles import runfiles
import launch_ros.actions


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

return LaunchDescription(
[
# Running a talker written in python.
ExecuteProcess(cmd=[talker_bin]),
# Running a listener written in cpp.
ExecuteProcess(cmd=[listener_bin]),
launch_ros.actions.Node(
package="ros2_example_bazel_installed",
executable="eg_talker",
),
launch_ros.actions.Node(
package="ros2_example_bazel_installed",
executable="eg_listener",
),
]
)
Loading