Repository: readthedocs/readthedocs.org
Issue: Long project names fail with shell error.

I am seeing the following build error:

```
/bin/sh: 1: /home/docs/checkouts/readthedocs.org/user_builds/rackspace-cloud-core-infrastructure-services-user-guide/envs/latest/bin/sphinx-build: not found
```

I also see:

```
doc_builder
-----

Traceback (most recent call last):
  File "/home/docs/checkouts/readthedocs.org/readthedocs/projects/utils.py", line 85, in run
    stderr=subprocess.PIPE, env=environment)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory
```

No settings have changed on our project, so can someone please take a look and see what is preventing our builds?

Thanks!


Changed files:
readthedocs/doc_builder/backends/mkdocs.py, readthedocs/projects/models.py, readthedocs/rtd_tests/tests/test_builds.py, readthedocs/doc_builder/backends/sphinx.py, readthedocs/doc_builder/environments.py, readthedocs/projects/tasks.py

diff --git a/readthedocs/doc_builder/backends/mkdocs.py b/readthedocs/doc_builder/backends/mkdocs.py
index 869fb0253..dde624ef4 100644
--- a/readthedocs/doc_builder/backends/mkdocs.py
+++ b/readthedocs/doc_builder/backends/mkdocs.py
@@ -130,6 +130,7 @@ class BaseMkdocs(BaseBuilder):
     def build(self, **kwargs):
         checkout_path = self.project.checkout_path(self.version.slug)
         build_command = [
+            'python',
             self.project.venv_bin(version=self.version.slug, bin='mkdocs'),
             self.builder,
             '--clean',
@@ -137,7 +138,11 @@ class BaseMkdocs(BaseBuilder):
         ]
         if self.use_theme:
             build_command.extend(['--theme', 'readthedocs'])
-        cmd_ret = self.run(*build_command, cwd=checkout_path)
+        cmd_ret = self.run(
+            *build_command,
+            cwd=checkout_path,
+            bin_path=self.project.venv_bin(version=self.version.slug)
+        )
         return cmd_ret.successful
 
 
diff --git a/readthedocs/doc_builder/backends/sphinx.py b/readthedocs/doc_builder/backends/sphinx.py
index 1db47b128..9814c2354 100644
--- a/readthedocs/doc_builder/backends/sphinx.py
+++ b/readthedocs/doc_builder/backends/sphinx.py
@@ -126,6 +126,7 @@ class BaseSphinx(BaseBuilder):
         self.clean()
         project = self.project
         build_command = [
+            'python',
             project.venv_bin(version=self.version.slug, bin='sphinx-build'),
             '-T'
         ]
@@ -138,8 +139,11 @@ class BaseSphinx(BaseBuilder):
             '.',
             self.sphinx_build_dir
         ])
-        cmd_ret = self.run(*build_command,
-                           cwd=project.conf_dir(self.version.slug))
+        cmd_ret = self.run(
+            *build_command,
+            cwd=project.conf_dir(self.version.slug),
+            bin_path=project.venv_bin(version=self.version.slug)
+        )
         return cmd_ret.successful
 
 
@@ -232,13 +236,15 @@ class PdfBuilder(BaseSphinx):
 
         # Default to this so we can return it always.
         self.run(
+            'python',
             self.project.venv_bin(version=self.version.slug, bin='sphinx-build'),
             '-b', 'latex',
             '-D', 'language={lang}'.format(lang=self.project.language),
             '-d', '_build/doctrees',
             '.',
             '_build/latex',
-            cwd=cwd
+            cwd=cwd,
+            bin_path=self.project.venv_bin(version=self.version.slug)
         )
         latex_cwd = os.path.join(cwd, '_build', 'latex')
         tex_files = glob(os.path.join(latex_cwd, '*.tex'))
diff --git a/readthedocs/doc_builder/environments.py b/readthedocs/doc_builder/environments.py
index a341b8bf7..a97935d47 100644
--- a/readthedocs/doc_builder/environments.py
+++ b/readthedocs/doc_builder/environments.py
@@ -44,7 +44,8 @@ class BuildCommand(object):
 
     # TODO add short name here for reporting
     def __init__(self, command, cwd=None, shell=False, environment=None,
-                 combine_output=True, input_data=None, build_env=None):
+                 combine_output=True, input_data=None, build_env=None,
+                 bin_path=None):
         self.command = command
         self.shell = shell
         if cwd is None:
@@ -55,6 +56,7 @@ class BuildCommand(object):
             self.environment.update(environment)
         self.combine_output = combine_output
         self.build_env = build_env
+        self.bin_path = bin_path
         self.status = None
         self.input_data = input_data
         self.output = None
