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

install pip under PACKAGE_DESTINATION instead of GLOBAL_DESTINATION #98

Open
wants to merge 1 commit into
base: devel
Choose a base branch
from

Conversation

k-okada
Copy link
Contributor

@k-okada k-okada commented Mar 14, 2017

The motivation of this PR is discussed in ros-infrastructure/bloom#412 (comment)

  • we want to distribute pip package as an deb package from package.ros.org
  • if the target pip package has dependency to other pip package, we want to include that pip file into the deb package
  • but if that pip package is not compatible with system package, that may cause significant problem. for example, we know pip insstall pyparsing will break rqt_graph
  • so let's consider an example package ( http://github.com/k-okada/break_rqt_graph), which depends on pyparsing through pip requirements.txt
  • we want to install pip packge under not CATKIN_GLOBAL_xxx_DIRECTORY, but somwhere that not included in normal PYTHONPATH, in this example we put pip pckage under PYTHON_PACKAGE_SHARE_DESTINATION
  • workflow need to change
$ source /opt/ros/indigo/setup.bash
$ cd existing_catkin_ws
$ catkin_make
$ source devel/setup.bash
$ rosrun my_package python -m my_package

in this example, or you may update PR something like

$ source /opt/ros/indigo/setup.bash
$ cd existing_catkin_ws
$ catkin_make
$ source devel/setup.bash
$ rosrun my_package activate
$ (my_package_env) python -m my_package
$ (my_package_env) nosetests my_package
$ rosrun my_package deactivate
$
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph/
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing.pyc
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/WHEEL
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/top_level.txt
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/INSTALLER
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/DESCRIPTION.rst
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/metadata.json
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/RECORD
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing-2.2.0.dist-info/METADATA
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//lib/python2.7/site-packages/pyparsing.py
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//cmake
-- Up-to-date: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//cmake/break_rqt_graphConfig-version.cmake
-- Up-to-date: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph//cmake/break_rqt_graphConfig.cmake
-- Installing: /home/k-okada/catkin_ws/ws_catkin_pip/install/lib/pkgconfig/break_rqt_graph.pc
-- Up-to-date: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph/cmake/break_rqt_graphConfig.cmake
-- Up-to-date: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph/cmake/break_rqt_graphConfig-version.cmake
-- Up-to-date: /home/k-okada/catkin_ws/ws_catkin_pip/install/share/break_rqt_graph/package.xml
k-okada@p40-yoga:~/catkin_ws/ws_catkin_pip$ rosrun break_rqt_graph python 
Python 2.7.6 (default, Oct 26 2016, 20:30:19) 
[GCC 4.8.4] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyparsing
>>> pyparsing
<module 'pyparsing' from '/home/k-okada/catkin_ws/ws_catkin_pip/devel/share/break_rqt_graph/lib/python2.7/site-packages/pyparsing.pyc'>
>>> import os
>>> os.system('rqt_graph')
Couldn't import dot_parser, loading of dot files will not be possible.
PluginHandlerDirect._restore_settings() plugin "rqt_graph/RosGraph#0" raised an exception:
Traceback (most recent call last):
  File "/opt/ros/indigo/lib/python2.7/dist-packages/qt_gui/plugin_handler_direct.py", line 116, in _restore_settings
    self._plugin.restore_settings(plugin_settings_plugin, instance_settings_plugin)
  File "/opt/ros/indigo/lib/python2.7/dist-packages/rqt_graph/ros_graph.py", line 203, in restore_settings
    self._refresh_rosgraph()
  File "/opt/ros/indigo/lib/python2.7/dist-packages/rqt_graph/ros_graph.py", line 227, in _refresh_rosgraph
    self._update_graph_view(self._generate_dotcode())
  File "/opt/ros/indigo/lib/python2.7/dist-packages/rqt_graph/ros_graph.py", line 260, in _update_graph_view
    self._redraw_graph_view()
  File "/opt/ros/indigo/lib/python2.7/dist-packages/rqt_graph/ros_graph.py", line 293, in _redraw_graph_view
    same_label_siblings=True)
  File "/opt/ros/indigo/lib/python2.7/dist-packages/qt_dotgraph/dot_to_qt.py", line 234, in dotcode_to_qt_items
    graph = pydot.graph_from_dot_data(dotcode.encode("ascii", "ignore"))
  File "/usr/lib/python2.7/dist-packages/pydot.py", line 220, in graph_from_dot_data
    return dot_parser.parse_dot_data(data)
