diff --git a/AUTHORS.txt b/AUTHORS.txt index e1633b223..17d8c0a43 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -48,6 +48,7 @@ Paul van der Linden Peter Waller Phil Whelan Piet Delport +Przemek Wrzos Qiangning Hong Rafael Caricio Rene Dudfield diff --git a/docs/news.txt b/docs/news.txt index ac02e162f..ef84737da 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -27,6 +27,10 @@ develop (unreleased) * Added "pip show" command to get information about an installed package. Fixes #131. Thanks Kelsey Hightower and Rafael Caricio. +* Added `--root` option for "pip install" to specify root directory. Behaves + like the same option in distutils but also plays nice with pip's egg-info. + (Issue #253) + 1.2.1 (2012-09-06) ------------------ diff --git a/pip/commands/install.py b/pip/commands/install.py index 9900c434a..99947bdff 100644 --- a/pip/commands/install.py +++ b/pip/commands/install.py @@ -172,6 +172,13 @@ class InstallCommand(Command): action='store_true', help="Install as self contained egg file, like easy_install does.") + self.parser.add_option( + '--root', + dest='root_path', + metavar='DIR', + default=None, + help="Install everything relative to this alternate root directory") + def _build_package_finder(self, options, index_urls): """ Create a package finder appropriate to this install command. @@ -258,7 +265,7 @@ class InstallCommand(Command): requirement_set.locate_files() if not options.no_install and not self.bundle: - requirement_set.install(install_options, global_options) + requirement_set.install(install_options, global_options, root=options.root_path) installed = ' '.join([req.name for req in requirement_set.successfully_installed]) if installed: diff --git a/pip/req.py b/pip/req.py index 6fa43ac48..dbe7af27b 100644 --- a/pip/req.py +++ b/pip/req.py @@ -8,6 +8,7 @@ import shutil import tempfile import zipfile +from distutils.util import change_root from pip.locations import bin_py, running_under_virtualenv from pip.exceptions import (InstallationError, UninstallationError, BestVersionAlreadyInstalled, @@ -557,7 +558,7 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec')) name = name.replace(os.path.sep, '/') return name - def install(self, install_options, global_options=()): + def install(self, install_options, global_options=(), root=None): if self.editable: self.install_editable(install_options, global_options) return @@ -576,6 +577,9 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec')) if not self.as_egg: install_args += ['--single-version-externally-managed'] + if root is not None: + install_args += ['--root', root] + if running_under_virtualenv(): ## FIXME: I'm not sure if this is a reasonable location; probably not ## but we can't put it in the default location, as that is a virtualenv symlink that isn't writable @@ -598,11 +602,17 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec')) # so we unable to save the installed-files.txt return + def prepend_root(path): + if root is None or not os.path.isabs(path): + return path + else: + return change_root(root, path) + f = open(record_filename) for line in f: line = line.strip() if line.endswith('.egg-info'): - egg_info_dir = line + egg_info_dir = prepend_root(line) break else: logger.warn('Could not find .egg-info directory in install record for %s' % self) @@ -616,7 +626,7 @@ exec(compile(open(__file__).read().replace('\\r\\n', '\\n'), __file__, 'exec')) filename = line.strip() if os.path.isdir(filename): filename += os.path.sep - new_lines.append(make_path_relative(filename, egg_info_dir)) + new_lines.append(make_path_relative(prepend_root(filename), egg_info_dir)) f.close() f = open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w') f.write('\n'.join(new_lines)+'\n') @@ -1151,7 +1161,7 @@ class RequirementSet(object): _write_delete_marker_message(os.path.join(location, PIP_DELETE_MARKER_FILENAME)) return retval - def install(self, install_options, global_options=()): + def install(self, install_options, global_options=(), *args, **kwargs): """Install everything in this set (after having downloaded and unpacked the packages)""" to_install = [r for r in self.requirements.values() if not r.satisfied_by] @@ -1170,7 +1180,7 @@ class RequirementSet(object): finally: logger.indent -= 2 try: - requirement.install(install_options, global_options) + requirement.install(install_options, global_options, *args, **kwargs) except: # if install did not succeed, rollback previous uninstall if requirement.conflicts_with and not requirement.install_succeeded: diff --git a/tests/test_basic.py b/tests/test_basic.py index e1b990a35..d5a054b14 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -491,6 +491,18 @@ def test_install_package_with_target(): assert Path('scratch')/'target'/'initools' in result.files_created, str(result) +def test_install_package_with_root(): + """ + Test installing a package using pip install --root + """ + env = reset_env() + root_dir = env.scratch_path/'root' + result = run_pip('install', '--root', root_dir, '--install-option=--home=', + '--install-option=--install-lib=/lib/python', "initools==0.1") + + assert Path('scratch')/'root'/'lib'/'python'/'initools' in result.files_created, str(result) + + def test_find_command_folder_in_path(): """ If a folder named e.g. 'git' is in PATH, and find_command is looking for