@@ -91,6 +93,10 @@ class BuildCommand(object):
             del environment['DJANGO_SETTINGS_MODULE']
         if 'PYTHONPATH' in environment:
             del environment['PYTHONPATH']
+        if self.bin_path is not None:
+            env_paths = environment.get('PATH', '').split(':')
+            env_paths.insert(0, self.bin_path)
+            environment['PATH'] = ':'.join(env_paths)
 
         try:
             proc = subprocess.Popen(
diff --git a/readthedocs/projects/models.py b/readthedocs/projects/models.py
index 9dc02450c..bedb99004 100644
--- a/readthedocs/projects/models.py
+++ b/readthedocs/projects/models.py
@@ -484,8 +484,17 @@ class Project(models.Model):
     # End symlink paths
     #
 
-    def venv_bin(self, version=LATEST, bin='python'):
-        return os.path.join(self.venv_path(version), 'bin', bin)
+    def venv_bin(self, version=LATEST, bin=None):
+        """Return path to the virtualenv bin path, or a specific binary
+
+        If ``bin`` is :py:data:`None`, then return the path to the virtual env
+        path, otherwise, return the path to the executable ``bin`` in the
+        virtual env ``bin`` path
+        """
+        parts = [self.venv_path(version), 'bin']
+        if bin is not None:
+            parts.append(bin)
+        return os.path.join(*parts)
 
     def full_doc_path(self, version=LATEST):
         """
diff --git a/readthedocs/projects/tasks.py b/readthedocs/projects/tasks.py
index 2566d34c2..9bc29dc85 100644
--- a/readthedocs/projects/tasks.py
+++ b/readthedocs/projects/tasks.py
@@ -267,6 +267,7 @@ class UpdateDocsTask(Task):
         ]
 
         cmd = [
+            'python',
             self.project.venv_bin(version=self.version.slug, bin='pip'),
             'install',
             '--use-wheel',
@@ -280,7 +281,10 @@ class UpdateDocsTask(Task):
             # --system-site-packages is used)
             cmd.append('-I')
         cmd.extend(requirements)
-        self.build_env.run(*cmd)
+        self.build_env.run(
+            *cmd,
+            bin_path=self.project.venv_bin(version=self.version.slug)
+        )
 
         # Handle requirements
         requirements_file_path = self.project.requirements_file
@@ -298,11 +302,13 @@ class UpdateDocsTask(Task):
 
         if requirements_file_path:
             self.build_env.run(
+                'python',
                 self.project.venv_bin(version=self.version.slug, bin='pip'),
                 'install',
                 '--exists-action=w',
                 '-r{0}'.format(requirements_file_path),
-                cwd=checkout_path
+                cwd=checkout_path,
+                bin_path=self.project.venv_bin(version=self.version.slug)
             )
 
         # Handle setup.py
@@ -311,21 +317,22 @@ class UpdateDocsTask(Task):
         if os.path.isfile(setup_path):
             if getattr(settings, 'USE_PIP_INSTALL', False):
                 self.build_env.run(
-                    self.project.venv_bin(version=self.version.slug,
-                                          bin='pip'),
+                    'python',
+                    self.project.venv_bin(version=self.version.slug, bin='pip'),
                     'install',
                     '--ignore-installed',
                     '.',
-                    cwd=checkout_path
+                    cwd=checkout_path,
+                    bin_path=self.project.venv_bin(version=self.version.slug)
                 )
             else:
                 self.build_env.run(
-                    self.project.venv_bin(version=self.version.slug,
-                                          bin='python'),
+                    'python',
                     'setup.py',
                     'install',
                     '--force',
-                    cwd=checkout_path
+                    cwd=checkout_path,
+                    bin_path=self.project.venv_bin(version=self.version.slug)
                 )
 
     def build_docs(self):
diff --git a/readthedocs/rtd_tests/tests/test_builds.py b/readthedocs/rtd_tests/tests/test_builds.py
index e78228111..b47ca8774 100644
--- a/readthedocs/rtd_tests/tests/test_builds.py
+++ b/readthedocs/rtd_tests/tests/test_builds.py
@@ -46,7 +46,8 @@ class BuildEnvironmentTests(TestCase):
         # Get command and check first part of command list is a call to sphinx
         self.assertEqual(self.mocks.popen.call_count, 1)
         cmd = self.mocks.popen.call_args_list[0][0]
-        self.assertRegexpMatches(cmd[0][0], r'sphinx-build')
+        self.assertRegexpMatches(cmd[0][0], r'python')
+        self.assertRegexpMatches(cmd[0][1], r'sphinx-build')
 
     def test_build_respects_pdf_flag(self):
         '''Build output format control'''