NameError: global name 'dot_parser' is not defined

^C
>>> 

k-okada@p40-yoga:~/catkin_ws/ws_catkin_pip$ rqt_graph

@asmodehn
Copy link
Member

Having a quick look at https://github.com/k-okada/break_rqt_graph/ I see a few things that you don't want to do in a python package :

In general, the package should follow https://www.pypa.io/en/latest/ + CMakeLists.txt and package.xml.

As a consequence, the requirements.txt file is only used for development dependencies (for tools, tests, docs), that is the point of that catkin_pip_requirements function.
However it will not work from install workspace. Usual "install dependencies" are in the setup.py and are supposed to be ros packages, also registered in the package.xml. So the "dependency link" itself is transferred from pip package system to deb/ros package system.

@k-okada
Copy link
Contributor Author

k-okada commented Mar 18, 2017

I think my concern is a slightly different from yours, but hope we can share the motivation.

So question is that if your package depends on other pip package, which conflict with system installed python package. What should we do?

@asmodehn
Copy link
Member

As far as I understand the problem, this is what "distributions" are supposed to solve : some authority guarantees that all packages included in the distribution work well together...
The "authority" solves these conflicts which happen because we have a flat package structure (no hierarchical dependencies). So in the case of ROS we (package maintainers) need to find a way to make our packages work together.

