-
Notifications
You must be signed in to change notification settings - Fork 142
feature: allow extending a python package in ament_python_install_package
#587
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
base: rolling
Are you sure you want to change the base?
feature: allow extending a python package in ament_python_install_package
#587
Conversation
ament_python_isntall_packageament_python_install_package
…tension Signed-off-by: Nadav Elkabets <[email protected]>
Signed-off-by: Nadav Elkabets <[email protected]>
d83479d to
00311aa
Compare
Signed-off-by: Nadav Elkabets <[email protected]>
e2862ff to
0f9b36d
Compare
Signed-off-by: Nadav Elkabets <[email protected]>
0f9b36d to
a8419ae
Compare
While the monopackage pattern (containing both message interfaces and behavioral nodes) isn't intentionally unsupported by ROS2, it currently cannot support packages using `ament_cmake_python` to build C++ and Python code alongside `rosidl` message generation. This is a well-known and long-standing issue; see: ros2/rosidl_python#141 ament/ament_cmake#514 ament/ament_cmake#587 Accordingly, to support well-integrated inclusion of Python code in this package (i.e., by adding `run_vrx_experiments.py` to the CMake build system flow), this commit splits `virelex_msgs` out into a separate package. An argument could be made for converting `run_vrx_experiments.py` into a Python-syntax launch file, but it does a bit too much (defines a class, etc.) for that to feel like the correct option. In addition, this commit integrates the `pretrained_models` folder into the new `trained` subdirectory of `virelex`, providing a stable filesystem path to access those models during node execution (via `get_package_share_directory("virelex")`).
|
I've done an initial check of the code, and it generally looks good, except for one issue. As far as I can tell, you are not supporting AMENT_CMAKE_SYMLINK_INSTALL. Symlink install is probably incompatible with the concept of merging together multiple same-named python packages, but this need to merge is really rather rare, and symlink_install makes sense in the vast majority of packages that do not need this merger. It seems to me that the correct thing to do is to disable symlink install with a warning when multiple same-named packages are detected. Or did I miss something about how you supported this? I have not actually tested yet, but will soon. |
I kept the original package installation code without changes. To my knowledge, colcon changes the behavior of the |
|
This is the code from the original that does not have an equivalent in your PR. (I matched every line of the old code with your new code, and this is the only difference that I found). I'll test it today though to confirm whether it works or not. |
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 thoroughly checked the old code vs the new code, and confirmed that all of the old code has corresponding functionality in the new code, with one exception. I've also tested using a very simple combined msg/ directory with a python package of the same name, and everything works as I expect.
Concerning the exception code, I've compared results of the new code with old code for --symlink-install, and find that the new code works fine with the same result. As @nadavelkabets points out, the modifications to the install command are taking care of the symlinks. The only difference I could find is that in the old code, a symlink is created in the build/ directory, that is not present in the new code. But the end result in the /install directory is the same in both cases, so it is not clear to me why this symlink is needed. Trying to add that symlink would lead to a conflict between duplicate python packages and --symlink-install, so it is good that it is not needed.
But as @hidmic points out in PR #326 that created these symlinks, "There should be a Here be dragons sign in ament_cmake root README..." I'm afraid that fighting those dragons here is beyond my expertise. AFAICT that code is not needed. But perhaps @nadavelkabets you could give your own defense of why.
So with one nit, I've reviewed as thoroughly as I can and find no issues, so here's my gray checkmark.
Hi! Thanks for the review and sorry for the delay. This was my previous code before switching symlink to copy during build: I got also confused by the original implementation. After giving it some thought, I figured that the flag is named "symlink-install" and not "symlink-build". It should not matter to the user if the build is done with symlinks or by copying the files. Moreover, the use of symlinks in build does not effect the user, since the egg files that are built only contain the name of the files, and if a new file is added the user must rebuild both ways. The reason I changed this - to avoid generating scripts to execute during runtime. |
Signed-off-by: Nadav Elkabets <[email protected]>
|
@christophebedard I think we can move forward with this PR. |
|
@mjcarroll Also, as far as I'm aware, there are currently no tests for this package. I'm not really familiar with testing methodology in cmake and testing conventions in ament_cmake. How do you usually approach this? |
|
I see other PRs making progress in this repo, with reviews by @cottsay Is there any way we could get some attention to this PR? @tfoote or @christophebedard perhaps you could help. |
|
Hi there. After reading back through the discussions about this change, things make sense to me. It would give us a LOT more confidence if we had tests. I agree that it's not super straightforward to do, but it's totally possible to do. If you have some cycles, would you mind writing at least a simple test to exercise the code? I see a couple of possible approaches. You can just include the extension and use it (as was done for ament_cmake_vendor_package) or write one or more smoke test packages and then build/test it using something like |
|
@nadavelkabets will you be able to add a test for this? |
Signed-off-by: Nadav Elkabets <[email protected]>
Signed-off-by: Nadav Elkabets <[email protected]>
|
@cottsay @rkent I added tests, but apparently ament_cmake_test depends on ament_cmake_python, so ament_add_test or ament_add_pytest_test fail due to a circular import: Do you have any creative ideas to solve this? |
|
I tried an alternative approach in #597 which seems to work. That is, I created a separate package ament_cmake_python_test that only does the test. "Works" in the sense that it gets past the problem in the previous comment. However, now that the test is running there is a new issue. You can see that your test actually fails when it uses the overlay. I've spent a bit of time understanding this, and I'm pretty sure this is a race condition issue. That is, the test (and really this whole PR) assumes that if we do two calls to ament_python_install_package, then the first call is completed before the second call executes. I don't think that is true, they execute in parallel. So depending on which one goes first, the root It's a race condition because copy_directory is supposed to overwrite files that are the same, but that is not occurring here because the operations are parallel not sequential. I can make the test pass by arbitrarily adding some delaying elements to ament_cmake_python_install_registered_packages.cmake I don't have a clear example of that yet, but I have seen it locally. I think what needs to be done is to add some target dependencies to at least make it deterministic. But in general, the problem exposed by the test is real. That is, if you merge two different subdirectories into the same python package, and there are duplicate file or directory names, there is no way to really resolve that. In the use case that I care about, where there are python packages and custom messages in the same package, the files to merge from the custom messages are fairly predictable, so it should be possible to have reliable results. If someone tries to merge two different subdirectories into a single python package using this PR, we'll just have to assume that they know what they are doing. Some sort of warning message might be indicated. |
Signed-off-by: Nadav Elkabets <[email protected]>
|
I just want to add an update on my work on this. I've mostly been working on testing. Rather than simply manually testing the features, I've been putting together an automated test that exercises the various options of On the existing test, I'm not quite sure what exactly you are trying to test for in the various asserts. Python asserts have an optional clause where you can specify what you expected and why, or you can do it in the comments. If you have ever been in the position of submitting a PR for something, that causes a test failure in something completely unrelated to what you were doing, you'll appreciate why it is important to document why a test is there. If you want your tests added, they really need to be clear as to their purpose. As to issues with the code itself, I have not really gone over it thoroughly yet. But there is one known issue we need to discuss: ordering-dependent behavior of the merges, particularly as it affects the overall module I think I said earlier that the correct behavior would be to respect the order of items added in CMakeLists.py, but I am having second thoughts about that. I believe that by far the motivation for this PR is to be able to combine custom IDL generation (messages, actions, and services) with python code. In that case, the |
My refactor added the ability to overlay multiple python packages, following their declaration order in CMakelists.txt. I agree with you that combining idl generated interfaces with user code should be the only use case of this feature. This is the current behavior:
In my opinion, the ideal behavior would be the following:
To achieve that, this refactor is not enough, and modifications for rosidl_generator_py are required. I might need to think this further. Maybe some maintainers can provide feedback on the matter. |
|
I'm wondering if in your code: we could, before, detect an empty "init.py" wanting to overwrite a non-empty one, and exclude that file from the copy, so that this would work with combined idl/python code without having to worry about order. By the way, I've modified the tests from #598 to work with your code, and all of the tests pass, except one I added where the python install is called before the msg install. I think that will go a long way to making @cottsay comfortable with this change. |
First of all, thanks for the time you invested into testing this code.
My issue with this approach is that it's really case specific. If we're okay with giving users the freedom to overlay as many self made packages as they want, I think that the order dependent implementation is the simplest solution. While I agree that it might be confusing, I think that a more complex solution might make it even more confusing for users If they didn't utilize this feature exactly as we intended. |
|
Also, regarding the build target dependency issue, perhaps we should add a new DEPENDS flag for the ament_python_install_package function and modify rosidl_generator_py to add it's build target as a dependancy. |
Yes you understand correctly. I am specifically testing that the python package init.py is the one that survives, even if it is called before the msg install. So the issue is what is the desired behavior, not some detected issue with your code.
I would say that keeping a contentful
IMHO trying to worry about possible but unlikely abuses should not overrule the desire to make this "just work" for the most common use case, without adding the footgun of having to worry about which comes first in CMakeLists.txt |
You are more experienced in cmake than I am, so I'm not going to push "my" solution over yours. Yes I think doing this with dependencies would be preferable to my |
That's a solution for a different bug that exists in the current implementation and is not solved by my refactor.
@cottsay what's your take on the subject? Should we always take the non-empty root |
Co-authored-by: R. Kent James <[email protected]> Co-authored-by Nadav Elkabets <[email protected]> Signed-off-by: R Kent James <[email protected]>
Co-authored-by: R. Kent James <[email protected]> Co-authored-by Nadav Elkabets <[email protected]> Signed-off-by: R Kent James <[email protected]>
Co-authored-by: R. Kent James <[email protected]> Co-authored-by Nadav Elkabets <[email protected]> Signed-off-by: R Kent James <[email protected]>
|
I've added my test code now in #599 and all is working well. In that code, I proposed a change so that when idl code is combined with python code, the idl code is installed first so that the blank I'm not aware of any other issues in this PR's code but I want to look it over in detail one more time in the next day or so. I'd be interested in @nadavelkabets 's take on both that proposed fix, as well as the proposed test code. |
Hmm. Looking at CMake's own installation behavior: cmake_minimum_required(VERSION 3.15)
project(foo NONE)
install(FILES b/foo.txt DESTINATION share/)
install(FILES a/foo.txt DESTINATION share/)When I run this, both installs are performed in the order they're defined in, so the latter one overwrites. As much as I'd like it to be that simple, I believe I can reproduce the inconsistency you're discussing if I put the files in the same call to install(FILES a/foo.txt b/foo.txt DESTINATION share/)So it seems that CMake isn't entirely consistent with itself. Since package authors will be adding the "overlays" of the package data in separate calls from their CMakeLists, I think it would probably be good to align with the behavior when separate Separately, this could land us in a tricky spot if different "overlays" each include non-empty |
|
@cottsay I agree with one caveat - in the most common case of the combined use of idl files and python packages, the idl file will always have an empty But I won't argue this further if there are strong opinions against it. |
|
Taking this in another direction, would it be possible to explicitly disallow collisions entirely? Allow only additional Python modules to be added, and punt the merging problem entirely? |
|
How is that different from the current behavior? As far as I can tell, the major motivation for this is that people have packages converted from ROS1 that combined idl definitions with python modules, and they don't want to separate them. That could be because their packages are used by others, and they want to preserve the previous design, or they just have too many to be practical to adapt. I would be happy if we supported just the combined IDL and python case, but I don't see why your should restrict the weird case of trying to overlay two python packages, when it clearly could work with the code in this PR. |
|
I'd guess I was under the impression that extending Python packages was the goal here, where a Python package can contain multiple Python modules. It's possible that I'm mistaken or that extending modules is not sufficient for the use case you're seeking, but avoiding the ambiguity in question would be preferable to choosing one behavior or the other, so I thought I'd ask. |
|
If I look at some of the people who have commented on #514, here are some repos and the underlying problem: https://github.com/ADVRHumanoids/nicla_vision_ros2 (attempt to define idl and python in the same package) https://github.com/ros-drivers/audio_common/tree/master/sound_play (the same) @brta-jc said: "Currently we still maintain a fork of rosidl_generator_py and as such cannot build our packages on the upstream ROS industrial CI." so I assume it's the same issue. @sillkjc said: "We maintain a codebase of 100+ packages and splitting them into msgs and srvs is inappropriate for some, and far too much work overall." so I assume the same issue. So all of the example I have seen are combining python with idl. |
I agree that this is the issue we're trying to solve. I'll go over your tests in #599, I agree that we should merge it before merging this PR. |
|
In some of my testing with #599, I came across an issue that may or may not be real. One variation added multiple packages through cmake The problem comes that The solution is simple, just clear the list after I'm not sure if this is a real problem or not, or just an artifact of one way I tried to do testing. It probably does not make sense to have a cmake python package with 'sub packages'. But clearing the list after all packages are installed is probably a good idea anyway. |
I feel like the usage of add_subdirectory contradicts the idea of ros packages. According to the official documentation: "A single workspace can contain as many packages as you want, each in their own folder. You can also have packages of different build types in one workspace (CMake, Python, etc.). You cannot have nested packages." |
|
@cottsay |
That's fine, I don't feel strongly about it. I believe that ideally one of the point of writing tests is to identify edge cases that cause the solution to fail. We can then discuss whether the edge cases are real enough to warrant fixing. Right now I would argue that the "add_subdirectory" edge case is NOT worth fixing, but the "order matters with combined msg and python packages" IS worth fixing. But I can accept what others believe. |
|
@cottsay |
Thanks, sorry about that. I've re-triggered the jobs and updated the links in the original comment. |
Signed-off-by: R Kent James <[email protected]>
|
On https://github.com/rkent/ament_cmake/tree/1-feature/ament-python-extend-package I have the tests from #598 with the mixed msg/python parts enabled. This all passes for me locally. I've left the test of ordering of msg/python includes in CMakeLists.txt disabled, though as I said earlier I still think this is something that should work. |
Closes #514.
Following the suggestion from @clalancette,
calling
ament_python_install_packagenow registers an environment hook and sets the parameters in environment variables, moving installation to theament_packagestage.This allows to extend an existing package and thus allows to utilize
rosidl_generate_interfacesandament_python_install_packageat the same time.Related: