-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Execute process refactor #215
base: rolling
Are you sure you want to change the base?
Conversation
This has taken longer to get in than I would like, but such is the way of things sometimes. A few questions/notes as I was getting this together:
|
name, value = param.evaluate(context) | ||
return f'{name}:={yaml.dump(value)}' | ||
|
||
def prepare(self, context: LaunchContext, executable: Executable) -> None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain seems like traits
are ignored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possibly so; those are going to come into play more strongly with the next couple of PRs/commits related to this as we get into composable/lifecycle concepts. At the moment, they were just placeholders with no functionality. I'll see about adding in the appropriate calls. As you noted elsewhere, at the moment they call for an Action
, whereas here I have Executable
instead. I'll need to sanity check what's really necessary, and may have to add more plumbing depending on that result.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes! A review of the call chain would be healthy.
""" | ||
pass | ||
|
||
def prepare(self, node, context: LaunchContext, action: Action): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain nit:
def prepare(self, node, context: LaunchContext, action: Action): | |
def prepare(self, node: Node, context: LaunchContext, action: Action): |
plus necessary imports (dealing with any circularity)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, what action
? And where are you going to grab that from?
I'd say
BTW check your DCO! |
Overall, I think this is going in the right direction. I'd revisit the architecture, make sure that entity setup via descriptions and/or traits is clean (no crust like redundant arguments, no issues like unnecessary coupling). I'll try to do a deeper review later this week to both feature branches. |
56a5946
to
e8b2dcb
Compare
@hidmic And here's the corresponding changes on this side. I've tried to simplify and clean up some of the argument lists. I was trying to remove some of the extra layers of calls going on, but had to reintroduce one to make the unit tests happy; they wanted certain parts of the substitutions to happen, but I couldn't let the full preparation go through or it would have needed a lot more mock structure in the tests than I thought was really warranted. If this looks reasonable, I can go ahead and look at the Composable/Lifecycle stuff next. If there are other things you want to see here first, though, please let me know. The more specific the better. :) |
@hidmic Just wanted to check in and see if there's anything else I can do to help on this at the moment. |
0742de2
to
c648bbc
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain my apologies for not circling back sooner. Just did another pass. Check your failing PR job!
execute_process_logger.warning( | ||
'there are now at least {} nodes with the name {} created within this ' | ||
'launch context'.format(node_name_count, self.node_name) | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain nit: missing newline
|
||
self.__substitutions_performed = False | ||
def prepare(self, context: LaunchContext): | ||
self.__node_desc.prepare(context, self.__ros_exec, self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain why preparing the node description here? Won't ExecuteLocal.prepare()
invoke RosExecutable.prepare()
, which in turn will invoke NodeDescription.prepare()
? That'd make sense to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On reflection, I believe you're correct. I think this was probably added earlier in the development cycle, before the RosExecutable
portion came in.
**kwargs | ||
) -> None: | ||
""" | ||
Construct an Node description. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain nit:
Construct an Node description. | |
Construct a Node description. |
Construct an Node description. | ||
|
||
Many arguments are passed eventually to | ||
:class:`launch.actions.ExecuteProcess`, so see the documentation of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain did you mean to launch.actions.ExecuteLocal
? At any rate, it shouldn't specify an executor. Perhaps documentation needs a revisit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely was a copy/paste leftover from something. I've removed the clause entirely. I think the rest of it is okay, but if there are other deficiencies please let me know.
@@ -51,5 +51,5 @@ def test_node_name(): | |||
namespace='my_ns', | |||
) | |||
lc = LaunchContext() | |||
node_object._perform_substitutions(lc) | |||
node_object._Node__node_desc._perform_substitutions(lc, []) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain is it necessary to access a "private" attribute like this? Isn't the functionality under test observable via public API? Same everywhere else below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It actually wasn't (easily) available without making some assumptions about data types that I didn't want to make. I've added a new getter on the actions.Node
for ros_exec
, which will allow us to easily get access to the ROS-style executable, rather than the more generic Executable
description, which didn't provide the things we needed.
if self.__node_name is not None: | ||
ros_specific_arguments['name'] = '__node:={}'.format(self.__expanded_node_name) | ||
if self.__expanded_node_namespace != '': | ||
ros_specific_arguments['ns'] = '__ns:={}'.format(self.__expanded_node_namespace) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain meta: this will probably need a revisit. A blanket __node:=
remapping will apply to all nodes in the same executable. Probably not what a user would intend in this case. Same for __ns:=
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is probably a carry-over from the earlier design which assumed one Node
per executable process. I'm looking at the docs here (https://docs.ros.org/en/foxy/Guides/Node-arguments.html) and it looks like I need to prefix things with the "old" name of the node. That's something that I don't believe we've captured in the descriptions.Node
structure to this point. I'm adding a new parameter to capture original_node_name
; if it's not set, there's nothing we can do but let the global apply. (This will actually work out okay, though, because I think it maintains current functionality for backward-compatibility.)
I'm now having another thought, though, which is related to this. For "normal" (read: Not node name/namespace) parameters, the docs show the ability to prefix individual parameters with the (new) node name. I'm guessing this is something we'll need to bake in at this layer as well? How does that come into play if we're passed or creating parameter dictionary files? Not sure how far down this rabbit hole is the responsibility of this layer, vs telling the developer they need to specify things with the appropriate prefix. Would love your thoughts on which parts to tackle internally.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll start by saying that some of these rough edges when dealing with remapping predate your work, and that I'm not pushing to get them all addressed here.
I don't think launch
should be mutating parameter files to propagate a remapping and such. That's on the underlying executable's plate at best. IMHO launch
should simply provide a way to express the intent to remap.
For executables that bear many nodes, the current command line interface allows both node-specific and blanket configurations. To support the latter, we can simply let the ROSExecutable
action take extra command line arguments. To support the former, Node
descriptions have to bear the original name, that's right. We can allow a single Node
description to not specify it for convenience, in which case I think the ROSExecutable
action should enforce only one such description is given.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I ended up trying to make this more complicated than it needed to be. If we're setting aside the dictionary files, then in all cases we know what the applied node name should be by the time we're setting up command line parameters, so I can just add that as the normal way of doing it.
Here's a question on parameters, though. Is there such a thing as a node-specific (non-remapping) parameter? If so, I don't see it in the sample documentation I've been referring to.
Ignore the previous part. I confused myself. I think we're good.
pass | ||
|
||
def prepare(self, node: 'Node', context: LaunchContext, action: Action): | ||
"""Perform any actions necessary to prepare the node for execution.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@roger-strain missing pass
after docstring?
About to push up a new version that includes several of the changes discussed above. Something about my local unit test setup isn't capturing things the same as the build server does, so I may end up having to do a few passes to get that sorted, apologies for that. (If you have any tips on setting up a good environment to make sure the tests are working locally, I'd love to hear it.) |
Oh, just realized some of the failures here may be because this PR depends on the PR over in |
b431784
to
a68f1d8
Compare
@hidmic Hey, just wanted to check back in. I know we're still pending the I've been considering moving forward with the Composable and Lifecycle portions of the refactor, but didn't want to get too much pending at once. |
a68f1d8
to
861b2f9
Compare
@hidmic I've merged our changes back on top of master but Jenkins is running into errors because it doesn't seem to have the changes that were merged into the launch repository. Is there something I need to do to tell it to update? Also, the ros2launch_security repo will require a couple changes to work with the refactored code. I can submit a PR, but I thought this might warrant a bit of discussion first and want to make sure the discussion happens here instead of being split across two PR's. The ros2launch_security plugin was based on an extension to launch_ros called NodeActionExtension. However, this refactor deprecates the Node action in favor of simply passing a RosExecutable description to a standard ExecuteLocal action. The RosExecutable description contains one or more Node descriptions. I've moved NodeActionExtension into the new Node description class and changed the function signature of I noticed that the NodeActionExtension interface looks very similar to the NodeTrait description we included in anticipation of adding support for things like composable nodes and lifecycle nodes that might need additional setup steps before being fully launched. Would it be worth wrapping the two concepts (node traits and node action extensions) into one? Security as a node trait makes some sense, but I'm not so sure about composable/lifecycle nodes as plugins/extensions. Still, the interfaces are similar enough (almost identical) that I figure it's worth mentioning. |
No, we need to do a new release of |
1cff138
to
8ba498f
Compare
Ideally, consolidating would be best. Those extensions are plugin-based and implicit though. Not sure what the best path forward is here. Perhaps some traits can also be plugin-based and implicit? I'm open to ideas. FYI @gbiggs. |
@ros-pull-request-builder retest this please. |
@mlanting there's still a bunch of test failures in that Rpr job. |
@hidmic Yeah, I still had a few I was working through but wanted to get some fixes pushed up before I left for vacation. I've fixed a few more of them. **The remaining failures that I'm seeing are all failing when the testing infrastructure tries to call tests in the It's giving type errors along the lines of **Actually, i seem to be getting a few intermittent failures on some of these still, so i'll keep looking into them. |
I created a pull request (ros2/launch#587) with a fix for a test function in the launch repo to address the remaining fix. For an intermittent timer failure I was seeing, I simply increased the tolerance a bit. The tolerance was originally 0.1 seconds. The largest difference I saw in the few times I occurred was 0.37 seconds, so I just increased the tolerance to 0.5. While running the tests repeatedly trying to get the timer to fail, I also saw the error described here on a couple occasions: #280. I did not make any attempts to address it, but I do want to note that the issue can apparently happen on Ubuntu and is not just limited to Windows. |
@hidmic friendly ping |
1 similar comment
@hidmic friendly ping |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlantting did another pass. I'm sorry for the delay. I've been swamped.
# OPSEC #4584. | ||
# | ||
# Delivered to the U.S. Government with Unlimited Rights, as defined in DFARS | ||
# Part 252.227-7013 or 7014 (Feb 2014). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting there was a discussion about this already in ros2/launch#454. I'd be best if we can follow suit (by removing it).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct, I left this in by mistake. I'll remove it.
self.__substitutions_performed = False | ||
|
||
self.__logger = launch.logging.get_logger(__name__) | ||
self.__node_desc = NodeDescription(node_name=name, node_namespace=namespace, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting nit: why self.__node_desc
when we can self.__ros_exec.nodes[0]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This class only exists for backward compatibility with the old node action which really only supported a single node and this was just an artifact of the way we were thinking about the class when it was written. I've left it as is, but I don't have strong feelings either way and can change it if you'd prefer the other way.
self.__node_desc = NodeDescription(node_name=name, node_namespace=namespace, | ||
parameters=parameters, remappings=remappings) | ||
ros_exec_kwargs = {'additional_env': additional_env} | ||
self.__ros_exec = RosExecutable(package=package, executable=executable, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting nit^2: consider
self.__ros_exec = RosExecutable(package=package, executable=executable, | |
self.__executable = RosExecutable(package=package, executable=executable, |
The rest is implied IMHO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll make the change.
@@ -197,47 +123,20 @@ def __init__( | |||
passed to the node as ROS remapping rules | |||
:param: ros_arguments list of ROS arguments for the node | |||
:param: arguments list of extra arguments for the node | |||
:param additional_env: Dictionary of environment variables to be added. If env was | |||
None, they are added to the current environment. If not, env is updated with | |||
additional_env. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting hmm, what about env
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting nit: consider extracting RosExecutable
specific keyword arguments from kwargs
instead of duplicating argument documentation (as in {key: value for key, value in kwargs.items() if key in ROS_EXECUTABLE_KWARGS}
or similar).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of pulling out the keywords args and passing them via a pared-down kwargs variable. I used the inspect
module to get the available keywords from the Executable
class to filter out the appropriate pairs.
nodes: Iterable[Node] = None, | ||
ros_arguments: Optional[Iterable[SomeSubstitutionsType]] = None, | ||
arguments: Optional[Iterable[SomeSubstitutionsType]] = None, | ||
**kwargs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting meta: I wonder if it would make sense to allow for "global" parameters and remapping rules here (i.e. without a node name prefix to scope them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting even more so, there was (and probably still is) some confusion around parameter file formats and how do each apply to launch_ros
actions, mainly owing to the fact that the file format expected by rcl
accounts for multiple nodes in the same process whereas the file format expected by rclcpp_components
is bound to a single node.
We could potentially sort that out. RosExecutable
would accept the former while Node
descriptions would accept the latter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there more detailed discussion on this issue you can point me to?
Do you want to try to do anything with regard to this issue in this PR or are you just pointing it out as something to keep in mind for future changes?
node_name: Optional[SomeSubstitutionsType] = None, | ||
node_namespace: Optional[SomeSubstitutionsType] = None, | ||
original_node_name: Optional[SomeSubstitutionsType] = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting nit: consider
node_name: Optional[SomeSubstitutionsType] = None, | |
node_namespace: Optional[SomeSubstitutionsType] = None, | |
original_node_name: Optional[SomeSubstitutionsType] = None, | |
name: Optional[SomeSubstitutionsType] = None, | |
namespace: Optional[SomeSubstitutionsType] = None, | |
original_name: Optional[SomeSubstitutionsType] = None, |
as node
is implied.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, we deprecated (and then removed) the arguments with the "node_" prefix in the existing Node action.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed the node_ prefixes
:param: node_name the name of the node | ||
:param: node_namespace the ROS namespace for this Node | ||
:param: original_node_name the name of the node before remapping; if not specified, | ||
remappings/parameters (including node name/namespace changes) may be applied | ||
to all nodes which share a command line executable with this one |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting hmm, this is a bit clunky. Thinking out loud, perhaps node_name
and node_namespace
shouldn't be prescriptive but descriptive. That is, to be mandatory and expected to match what the actual node already defines, leaving name and namespace remapping to be done separately e.g.:
Node(
name = 'actual_name',
namespace = 'actual_namespace'
remappings = Node.naming(
name = 'my_new_name',
namespace = 'my_new_namespace'
) + [('/chit', '/chat')],
)
CC @wjwwood @jacobperron for feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way it seems a bit brittle to me if we expect the name in launch to match the name in the code. You have to remember to update both places; and worse if the launch file is not maintained by the node author (e.g. in some end-users package).
However, I don't see another way around this problem besides introducing some far-fetched mechanism to query the coded node name from launch. I like @hidmic's suggestion since I think it will result in less hidden surprises for users (i.e. it avoids accidentally renaming a bunch of nodes in a process to the same name). But I'm also worried that being strict about providing the correct node name will be frustrating for users, who currently don't have to do this.
I'm torn.
from ..remap_rule_type import SomeRemapRules | ||
|
||
|
||
class NodeActionExtension: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be worth wrapping the two concepts (node traits and node action extensions) into one? Security as a node trait makes some sense, but I'm not so sure about composable/lifecycle nodes as plugins/extensions.
@mlanting circling back to this. These extensions were added specifically to support osrf/ros2launch_security
. Looking at those extensions, it'd seem like hoisting this to the RosExecutable
action level would make the most sense (since --enclave
is a process-wide ROS argument).
@@ -184,7 +186,8 @@ def test_launch_node_with_parameter_descriptions(self): | |||
) | |||
self._assert_launch_no_errors([node_action]) | |||
|
|||
expanded_parameter_arguments = node_action._Node__expanded_parameter_arguments | |||
for node in node_action.ros_exec.nodes: | |||
expanded_parameter_arguments = node.expanded_parameter_arguments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mlanting hmm, should these be extending the expanded_parameter_arguments
list instead of overwriting? Same below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that would probably be more appropriate. I'll make the change.
774923e
to
fcfaf43
Compare
This still didn't work. I think that this is caching problem on the buildfarm side, due to some recent changes we made. This should be cleared up tonight, I believe. That said, while we like to have the Rpr jobs green, it isn't totally blocking for this. What is blocking is getting this approved in review and also running the https://ci.ros2.org jobs for all platforms. |
Alright, thank you for your help. Is there anything I need to do to trigger the CI jobs? @jacobperron @hidmic Are you content to move forward with this? |
@ros-pull-request-builder retest this please |
@clalancette @hidmic I think this PR has fallen through the cracks, and I wanted to check in with you guys on whether it's something we should continue to put energy into. This was intended to be a stepping stone towards some other design goals, but I'm honestly concerned at this point that other developments may have impacted what we were trying to do here. I'd appreciate your candid thoughts on how to best move forward at this point. |
f5a3528
to
e2ee8da
Compare
e2ee8da
to
cac239f
Compare
This PR is a couple years old, but I've rebased and resolved some warnings. Is this functionality that we would still like to include in launch_ros? |
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
Distro A; OPSEC #4584 Signed-off-by: Roger Strain <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
context in descriptions/node.py. Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
parameters between classes. Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
Distro A, OPSEC #4584. You may have additional rights; please see https://rosmilitary.org/faq/?category=ros-2-license Signed-off-by: matthew.lanting <[email protected]>
…esolve ros2/ros2#1254 by clalancette. Signed-off-by: veronica.knisley <[email protected]>
Signed-off-by: veronica.knisley <[email protected]>
cac239f
to
6e95e60
Compare
@clalancette @hidmic I just wanted to check whether this PR is still something we would like to include in launch_ros. It was originally opened a few years ago, but I've rebased it to include the latest rolling changes (from approximately 3 weeks ago). Thanks! |
This is an initial commit toward the process described in ros2/design#272.
Part of implementation of refactor for ros2/launch#114, and depends on ros2/launch#454; unit tests will fail without those changes.