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

use pip install --user when --as_root pip:false, rather than pip install #693

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
6 changes: 6 additions & 0 deletions config/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
installer-options:
pip:
additional-flags: user
sudo: false
command-options: --user

1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
test_suite='nose.collector',
test_requires=['mock', 'nose >= 1.0'],
scripts=['scripts/rosdep', 'scripts/rosdep-source'],
data_files=[('etc/ros/rosdep', ['config/config.yaml'])],
author='Tully Foote, Ken Conley',
author_email='[email protected]',
url='http://wiki.ros.org/rosdep',
Expand Down
1 change: 1 addition & 0 deletions src/rosdep2/installers.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ def __init__(self, detect_fn, supports_depends=False):
self.supports_depends = supports_depends
self.as_root = True
self.sudo_command = 'sudo -H' if os.geteuid() != 0 else ''
self.installer_options = {}

def elevate_priv(self, cmd):
"""
Expand Down
183 changes: 111 additions & 72 deletions src/rosdep2/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@
from urllib2 import URLError
import warnings

from optparse import OptionParser
from argparse import ArgumentParser

import rospkg
import yaml

from . import create_default_installer_context, get_default_installer
from . import __version__
Expand Down Expand Up @@ -265,77 +266,110 @@ def _rosdep_main(args):
# sources cache dir is our local database.
default_sources_cache = get_sources_cache_dir()

parser = OptionParser(usage=_usage, prog='rosdep')
parser.add_option('--os', dest='os_override', default=None,
metavar='OS_NAME:OS_VERSION', help='Override OS name and version (colon-separated), e.g. ubuntu:lucid')
parser.add_option('-c', '--sources-cache-dir', dest='sources_cache_dir', default=default_sources_cache,
metavar='SOURCES_CACHE_DIR', help='Override %s' % (default_sources_cache))
parser.add_option('--verbose', '-v', dest='verbose', default=False,
action='store_true', help='verbose display')
parser.add_option('--version', dest='print_version', default=False,
action='store_true', help='print just the rosdep version, then exit')
parser.add_option('--all-versions', dest='print_all_versions', default=False,
action='store_true', help='print rosdep version and version of installers, then exit')
parser.add_option('--reinstall', dest='reinstall', default=False,
action='store_true', help='(re)install all dependencies, even if already installed')
parser.add_option('--default-yes', '-y', dest='default_yes', default=False,
action='store_true', help='Tell the package manager to default to y or fail when installing')
parser.add_option('--simulate', '-s', dest='simulate', default=False,
action='store_true', help='Simulate install')
parser.add_option('-r', dest='robust', default=False,
action='store_true', help='Continue installing despite errors.')
parser.add_option('-q', dest='quiet', default=False,
action='store_true', help='Quiet. Suppress output except for errors.')
parser.add_option('-a', '--all', dest='rosdep_all', default=False,
action='store_true', help='select all packages')
parser.add_option('-n', dest='recursive', default=True,
action='store_false', help="Do not consider implicit/recursive dependencies. Only valid with 'keys', 'check', and 'install' commands.")
parser.add_option('--ignore-packages-from-source', '--ignore-src', '-i',
dest='ignore_src', default=False, action='store_true',
help="Affects the 'check', 'install', and 'keys' verbs. "
'If specified then rosdep will ignore keys that '
'are found to be catkin packages anywhere in the '
'ROS_PACKAGE_PATH or in any of the directories '
'given by the --from-paths option.')
parser.add_option('--skip-keys',
dest='skip_keys', action='append', default=[],
help="Affects the 'check' and 'install' verbs. The "
'specified rosdep keys will be ignored, i.e. not '
'resolved and not installed. The option can be supplied multiple '
'times. A space separated list of rosdep keys can also '
'be passed as a string. A more permanent solution to '
'locally ignore a rosdep key is creating a local rosdep rule '
'with an empty list of packages (include it in '
'/etc/ros/rosdep/sources.list.d/ before the defaults).')
parser.add_option('--filter-for-installers',
action='append', default=[],
help="Affects the 'db' verb. If supplied, the output of the 'db' "
'command is filtered to only list packages whose installer '
'is in the provided list. The option can be supplied '
'multiple times. A space separated list of installers can also '
'be passed as a string. Example: `--filter-for-installers "apt pip"`')
parser.add_option('--from-paths', dest='from_paths',
default=False, action='store_true',
help="Affects the 'check', 'keys', and 'install' verbs. "
'If specified the arguments to those verbs will be '
'considered paths to be searched, acting on all '
'catkin packages found there in.')
parser.add_option('--rosdistro', dest='ros_distro', default=None,
help='Explicitly sets the ROS distro to use, overriding '
'the normal method of detecting the ROS distro '
'using the ROS_DISTRO environment variable.')
parser.add_option('--as-root', default=[], action='append',
metavar='INSTALLER_KEY:<bool>', help='Override '
'whether sudo is used for a specific installer, '
"e.g. '--as-root pip:false' or '--as-root \"pip:no homebrew:yes\"'. "
'Can be specified multiple times.')
parser.add_option('--include-eol-distros', dest='include_eol_distros',
default=False, action='store_true',
help="Affects the 'update' verb. "
'If specified end-of-life distros are being '
'fetched too.')

options, args = parser.parse_args(args)
parser = ArgumentParser(usage=_usage)
parser.add_argument('--os', dest='os_override', default=None,
metavar='OS_NAME:OS_VERSION', help='Override OS name and version (colon-separated), e.g. ubuntu:lucid')
parser.add_argument('-c', '--sources-cache-dir', dest='sources_cache_dir', default=default_sources_cache,
metavar='SOURCES_CACHE_DIR', help='Override %s' % (default_sources_cache))
parser.add_argument('--verbose', '-v', dest='verbose', default=False,
action='store_true', help='verbose display')
parser.add_argument('--version', dest='print_version', default=False,
action='store_true', help='print just the rosdep version, then exit')
parser.add_argument('--all-versions', dest='print_all_versions', default=False,
action='store_true', help='print rosdep version and version of installers, then exit')
parser.add_argument('--reinstall', dest='reinstall', default=False,
action='store_true', help='(re)install all dependencies, even if already installed')
parser.add_argument('--default-yes', '-y', dest='default_yes', default=False,
action='store_true', help='Tell the package manager to default to y or fail when installing')
parser.add_argument('--simulate', '-s', dest='simulate', default=False,
action='store_true', help='Simulate install')
parser.add_argument('-r', dest='robust', default=False,
action='store_true', help='Continue installing despite errors.')
parser.add_argument('-q', dest='quiet', default=False,
action='store_true', help='Quiet. Suppress output except for errors.')
parser.add_argument('-a', '--all', dest='rosdep_all', default=False,
action='store_true', help='select all packages')
parser.add_argument('-n', dest='recursive', default=True,
action='store_false', help="Do not consider implicit/recursive dependencies. Only valid with 'keys', 'check', and 'install' commands.")
parser.add_argument('--ignore-packages-from-source', '--ignore-src', '-i',
dest='ignore_src', default=False, action='store_true',
help="Affects the 'check', 'install', and 'keys' verbs. "
'If specified then rosdep will ignore keys that '
'are found to be catkin packages anywhere in the '
'ROS_PACKAGE_PATH or in any of the directories '
'given by the --from-paths option.')
parser.add_argument('--skip-keys',
dest='skip_keys', action='append', default=[],
help="Affects the 'check' and 'install' verbs. The "
'specified rosdep keys will be ignored, i.e. not '
'resolved and not installed. The option can be supplied multiple '
'times. A space separated list of rosdep keys can also '
'be passed as a string. A more permanent solution to '
'locally ignore a rosdep key is creating a local rosdep rule '
'with an empty list of packages (include it in '
'/etc/ros/rosdep/sources.list.d/ before the defaults).')
parser.add_argument('--filter-for-installers',
action='append', default=[],
help="Affects the 'db' verb. If supplied, the output of the 'db' "
'command is filtered to only list packages whose installer '
'is in the provided list. The option can be supplied '
'multiple times. A space separated list of installers can also '
'be passed as a string. Example: `--filter-for-installers "apt pip"`')
parser.add_argument('--from-paths', dest='from_paths',
default=False, action='store_true',
help="Affects the 'check', 'keys', and 'install' verbs. "
'If specified the arguments to those verbs will be '
'considered paths to be searched, acting on all '
'catkin packages found there in.')
parser.add_argument('--rosdistro', dest='ros_distro', default=None,
help='Explicitly sets the ROS distro to use, overriding '
'the normal method of detecting the ROS distro '
'using the ROS_DISTRO environment variable.')
parser.add_argument('--as-root', default=[], action='append',
metavar='INSTALLER_KEY:<bool>', help='Override '
'whether sudo is used for a specific installer, '
"e.g. '--as-root pip:false' or '--as-root \"pip:no homebrew:yes\"'. "
'Can be specified multiple times.')
parser.add_argument('--include-eol-distros', dest='include_eol_distros',
default=False, action='store_true',
help="Affects the 'update' verb. "
'If specified end-of-life distros are being '
'fetched too.')
parser.add_argument('--installer-options', default=None,
metavar='{INSTALLER_KEY: {additional-flags: <string>, '
'sudo: <bool>, command-options: <string>, help: <string>}',
help='Add argument flags to set customized command options for a specific installer. '
"e.g. '--installer-options \"{pip: {additional-flags: user, "
"sudo: false, command-options: -v --user, "
"help: Install to the user install directory, typically ~/.local}}\"")

# skip --help to show --installer-options in help command
options, _ = parser.parse_known_args([arg for arg in args if arg not in ['-h', '--help']])
# load rosdep/config.yaml
installer_options = {}
for etc_ros_dir in ['/etc/ros', rospkg.get_ros_home()]:
# we can't use get_etc_ros_dir() because environment config does not carry out over under sudo
rosdep_config = os.path.join(etc_ros_dir, 'rosdep', 'config.yaml')
if os.path.exists(rosdep_config):
if options.verbose:
print('loading rosdep config from %s\n' % (rosdep_config), file=sys.stderr)
with open(rosdep_config, 'r') as f:
loaded = yaml.safe_load(f.read())
if 'installer-options' in loaded:
installer_options.update(loaded['installer-options'])
if options.installer_options:
# process --installer-options to add additional argument flags for a specific installer
installer_options.update(yaml.safe_load(options.installer_options.rstrip()))
for k, v in installer_options.items():
if 'additional-flags' not in v:
parser.error("'additional-flags' key is required for --installer-options\n"
"Current argument is '--installer-options %s'" % installer_options)
else:
parser.add_argument('--' + v['additional-flags'], action='store_true', help=v['help'] if 'help' in v else None)
options, args = parser.parse_known_args(args)
# finally set options.installer_options as a dict of settings
options.installer_options = installer_options

if options.print_version or options.print_all_versions:
# First print the rosdep version.
print('{}'.format(__version__))
Expand Down Expand Up @@ -516,6 +550,7 @@ def configure_installer_context(installer_context, options):

- Override the OS detector in *installer_context* if necessary.
- Set *as_root* for installers if specified.
- Set *installer_options* for installers if specified and additional-flags are set.

:raises: :exc:`UsageError` If user input options incorrectly
"""
Expand All @@ -527,6 +562,10 @@ def configure_installer_context(installer_context, options):
installer_context.get_installer(k).as_root = v
except KeyError:
raise UsageError("Installer '%s' not defined." % k)
for k, v in options.installer_options.items():
# set install_options only when options holds v['additional-flags'] argument as True
if vars(options)[v['additional-flags']]:
installer_context.get_installer(k).installer_options = v


