diff --git a/docs/news.txt b/docs/news.txt index 2c71b53346502c574453742f77dbfd1d9008d236..236e2498cdfd410f3ab5547c61b8c7cdfdae54ef 100644 --- a/docs/news.txt +++ b/docs/news.txt @@ -37,6 +37,8 @@ hg tip * Support Debian/Ubuntu "dist-packages" in zip command. Thanks duckx. * Fix relative --src folder. Thanks Simon Cross. * Handle missing VCS with an error message. Thanks Alexandre Conrad. +* Added --no-download option to install; pairs with --no-install to separate + download and installation into two steps. Thanks Simon Cross. 0.6.3 ----- diff --git a/pip/commands/install.py b/pip/commands/install.py index c2974acfcb6834e7f251b17fb744fb7abec93923..7e688edcfe5a892ccd08931312e6de6843e3bf4a 100644 --- a/pip/commands/install.py +++ b/pip/commands/install.py @@ -106,6 +106,12 @@ class InstallCommand(Command): dest='no_install', action='store_true', help="Download and unpack all packages, but don't actually install them") + self.parser.add_option( + '--no-download', + dest='no_download', + action="store_true", + help="Don't download any packages, just install the ones already downloaded " + "(completes an install run with --no-install)") self.parser.add_option( '--install-option', @@ -151,7 +157,10 @@ class InstallCommand(Command): for filename in options.requirements: for req in parse_requirements(filename, finder=finder, options=options): requirement_set.add_requirement(req) - requirement_set.install_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) + if not options.no_download: + requirement_set.install_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) + else: + requirement_set.locate_files() if not options.no_install and not self.bundle: requirement_set.install(install_options) installed = ' '.join([req.name for req in diff --git a/pip/req.py b/pip/req.py index 6536d903d953ea7f7a3498d0ba9f70b10bc3c632..3a942086a3180e806faba379f7386830a2f4cddf 100644 --- a/pip/req.py +++ b/pip/req.py @@ -769,6 +769,43 @@ class RequirementSet(object): req.uninstall(auto_confirm=auto_confirm) req.commit_uninstall() + def locate_files(self): + ## FIXME: duplicates code from install_files; relevant code should + ## probably be factored out into a separate method + unnamed = list(self.unnamed_requirements) + reqs = self.requirements.values() + while reqs or unnamed: + if unnamed: + req_to_install = unnamed.pop(0) + else: + req_to_install = reqs.pop(0) + install_needed = True + if not self.ignore_installed and not req_to_install.editable: + req_to_install.check_if_exists() + if req_to_install.satisfied_by: + if self.upgrade: + req_to_install.conflicts_with = req_to_install.satisfied_by + req_to_install.satisfied_by = None + else: + install_needed = False + if req_to_install.satisfied_by: + logger.notify('Requirement already satisfied ' + '(use --upgrade to upgrade): %s' + % req_to_install) + + if req_to_install.editable: + if req_to_install.source_dir is None: + req_to_install.source_dir = req_to_install.build_location(self.src_dir) + elif install_needed: + req_to_install.source_dir = req_to_install.build_location(self.build_dir, not self.is_download) + + if req_to_install.source_dir is not None and not os.path.isdir(req_to_install.source_dir): + raise InstallationError('Could not install requirement %s ' + 'because source folder %s does not exist ' + '(perhaps --no-download was used without first running ' + 'an equivalent install with --no-install?)' + % (req_to_install, req_to_install.source_dir)) + def install_files(self, finder, force_root_egg_info=False, bundle=False): unnamed = list(self.unnamed_requirements) reqs = self.requirements.values() diff --git a/tests/test_basic.py b/tests/test_basic.py index c9b50e744cfb6a09eac1d7c9cd3d33da713bb90d..8e350a768ba43f38d15cf3f0a16ba6900130019f 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -90,6 +90,56 @@ def test_download_editable_to_custom_path(): assert 'customsrc/initools/setup.py' in result.files_created assert [filename for filename in result.files_created.keys() if filename.startswith('customdl/initools')] +def test_editable_no_install_followed_by_no_download(): + """ + Test installing an editable in two steps (first with --no-install, then with --no-download). + + """ + reset_env() + + result = run_pip('install', '-e', 'svn+http://svn.colorstudy.com/INITools/trunk#egg=initools-dev', + '--no-install', expect_error=True) + assert lib_py + 'site-packages/INITools.egg-link' not in result.files_created + assert 'src/initools' in result.files_created + assert 'src/initools/.svn' in result.files_created + + result = run_pip('install', '-e', 'svn+http://svn.colorstudy.com/INITools/trunk#egg=initools-dev', + '--no-download', expect_error=True) + egg_link = result.files_created[lib_py + 'site-packages/INITools.egg-link'] + # FIXME: I don't understand why there's a trailing . here: + assert egg_link.bytes.endswith('/test-scratch/src/initools\n.'), egg_link.bytes + assert (lib_py + 'site-packages/easy-install.pth') in result.files_updated + assert 'src/initools' not in result.files_created + assert 'src/initools/.svn' not in result.files_created + +def test_no_install_followed_by_no_download(): + """ + Test installing in two steps (first with --no-install, then with --no-download). + + """ + reset_env() + + result = run_pip('install', 'INITools==0.2', '--no-install', expect_error=True) + assert (lib_py + 'site-packages/INITools-0.2-py%s.egg-info' % pyversion) not in result.files_created, str(result) + assert (lib_py + 'site-packages/initools') not in result.files_created, sorted(result.files_created.keys()) + assert 'build/INITools' in result.files_created + assert 'build/INITools/INITools.egg-info' in result.files_created + + result = run_pip('install', 'INITools==0.2', '--no-download', expect_error=True) + assert (lib_py + 'site-packages/INITools-0.2-py%s.egg-info' % pyversion) in result.files_created, str(result) + assert (lib_py + 'site-packages/initools') in result.files_created, sorted(result.files_created.keys()) + assert 'build/INITools' not in result.files_created + assert 'build/INITools/INITools.egg-info' not in result.files_created + +def test_bad_install_with_no_download(): + """ + Test that --no-download behaves sensibly if the package source can't be found. + + """ + reset_env() + + result = run_pip('install', 'INITools==0.2', '--no-download', expect_error=True) + assert result.stdout.find("perhaps --no-download was used without first running an equivalent install with --no-install?") > 0 def test_install_dev_version_from_pypi(): """