Skip to content
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

Add rosidl generate CLI. #567

Merged
merged 22 commits into from
Mar 3, 2021
Merged

Add rosidl generate CLI. #567

merged 22 commits into from
Mar 3, 2021

Conversation

hidmic
Copy link
Contributor

@hidmic hidmic commented Jan 28, 2021

Connected to #565. This patch puts the basic functionality for an extensible rosidl generate CLI in place. Largely inspired in ros2/ros2cli (minus troublesome dependencies like rclpy).

CI up to rosidl_cli:

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic requested a review from sloretz January 28, 2021 18:34
@hidmic hidmic mentioned this pull request Jan 28, 2021
21 tasks
@hidmic hidmic requested a review from azeey January 29, 2021 22:35
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Copy link
Contributor

@sloretz sloretz left a comment

Choose a reason for hiding this comment

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

Partial review

rosidl_cli/package.xml Outdated Show resolved Hide resolved
rosidl_cli/package.xml Outdated Show resolved Hide resolved
rosidl_cli/rosidl_cli/common.py Outdated Show resolved Hide resolved
This function assumes ROS interface definition files follow the typical
``rosidl`` install space layout i.e. 'package_name/subfolder/interface.idl'.
"""
return pathlib.Path(path).parents[1].name
Copy link
Contributor

Choose a reason for hiding this comment

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

Recommend documenting that it assumes path is a relative path starting from the package's install prefix

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm, it should work with absolute paths too. I do realize I should've resolved the path. See 3a75bcd.

return subparser


def main(*, script_name='rosidl', argv=None, description=None, commands=None):
Copy link
Contributor

Choose a reason for hiding this comment

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

There's no need for arguments; justdef main(): is fine because console_scripts entry points are called with no arguments

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This I took from ros2cli. I understand it's there to (cheaply) support reuse. I can drop it if you think it's not warranted.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd recommend removing them. See 4fa6682 for a suggested way of doing it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See 1140a09.

rosidl_cli/rosidl_cli/cli.py Outdated Show resolved Hide resolved
with pytest.raises(ValueError):
normalize_entry_point_specs(['name?'])

specs = normalize_entry_point_specs(['c', 'cpp==0.1.0'])
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the motivation for the version specification? IIUC this would mean two different packages could do

    # First package
    entry_points={
        'rosidl_cli.command.generate.type_extensions': [
            'cpp = first_pkg.v1.CppExtension',
        ],
    # Second package
    entry_points={
        'rosidl_cli.command.generate.type_extensions': [
            'cpp = second_pkg.v2.CppExtension',
        ],

but why would this situation arise?

Copy link
Contributor Author

@hidmic hidmic Feb 26, 2021

Choose a reason for hiding this comment

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

What's the motivation for the version specification?

It's there to:

  • Enforce tooling versioning to ensure API/ABI stability and simplify deprecation cycles

I envision packages pinning plugin versions to guarantee API/ABI stability.

IIUC this would mean two different packages could do

That would cause a warning at least, and an error at most. I do not expect the same extension to be provided by different Python distributions (i.e. packages), I expect source code generation to be attempted against different versions of the same Python distribution.

Copy link
Contributor

Choose a reason for hiding this comment

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

I envision packages pinning plugin versions to guarantee API/ABI stability.

Pin in what context? As in, someone has a package with message files and they want to be sure they're only generated with cpp==0.1.0? If they're releasing their package they can pin using the version_* attributes in the <*depend> tags of the package.xml.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As in, someone has a package with message files and they want to be sure they're only generated with cpp==0.1.0?

Precisely.

If they're releasing their package they can pin using the version_* attributes in the <*depend> tags of the package.xml.

Hmm, do build tools like colcon enforce them? What if there's no package.xml e.g. if source code generation is performed outside a ROS package? At any rate, it seems way simpler and cheaper to me to have plugin versions (that ultimately map to that of the package) than to overcome those limitations in ROS' package management system.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, do build tools like colcon enforce them? What if there's no package.xml e.g. if source code generation is performed outside a ROS package?

You're right that a version requirement in the package.xml AFAIK is ignored by colcon, but I don't see how that or generating messages inside or outside a ROS package would make a user choose to give a version specifier here. If the generator is installed from a deb/rpm and the packaged version doesn't meet a user's requirements, then they need to build the generator from source. If they're building the generator from source, then they get to pick the version by checking out the one they want to build.

Have a look at 4c39003 to see how much less code there is when the PEP 440 specifiers are removed.

Copy link
Contributor Author

@hidmic hidmic Mar 1, 2021

Choose a reason for hiding this comment

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

If the generator is installed from a deb/rpm and the packaged version doesn't meet a user's requirements, then they need to build the generator from source. If they're building the generator from source, then they get to pick the version by checking out the one they want to build.

That's absolutely correct whenever you have full control over sources. But it quickly becomes unmanageable as you integrate third-party code or start building software layers on top.

Let's say you want to use some third-party packages: foo and bar. Both have the same dependencies at the same version. Both use interfaces from a baz_msgs package at its 1.0.0 version. And yet, it may not be possible to use them in your workspace, or even together at all. Which baz_msgs API/ABI do these packages depend on? Version 1.0.0 only tells half the story. That exact same baz_msgs package could exhibit completely different API/ABIs depending on the state of the workspace it was built in.

Today, we "ensure" consistency by resting on the assumption that no API/ABI breaks will (should?) occur within a ROS distro, and thus no API/ABI break will occur in code generated by released generators. In a sense, we're (implicitly) versioning packages by distro: package foo depends on package baz_msgs at version 1.0.0 in Foxy, but breaks for version 1.0.0 in Rolling because a relevant generator had a major version bump.

IMHO spooky actions at a distance like these are brittle (and break semver if I'm not mistaken).

I don't see how that or generating messages inside or outside a ROS package would make a user choose to give a version specifier here.

By pinning a version specifier, you can make sure interfaces API/ABIs will stay the same (or fail to build with a self explanatory message) regardless of the state of workspace (and underlays).

An alternative solution would be to have each package that generates interfaces explicitly depend on the versions of the generators it uses. And drop all checks. It's less friendly for non-ROS packages consuming ROS, but it's an option.

Copy link
Contributor Author

@hidmic hidmic Mar 1, 2021

Choose a reason for hiding this comment

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

As per offline discussion with @sloretz, the issue I'm trying to address is not exclusive to generators... A C++ package can see its public ABI changed if one of its dependencies changed its own -- all without modifying a single source file. Which kinda explains why our buildfarm has to use package versions, distro names, and build timestamps to ensure binaries come out OK.

It doesn't make sense to have an ad-hoc solution for the rosidl pipeline alone. I'll strip away version specifiers. Thanks for the discussion Shane !!


Still, we should have a discussion about this @ros2/team. It's not semantic versioning what we do, or at least it's not preventing the dependency hell it is meant to prevent. Relevant discussion at semver/semver#341.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See 9939a1b for an alternative to 4c39003 that keeps a few checks (make sure specs are satisfied, complain on duplicate entry points).

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic mentioned this pull request Feb 26, 2021
@hidmic hidmic requested a review from sloretz February 26, 2021 21:29
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
hidmic and others added 7 commits March 1, 2021 10:34
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
No need for ` command`, rosidl has contol over all sub commands and can
make sure none adds an argument called _command.

Signed-off-by: Shane Loretz <sloretz@osrfoundation.org>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic
Copy link
Contributor Author

hidmic commented Mar 1, 2021

BTW made a few minor tweaks here and there in 9036d3b.

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Copy link
Contributor

@sloretz sloretz left a comment

Choose a reason for hiding this comment

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

I'm almost done reviewing, just submitting a couple thoughts before lunch.

rosidl_cli/rosidl_cli/command/__init__.py Show resolved Hide resolved
rosidl_cli/rosidl_cli/extensions.py Outdated Show resolved Hide resolved
rosidl_cli/rosidl_cli/command/generate/helpers.py Outdated Show resolved Hide resolved
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Copy link
Contributor

@sloretz sloretz left a comment

Choose a reason for hiding this comment

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

LGTM. One last recommendation for a corner case, but I don't think it's blocking.

raise RuntimeError(msg)
logger.warning(msg)
continue
if specs and name not in specs:
Copy link
Contributor

Choose a reason for hiding this comment

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

I would recommend putting this check first so that if someone asked for the "c" entrypoint but there were two "cpp" entrypoints then it could still use the "c" extension without being blocked by the problem with the duplicate "cpp" extensions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds like a great idea. See 0128d9d.

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic
Copy link
Contributor Author

hidmic commented Mar 2, 2021

Re-running CI up to rosidl_cli:

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic
Copy link
Contributor Author

hidmic commented Mar 2, 2021

Try again after b24e4fa and f5f5e2b.

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

@hidmic
Copy link
Contributor Author

hidmic commented Mar 2, 2021

So, pathlib.Path.resolve() doesn't work on Windows when the file doesn't exist (see https://bugs.python.org/issue39090)... I'll update this patch, and all connected ones.

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic
Copy link
Contributor Author

hidmic commented Mar 2, 2021

Finally. @sloretz would you mind to take another look?

@hidmic
Copy link
Contributor Author

hidmic commented Mar 3, 2021

Ok, going in. Thanks for the thorough review @sloretz!

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.

None yet

2 participants