Repository: pypa/pip

Issue:
install of identical code fails if it's installing from a VCS subdirectory
[carrot](https://github.com/ask/carrot) is a AMQP messaging framework, and an editable install be done with pip:

```
pip install -e "git+https://github.com/ask/carrot.git#egg=carrot"
```

However, if I move the whole source code to a different subdirectory, you cannot install it using pip 1.5+'s new 'subdirectory' option.

Here's [a recent fork of carrot](https://github.com/rory/carrot), [the only change I made](https://github.com/rory/carrot/commit/91afc45d23c9c886b09df61122e526d0f530dd2c) was that I moved everything into `my-carrot-subdirectory`. Now when I try to install from that subdirectory I get this error:

```
$ pip install -e "git+https://github.com/rory/carrot.git#egg=carrot&subdirectory=my-carrot-subdirectory"
Obtaining carrot from git+https://github.com/rory/carrot.git#egg=carrot&subdirectory=my-carrot-subdirectory
  Cloning https://github.com/rory/carrot.git to /tmp/ve/src/carrot
  Running setup.py (path:/tmp/ve/src/carrot/my-carrot-subdirectory/setup.py) egg_info for package carrot
    Traceback (most recent call last):
      File "<string>", line 17, in <module>
      File "/tmp/ve/src/carrot/my-carrot-subdirectory/setup.py", line 17, in <module>
        import carrot
    ImportError: No module named carrot
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):

  File "<string>", line 17, in <module>

  File "/tmp/ve/src/carrot/my-carrot-subdirectory/setup.py", line 17, in <module>

    import carrot

ImportError: No module named carrot

----------------------------------------
Cleaning up...
Command python setup.py egg_info failed with error code 1 in /tmp/ve/src/carrot
Storing debug log for failure in /home/rory/.pip/pip.log
```

It does not make sense that the same code should install fine without a subdirectory, but will fail with it.



Discussion:
As a side note, this can be avoided by avoiding the brittle practice of importing the package you're trying to install. i.e, `import carrot`.

It creates exactly the sort of problems you see here, e.g when `setup.py` isn't run from the current directory.

Maybe pip should cd to the subdirectory first?


Resolving patch:
diff --git a/pip/req/req_install.py b/pip/req/req_install.py
--- a/pip/req/req_install.py
+++ b/pip/req/req_install.py
@@ -251,9 +251,7 @@ def url_name(self):
     @property
     def setup_py(self):
         try:
-            import setuptools
-            # Small hack to make flake8 not complain about an unused import
-            setuptools
+            import setuptools  # noqa
         except ImportError:
             # Setuptools is not available
             raise InstallationError(
@@ -318,9 +316,13 @@ def run_egg_info(self, force_root_egg_info=False):
                 if not os.path.exists(egg_info_dir):
                     os.makedirs(egg_info_dir)
                 egg_base_option = ['--egg-base', 'pip-egg-info']
+            cwd = self.source_dir
+            if self.editable_options and \
+                    'subdirectory' in self.editable_options:
+                cwd = os.path.join(cwd, self.editable_options['subdirectory'])
             call_subprocess(
                 egg_info_cmd + egg_base_option,
-                cwd=self.source_dir,
+                cwd=cwd,
                 filter_stdout=self._filter_install,
                 show_stdout=False,
                 command_level=logger.VERBOSE_DEBUG,
@@ -826,6 +828,10 @@ def install_editable(self, install_options, global_options=()):
         logger.indent += 2
         try:
             ## FIXME: should we do --install-headers here too?
+            cwd = self.source_dir
+            if self.editable_options and \
+                    'subdirectory' in self.editable_options:
+                cwd = os.path.join(cwd, self.editable_options['subdirectory'])
             call_subprocess(
                 [
                     sys.executable,
@@ -838,7 +844,7 @@ def install_editable(self, install_options, global_options=()):
                 + ['develop', '--no-deps']
                 + list(install_options),
 
-                cwd=self.source_dir, filter_stdout=self._filter_install,
+                cwd=cwd, filter_stdout=self._filter_install,
                 show_stdout=False)
         finally:
             logger.indent -= 2


Test patch:
diff --git a/tests/functional/test_install_reqs.py b/tests/functional/test_install_reqs.py
--- a/tests/functional/test_install_reqs.py
+++ b/tests/functional/test_install_reqs.py
@@ -132,11 +132,11 @@ def test_respect_order_in_requirements_file(script, data):
 
 def test_install_local_editable_with_subdirectory(script):
     version_pkg_path = _create_test_package_with_subdirectory(script,
-                                                              'version_subpkg')
+                                                              'version_subdir')
     result = script.pip(
         'install', '-e',
-        '%s#egg=version_subpkg&subdirectory=version_subpkg' %
+        '%s#egg=version_subpkg&subdirectory=version_subdir' %
         ('git+file://%s' % version_pkg_path,)
     )
 
-    result.assert_installed('version-subpkg')
+    result.assert_installed('version-subpkg', sub_dir='version_subdir')
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -130,11 +130,14 @@ def __str__(self):
 
     def assert_installed(self, pkg_name, editable=True, with_files=[],
                          without_files=[], without_egg_link=False,
-                         use_user_site=False):
+                         use_user_site=False, sub_dir=False):
         e = self.test_env
 
         if editable:
             pkg_dir = e.venv / 'src' / pkg_name.lower()
+            # If package was installed in a sub directory
+            if sub_dir:
+                pkg_dir = pkg_dir / sub_dir
         else:
             without_egg_link = True
             pkg_dir = e.site_packages / pkg_name
@@ -159,18 +162,19 @@ def assert_installed(self, pkg_name, editable=True, with_files=[],
 
             egg_link_file = self.files_created[egg_link_path]
 
-            if not (  # FIXME: I don't understand why there's a trailing . here
-                    egg_link_file.bytes.endswith('.')
-                    and egg_link_file.bytes[:-1].strip().endswith(pkg_dir)):
+            # FIXME: I don't understand why there's a trailing . here
+            if not (egg_link_file.bytes.endswith('\n.')
+                    and egg_link_file.bytes[:-2].endswith(pkg_dir)):
                 raise TestFailure(textwrap.dedent(u('''\
-                Incorrect egg_link file %r
-                Expected ending: %r
-                ------- Actual contents -------
-                %s
-                -------------------------------''' % (
+                    Incorrect egg_link file %r
+                    Expected ending: %r
+                    ------- Actual contents -------
+                    %s
+                    -------------------------------''' % (
                     egg_link_file,
-                    pkg_dir + u('\n.'),
-                    egg_link_file.bytes))))
+                    pkg_dir + '\n.',
+                    repr(egg_link_file.bytes))
+                )))
 
         if use_user_site:
             pth_file = e.user_site / 'easy-install.pth'