Skip to content

Conversation

@emersonknapp
Copy link
Contributor

@emersonknapp emersonknapp commented Oct 23, 2025

Description

Fixes #849

FindLaunchfile substitution can find launch files in a directory by stem name only, if it matches valid suffixes.

This enables users to not care what language an included launchfile is written in, or whether it uses .launch or _launch style.

So instead of

IncludeLaunchDescription(launch_dir / 'radical_cool_autonomy.launch.xml')

the user can do:

IncludeLaunchDescription(FindLaunchfile(path=launch_dir, name='radical_cool_autonomy'))

This is slightly more verbose, but incredibly more flexible.

Now if the package maintainer migrates their Python launchfile to YAML, for example - we as users don't have to change our launchfiles and we'll automatically include the new one.

This change enables new features in launch_ros that I'll push soon: FindPackageLaunch(pkg, name) and IncludePackageLaunch(pkg, name). These will utilize FindPackageShareDirectory + FindLaunchfile in combination.

CAVEAT: if maintainers follow the "redirect migration strategy" that I recommend in my upcoming roscon talk (below), there will now be two launchfiles matching the searched stem, which will fail the substitution. But I'm not sure what the strategy should be to mitigate that.

# migrated_launch.py
from launch import LaunchDescription
from launch.actions import LogWarning, IncludeLaunchDescription
from launch.substitutions import PathSubstitution, ThisLaunchDir

def generate_launch_description():
    return LaunchDescription([
        LogWarning(msg='migrated_launch.py is deprecated: redirecting to migrated_launch.yaml'),
        IncludeLaunchDescription(PathSubstitution(ThisLaunchDir()), 'autonomy_launch.yaml'),
    ])

I can think of these possible strategies:

  • user side: use a new stem when following this migration strategy
  • launch side: allow an optional "soft language preference" option which would help choose from multiple matches, e.g. FindLaunchfile(name='stem', path=launch_dir, prefer='yaml'). If there's only python, it will return that. If py and yaml, then the yaml one returned. If py and xml, then... error? This is probably too confusing of an idea.

Probably the "you should only have one launchfile per stem" soft constraint is better.

Is this user-facing behavior change?

New feature!

Did you use Generative AI?

No

Additional Information

N/A

…h valid suffixes in a directory

Signed-off-by: Emerson Knapp <[email protected]>
if len(data) != 2:
raise AttributeError(
f'find-launchfile substitution expects 2 argument, {len(data)} given')
return cls, {'name': data[0], 'path': data[1]}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to make the arguments $(find-launchfile PATH FILE) instead of FILE PATH?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open for debate, my reasoning was "find name in dir" which matches "grep pattern in location".

I'm sure a sensible argument could be made for the opposite as well. But I'll stick with this implementation as my suggestion.

Copy link

@SuperJappie08 SuperJappie08 Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess it is a matter of opinion.

An argument for my alternative would be that it would match the ros2launch argument ordering (in the package case), which makes the most sense for the suggested IncludePackageLaunch.
Keeping the same order of [package/folder, launchfile'] for this substitution would keep everything consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is sensible, yes. Internal consistency is a good argument 👍 I'll change it

def name_matches_launchfile(name: Text, file: Path) -> bool:
from ..frontend import Parser # avoid circular import
valid_extensions = {'py', *Parser.get_available_extensions()}
valid_suffixes = {'', '.launch', '_launch'}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't allowing a non-suffix lead to matching Python package files as well?

I could imagine a package containing my_node.py and my_node.launch.py, which might introduce issues in the IncludePackageLaunch case.

@SuperJappie08
Copy link

@emersonknapp, have you considered what to do with subfolders?

e.g:

launch/
├─ subsystem/
│  ├─ sublaunch.launch.xml
├─ general_launch_file.launch.xml

Should it be able to find the sublaunch.launch.xml from the launch folder?

@emersonknapp
Copy link
Contributor Author

@SuperJappie08 I thought about it but didn't come to a conclusion. Maybe it should take a recursive boolean argument?

@SuperJappie08
Copy link

I think it really depends on the way this substitution is framed:

  • Now, as FindLaunchfile, it hints that a search and (possibly optional) recursion would make sense.
  • If it were phrased as 'get this launch file, with any extension,' recursion would make significantly less sense, as the specific launch file base name needs to be specified. (GetLaunchfile could be a clearer name for this function.)

If recursion were to be implemented, I can think of 3 options:

  • A trailing optional boolean argument to enable it. However, this would be the only substitution with a flag, which feels off.
  • A separate substitution, which could be called FindLaunchfileRecursive. Also probably not the nicest and more maintance.
  • Allowing some globbing in the launch file name. So if you would want to select something from any folder, use '**/my_file'. It's a bit weird and harder to parse, but it would keep the user API relatively clean.

But after writing this, I feel the best option could just be to rename it to GetLaunchfile and not support recursion, as it will only do exact matches on the base name.
This also prevents edge cases, for when there are multiple folders containing launch files with the same base name.
This also has great user explanation, that you probably should know exactly what the included launch file does and not just pick a random one, since it could be a safety issue.
(For example, I move similar launch files in subfolders, one of the folders gets renamed, and the preferred launch file now changes, changing the behavior of the system completely. It is a bit of a stretch, but this would probably be some user who would just do that.

@emersonknapp emersonknapp moved this from In review to In progress in Robograph Planning Oct 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IncludeLaunchDescription - request name without suffix/extension

2 participants