diff --git a/pip/req.py b/pip/req.py index ff423dfa2c9..9a51dd59155 100644 --- a/pip/req.py +++ b/pip/req.py @@ -34,7 +34,7 @@ class InstallRequirement(object): def __init__(self, req, comes_from, source_dir=None, editable=False, - url=None, update=True): + url=None, update=True, requirement_line=None): self.extras = () if isinstance(req, string_types): req = pkg_resources.Requirement.parse(req) @@ -44,6 +44,7 @@ def __init__(self, req, comes_from, source_dir=None, editable=False, self.source_dir = source_dir self.editable = editable self.url = url + self.requirement_line = requirement_line self._egg_info_path = None # This holds the pkg_resources.Distribution object if this requirement # is already available: @@ -62,12 +63,13 @@ def __init__(self, req, comes_from, source_dir=None, editable=False, @classmethod def from_editable(cls, editable_req, comes_from=None, default_vcs=None): + requirement_line = "--editable=%s" % editable_req name, url = parse_editable(editable_req, default_vcs) if url.startswith('file:'): source_dir = url_to_path(url) else: source_dir = None - return cls(name, comes_from, source_dir=source_dir, editable=True, url=url) + return cls(name, comes_from, source_dir=source_dir, editable=True, url=url, requirement_line=requirement_line) @classmethod def from_line(cls, name, comes_from=None): @@ -104,7 +106,7 @@ def from_line(cls, name, comes_from=None): else: req = name - return cls(req, comes_from, url=url) + return cls(req, comes_from, url=url, requirement_line=name) def __str__(self): if self.req: @@ -548,6 +550,19 @@ def _clean_zip_name(self, name, prefix): name = name.replace(os.path.sep, '/') return name + def _write_info_ini(self, egg_info_dir, options): + info = ConfigParser.RawConfigParser() + + for section, values in options.iteritems(): + info.add_section(section) + + for key, value in values.iteritems(): + info.set(section, key, value) + + f = open(os.path.join(egg_info_dir, 'pip.ini'), 'w') + info.write(f) + f.close() + def install(self, install_options, global_options=()): if self.editable: self.install_editable(install_options, global_options) @@ -601,8 +616,24 @@ def install(self, install_options, global_options=()): filename += os.path.sep new_lines.append(make_path_relative(filename, egg_info_dir)) f.close() + + info_data = { + 'download': { + 'url': self.url.url, + } + } + + if self.requirement_line: + info_data['download']['requirement'] = self.requirement_line + else: + logger.warn("No requirement line recorded for %s" % self.name) + self._write_info_ini(egg_info_dir, info_data) + + # Add the pip.ini to the installed-files.txt + new_lines.append('pip.ini') + f = open(os.path.join(egg_info_dir, 'installed-files.txt'), 'w') - f.write('\n'.join(new_lines)+'\n') + f.write('\n'.join(new_lines) + '\n') f.close() finally: if os.path.exists(record_filename): @@ -637,6 +668,21 @@ def install_editable(self, install_options, global_options=()): logger.indent -= 2 self.install_succeeded = True + egg_info_dir = os.path.dirname(self.egg_info_path("pip.ini")) + + if egg_info_dir: + info_data = { + 'download': { + 'url': self.url, + } + } + + if self.requirement_line: + info_data['download']['requirement'] = self.requirement_line + else: + logger.warn("No requirement line recorded for %s" % self.name) + self._write_info_ini(egg_info_dir, info_data) + def _filter_install(self, line): level = logger.NOTIFY for regex in [r'^running .*', r'^writing .*', '^creating .*', '^[Cc]opying .*', @@ -981,6 +1027,7 @@ def prepare_files(self, finder, force_root_egg_info=False, bundle=False): url = Link(req_to_install.url) assert url if url: + req_to_install.url = url try: self.unpack_url(url, location, self.is_download) except HTTPError: diff --git a/tests/test_info_file.py b/tests/test_info_file.py new file mode 100644 index 00000000000..906eff5de46 --- /dev/null +++ b/tests/test_info_file.py @@ -0,0 +1,79 @@ +import os + +from pip.backwardcompat import ConfigParser +from pip.download import path_to_url2 +from tests.test_pip import here, reset_env, run_pip +from tests.path import Path + + +def test_index(): + """ + Test that the info.ini is written and works from an index (PyPI). + """ + env = reset_env() + run_pip('install', '-i http://pypi.python.org/simple/', 'INITools') + + egg_info_dir = None + for x in os.listdir(os.path.join(os.path.dirname(env.venv_path), env.site_packages)): + if x.startswith("INITools-") and x.endswith(".egg-info"): + egg_info_dir = os.path.join(os.path.dirname(env.venv_path), env.site_packages, x) + break + assert egg_info_dir is not None + + infofp = open(os.path.join(os.path.dirname(env.venv_path), env.site_packages, egg_info_dir, "info.ini")) + info = ConfigParser.RawConfigParser() + info.readfp(infofp) + infofp.close() + + assert info.has_section("download") + assert info.get("download", "url").startswith("http://pypi.python.org/packages/source/I/INITools/INITools") + assert info.get("download", "requirement") == "INITools" + + +def test_tarball(): + """ + Test that the info.ini is written and works from an tarball. + """ + env = reset_env() + run_pip('install', 'http://pypi.python.org/packages/source/I/INITools/INITools-0.3.1.tar.gz') + + egg_info_dir = None + for x in os.listdir(os.path.join(os.path.dirname(env.venv_path), env.site_packages)): + if x.startswith("INITools-") and x.endswith(".egg-info"): + egg_info_dir = os.path.join(os.path.dirname(env.venv_path), env.site_packages, x) + break + assert egg_info_dir is not None + + infofp = open(os.path.join(egg_info_dir, "info.ini")) + info = ConfigParser.RawConfigParser() + info.readfp(infofp) + infofp.close() + + assert info.has_section("download") + assert info.get("download", "url") == "http://pypi.python.org/packages/source/I/INITools/INITools-0.3.1.tar.gz" + assert info.get("download", "requirement") == "http://pypi.python.org/packages/source/I/INITools/INITools-0.3.1.tar.gz" + + +def test_editable(): + """ + Test that the info.ini is written and works from an editable. + """ + env = reset_env() + fspkg = path_to_url2(Path(here) / 'packages' / 'FSPkg') + run_pip('install', '-e', fspkg) + + egg_info_dir = None + for x in os.listdir(Path(here) / 'packages' / 'FSPkg'): + if x.startswith("FSPkg") and x.endswith(".egg-info"): + egg_info_dir = os.path.join(Path(here) / 'packages' / 'FSPkg', x) + break + assert egg_info_dir is not None + + infofp = open(os.path.join(egg_info_dir, "info.ini")) + info = ConfigParser.RawConfigParser() + info.readfp(infofp) + infofp.close() + + assert info.has_section("download") + assert info.get("download", "url") == "file:///Users/dstufft/projects/pip/tests/packages/FSPkg" + assert info.get("download", "requirement") == "--editable=file:///Users/dstufft/projects/pip/tests/packages/FSPkg"