I investigated the problem you had (I think - I had the same thing on my machine already, I just didn't notice). Details there (I was not sure where to put it) : k-okada/break_rqt_graph#1

So looking at http://packages.ubuntu.com/search?keywords=pyparsing&searchon=sourcenames It seems the ubuntu distribution still ships an "old" version of pyparsing.

To solve this kind of A --depends on--> B problems with A and B being compatible only for some versions, either A can be modified to support more versions of B, or B can be modified to provide some backward compatible API.

In that situation, I usually :

  • check all ubuntu versions of B (pyparsing), to find out which package versions they are using, and then I try them all with the software I want to use. It can help me determine which distribution my package A will be usable with no modification.
  • check the code of A to find out if I can modify it to make it work with different versions.
  • decide which distribution I am going to use for B, and modify A to work with that version of B.
  • If I need a more recent version of B on an older distro, I backport a recent version of B (like the xenial one for example) onto my old ROS distro (indigo for example). I did that for https://github.com/asmodehn/backports.ssl_match_hostname-rosrelease, backporting the xenial version onto indigo/jade.

So for your pyparsing / rqt_graph, I assume it is simple enough to modify rqt_graph to get it to work with whatever version of pyparsing.
However for chainer and pytorch it will be more tricky, but at least testing with different version of pyparsing will help you make their requirements more precise...
Python devs usually focus on using latest version of everything. but when building system packages and making distributions we have to be much more strict about it.

Good luck ! Don't hesitate to keep me posted.

@asmodehn
Copy link
Member

Thinking again about this, python packages and debian packages have quite a different usecase :

  • you install debian package on a system, along with many other packages, and they all have to work well together.
  • you install python packages in a virtual environment, and they only need to work well with package in that environment.

So mutating a python package into a debian package will take some work, in order to make sure potential conflicts with other existing packages are avoided.

@asmodehn
Copy link
Member

I found out pyparsing is used by many, many python packages, so you probably don't want to change that version in a specific distribution. Which means that to fix these problems, it is better to chose a minimum distribution to support, and change the version of software you want to use to work with the corresponding version of pyparsing.

Also pytorch relies on pyyaml (no minimum version specified), and pyyaml is used as a base for ros distributions (It think empy uses it), so you probably need to check which minimal version of pyyaml is necessary for different version of pytorch, and decide on one pytorch version that match a specific ros distro...

@k-okada
Copy link
Contributor Author

k-okada commented Apr 4, 2017

Thanks for comment,

you install debian package on a system, along with many other packages, and they all have to work well together.
you install python packages in a virtual environment, and they only need to work well with package in that environment.

I think challenge here is that ROS build firm will distribute debian/ubuntu system package, where as ROS development system allow us to use python development environment, mainly pip.
So ROS developer build their package using pip environment using rosdep or this catkin_pip tool, however currently we do not have way to distribute the system through build firm.
They are totally two different world/ecosystem so it is different/challenge to merge or find way to co-exists these two systems, but I think we are almost find a way to solve this as shown in this PR.

A rational behind this is that;

  • Usually debian/ubuntu system package installs C/C++ executable files under GLOBAL path, i.e. /usr/bin, however ROS package system installs the under PACKAGE path, i.e. /opt/ros/$ROS_DISTRO/lib/<package>/. This resolves conflicts if two package has same executable file name (Although, usually it does not happens...). So partially ROS provides PACKAGE based file systems, so my proposal here is to support python library as PACKAGE based library path.
  • Python seems to pursuit WORKSPACE based file systems, like virtual environment or anaconda. Which enables us to create same python environment regardless of their system installed systems. If we look at ROS and this PYTHON way of development, I think PACKAGE based ROS python library is very reasonable.

If I look into these situation, I think evolution of development system is moving towards encapsule their development environment, Current catkin_pip is try to encapule python development environment to catkin work space level, not system level (/usr/lib/python..), not user level (~/.local/lib/python...). I think this is great design. And what I propose here is to encapsule python develop environment to package level, as ROS C/C++ executables.

In future, I think we'll encapsule the environment for each node like https://gist.github.com/ruffsl/4a24c26a1aa2cc733c64, but I think this is different story.

@asmodehn
Copy link
Member

asmodehn commented Apr 5, 2017

I think challenge here is that ROS build firm will distribute debian/ubuntu system package, where as ROS development system allow us to use python development environment, mainly pip.

As far as I understand ROS development system allow to use pip only for devel space. When installing the use of pip is not supported. Even if your dependency in rosdep has a pip key, it will install in /usr/local/ which is advised against since it might break your system (it can silently install a different package version than the one that an ubuntu tool rely on...). It will also work for install space though, as long as you are sure that no conflict will occur on any user machine.

So ROS developer build their package using pip environment using rosdep or this catkin_pip tool, however currently we do not have way to distribute the system through build firm.

Using pip environment and rosdep will work for devel space, and even for install space, but might break someone else machine when doing rosdep install because he was not expecting something to be installed on the system and change his ubuntu package behavior.
Note there is a small different between trusty and xenial here, since pip by default install in userspace (not system) from xenial (using a debian patch). But even in user space the problem remains : rosdep will change the default python setup for the user.

That is why catkin_pip install the dependencies or your python packages in the devel space. And the PYTHONPATH necessary modifications are handled by envhooks, which need to play nice (correct ordering) with underlays and overlays, and other pip installed packages (so many different ways to support even pip is buggy, and we need to work around these until they get fixed)...

We currently have a way to distribute one package through the build farm. But we do not have a way to distribute a set of packages that can contain different version of packages with another set of packages, in the same distro. Which is why we have multiple distros.

--

Apart from that, I agree with your observations.
I think about it in multiple layers like :

  • SYSTEM layer, based on unix files hierarchy, or GLOBAL path, i.e. /usr/bin
  • WORKSPACE/virtual environment layer (user's home, ros distro path, python virtualenv, docker image)
  • PACKAGE content (files from deb package, files from pip package, files from npm package)

As you noticed catkin_pip strategy is to follow the python strategy of WORKSPACE based file system, by setting up this python workspace in catkin devel space (with catkin_pip tools in build folder, so they - especially the correct version of setuptools - can also can be used when installing)

When building install space we need to prepare a structure that can be put inside a deb package, and installed with other ros package, in the distro. => The WORKSPACE is not your devel environment anymore, it is the whole distro, which can create conflicts and dependencies issues.

But this is already the case with usual debian package, and with python packages, and is solved by tasking the package maintainers to make sure their package fit into the distro.

If I look into these situation, I think evolution of development system is moving towards encapsule their development environment

I also agree with this, but only when space does not matter, so we do not want to change what we currently have, but add some different way to do it. What I am saying is that ROS currently does not provide the required tools/infrastructure to encapsulate your whole (python) development environment in a "thing" that is easily deployable.

Debian packages and Python packages rely on files structure where the dependency of a package is also part of the distribution, and expect an install to fail when there is a conflict. So we cannot rely on python package system, or on debian package system, to address these dependencies conflicts automatically when the user install a package.

An interesting example of this might be what rosbridge did (embed a different version of a python package into another deb package) :
RobotWebTools/rosbridge_suite#162
which breaks pip : ros-infrastructure/rosdep#456, and probably a number of other tools and import behaviour in other python packages, and apparently nobody tested/reported that for years...
We kept having problem with these dependencies at Yujin until I found the time to dig and find out what was silently installing python packages with unexpected version in our rosdistro workspace...

Also we (ROS community) do not have the resources to invent some new way of packaging python code and maintain it reliably, so we need to rely on existing software for this.There are multiple options to this (snaps, docker, dh_virtualenv, and probably many other package systems that I am not aware of ).

So catkin_pip is not trying to invent a new way to distribute new kinds of packages. The point of catkin_pip is to fix the way python packages are handled in ROS currently, which is broken when following python packaging authority.

So I dropped the idea of packaging an entire python environment in a deb package when I realized that even something like dh_virtualenv would require buildfarm changes.

--

That being said, I think you are hitting multiple problems :

  1. you need a specific version of a pip package not compatible with the ROS distro version
  2. you need to install it (not on the system)
  3. you need to import it
  4. you need to distribute it

If we forget the last one, to solve 1. 2. and 3. you used catkin_pip, but there are other (maybe simpler?) possibilities

If you care only about devel space (multiple people working on same set of repos) you can have in your package, a set of submodules satisfying its specific dependencies requirements (I did that in https://github.com/asmodehn/rostful/tree/ff8d1c156ada44094b72a3be3310b6a8f149e009/deps because changing all dependencies to be the right version for the distro you are targeting, to be released as ros packages, is very time consuming). This should not require any change to the python path (since your current package is already in the path) or to the cmake.

If you care about install space (distributing binaries/installs by copying files around - encapsulating everything into a docker or so), there is an issue listed for submodules to work there : #51. It might be already solved (since I didnt get any bug report about this for rostful...) but I haven't looked at it in a long time.

But if you have to put them in a ROS debian package, and distribute that package, then they need to not create any conflict into the ROS distro, or on the user system. Putting more python packages in that deb file is not supported by the package system and will create all kinds of tricky problems.

I am open to investigate how we can incorporate an existing solution like dh_virtualenv into catkin_pip workflow, but it will require some effort. Let me know if you are interested in that, but I see it as "another way" of distributing python packages, not the "standard" one (we should aim to stay as close to https://www.debian.org/doc/packaging-manuals/python-policy/ as ROS permits). We could extend the cmake API for this...

--

Sorry for the long message, but I hope it is clear and detailed enough.
Feel free to let me know if you want me to illustrate some part with more details/example.

@k-okada
Copy link
Contributor Author

k-okada commented Apr 5, 2017 via email

@asmodehn
Copy link
Member

asmodehn commented Apr 6, 2017

Yes installing a pip package anywhere is easy. But setting up everything to make it usable properly is really tricky (as you know rosdep with pip, or a ros package with multiple python packages, can break your system). The import machinery of python is quite complex.

There are other things you didn't list in the things we have to consider :

  • we need to update our system as soon as setuptools / pip are updated
  • we need to support different kinds of packages repo (pypi, devpi, from repo, etc.)
  • we need to support all kinds of packages (like namespace packages, eggs, wheels)
  • we need to support different ways of importing (via import, via importlib, via custom importers)

So trying to minimize our development effort here, as far as I can see there are two ways to solve your problems :

  • use submodules to embed your dependencies in source, and get that to work all the way to install space, and distribute that install space (snaps or docker for example)

  • implement dh_virtualenv to be able to get a full virtual environment inside a deb. I don't know of any other possibilities to get a virtual environment (because we need a full python supported solution, not a hack) into a deb package (because we need a ROS supported package system and there is only one) Let me know if you know of another, but the task is an adventure in itself...

So I think you should investigate how catkin_pip :

  • build the devel workspace using pip
  • build the install workspace using setuptools -> This we need to modify to support dh_virtualenv. maybe we need a catkin_pip_virtualenv...

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.

2 participants