def command_init(options):
Expand Down
4 changes: 4 additions & 0 deletions src/rosdep2/platforms/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ def get_install_command(self, resolved, interactive=True, reinstall=False, quiet
if not packages:
return []
cmd = ['pip', 'install', '-U']
if 'sudo' in self.installer_options:
self.as_root = self.installer_options['sudo']
if 'command-options' in self.installer_options:
cmd += self.installer_options['command-options'].split(' ')
if quiet:
cmd.append('-q')
if reinstall:
Expand Down
6 changes: 6 additions & 0 deletions test/ros_home/rosdep/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
installer-options:
pip:
additional-flags: user
sudo: false
command-options: --user

16 changes: 16 additions & 0 deletions test/test_rosdep_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ def get_cache_dir():
return p


def get_home_dir():
p = os.path.join(get_test_dir(), 'ros_home')
assert os.path.isdir(p)
return p


@contextmanager
def fakeout():
realstdout = sys.stdout
Expand Down Expand Up @@ -291,3 +297,13 @@ def test_invalid_package_message(self):
assert len(output) == 2
assert test_package_dir in output[0]
assert 'Package version ":{version}" does not follow version conventions' in output[1]

def test_installer_options(self):
import rospkg
rospkg.get_ros_home = get_home_dir
sources_cache = get_cache_dir()
cmd_extras = ['-c', sources_cache]
with fakeout() as b:
rosdep_main(['install', 'python_dep', '--user'] + cmd_extras)
stdout, stderr = b
assert 'All required rosdeps installed' in stdout.getvalue(), stdout.getvalue()
23 changes: 23 additions & 0 deletions test/test_rosdep_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,29 @@ def test(mock_method, mock_is_pip_installed):
['sudo', '-H', 'pip', 'install', '-U', 'b']]
val = installer.get_install_command(['whatever'], interactive=True)
assert val == expected, val

# check as_root option with PIP
installer.as_root = False
expected = [['pip', 'install', '-U', 'a'],
['pip', 'install', '-U', 'b']]
val = installer.get_install_command(['whatever'], interactive=False)
assert val == expected, val
expected = [['pip', 'install', '-U', 'a'],
['pip', 'install', '-U', 'b']]
val = installer.get_install_command(['whatever'], interactive=True)
assert val == expected, val

# check as_root option with PIP
installer = PipInstaller()
installer.installer_options = {'sudo': False, 'command-options': '--user -v'}
expected = [['pip', 'install', '-U', '--user', '-v', 'a'],
['pip', 'install', '-U', '--user', '-v', 'b']]
val = installer.get_install_command(['whatever'], interactive=False)
assert val == expected, val
expected = [['pip', 'install', '-U', '--user', '-v', 'a'],
['pip', 'install', '-U', '--user', '-v', 'b']]
val = installer.get_install_command(['whatever'], interactive=True)
assert val == expected, val
try:
test()
except AssertionError:
Expand Down