Skip to content

Commit 23d2859

Browse files
committed
Call python scripts as argument to virtualenv python command
This fixes #994, where the 127 character limit on shebang line lengths was triggering failures of commands inside virtualenvs that had long names or long branch names. This PR adds some path environment variable handling to allow for shorter commands, and makes all python script call wrapped by a call to the virtual env symlinked python binary.
1 parent b977191 commit 23d2859

File tree

6 files changed

+50
-14
lines changed

6 files changed

+50
-14
lines changed

readthedocs/doc_builder/backends/mkdocs.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -130,14 +130,19 @@ def append_conf(self, **kwargs):
130130
def build(self, **kwargs):
131131
checkout_path = self.project.checkout_path(self.version.slug)
132132
build_command = [
133+
'python',
133134
self.project.venv_bin(version=self.version.slug, bin='mkdocs'),
134135
self.builder,
135136
'--clean',
136137
'--site-dir', self.build_dir,
137138
]
138139
if self.use_theme:
139140
build_command.extend(['--theme', 'readthedocs'])
140-
cmd_ret = self.run(*build_command, cwd=checkout_path)
141+
cmd_ret = self.run(
142+
*build_command,
143+
cwd=checkout_path,
144+
bin_path=self.project.venv_bin(version=self.version.slug, bin=None)
145+
)
141146
return cmd_ret.successful
142147

143148

readthedocs/doc_builder/backends/sphinx.py

+9-3
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ def build(self, **kwargs):
126126
self.clean()
127127
project = self.project
128128
build_command = [
129+
'python',
129130
project.venv_bin(version=self.version.slug, bin='sphinx-build'),
130131
'-T'
131132
]
@@ -138,8 +139,11 @@ def build(self, **kwargs):
138139
'.',
139140
self.sphinx_build_dir
140141
])
141-
cmd_ret = self.run(*build_command,
142-
cwd=project.conf_dir(self.version.slug))
142+
cmd_ret = self.run(
143+
*build_command,
144+
cwd=project.conf_dir(self.version.slug),
145+
bin_path=project.venv_bin(version=self.version.slug, bin=None)
146+
)
143147
return cmd_ret.successful
144148

145149

@@ -232,13 +236,15 @@ def build(self, **kwargs):
232236

233237
# Default to this so we can return it always.
234238
self.run(
239+
'python',
235240
self.project.venv_bin(version=self.version.slug, bin='sphinx-build'),
236241
'-b', 'latex',
237242
'-D', 'language={lang}'.format(lang=self.project.language),
238243
'-d', '_build/doctrees',
239244
'.',
240245
'_build/latex',
241-
cwd=cwd
246+
cwd=cwd,
247+
bin_path=self.project.venv_bin(version=self.version.slug, bin=None)
242248
)
243249
latex_cwd = os.path.join(cwd, '_build', 'latex')
244250
tex_files = glob(os.path.join(latex_cwd, '*.tex'))

readthedocs/doc_builder/environments.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ class BuildCommand(object):
4444

4545
# TODO add short name here for reporting
4646
def __init__(self, command, cwd=None, shell=False, environment=None,
47-
combine_output=True, input_data=None, build_env=None):
47+
combine_output=True, input_data=None, build_env=None,
48+
bin_path=None):
4849
self.command = command
4950
self.shell = shell
5051
if cwd is None:
@@ -55,6 +56,7 @@ def __init__(self, command, cwd=None, shell=False, environment=None,
5556
self.environment.update(environment)
5657
self.combine_output = combine_output
5758
self.build_env = build_env
59+
self.bin_path = bin_path
5860
self.status = None
5961
self.input_data = input_data
6062
self.output = None
@@ -91,6 +93,10 @@ def run(self):
9193
del environment['DJANGO_SETTINGS_MODULE']
9294
if 'PYTHONPATH' in environment:
9395
del environment['PYTHONPATH']
96+
if self.bin_path is not None:
97+
env_paths = environment.get('PATH', '').split(':')
98+
env_paths.insert(0, self.bin_path)
99+
environment['PATH'] = ':'.join(env_paths)
94100

95101
try:
96102
proc = subprocess.Popen(

readthedocs/projects/models.py

+8
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,14 @@ def single_version_symlink_path(self):
485485
#
486486

487487
def venv_bin(self, version=LATEST, bin='python'):
488+
"""Return path to the virtualenv bin path, or a specific binary
489+
490+
By default, return the path to the ``python`` binary in the virtual
491+
environment path. If ``bin`` is :py:data:`None`, then return the path to
492+
the virtual env path.
493+
"""
494+
if bin is None:
495+
bin = ''
488496
return os.path.join(self.venv_path(version), 'bin', bin)
489497

490498
def full_doc_path(self, version=LATEST):

readthedocs/projects/tasks.py

+18-8
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ def setup_environment(self):
267267
]
268268

269269
cmd = [
270+
'python',
270271
self.project.venv_bin(version=self.version.slug, bin='pip'),
271272
'install',
272273
'--use-wheel',
@@ -280,7 +281,10 @@ def setup_environment(self):
280281
# --system-site-packages is used)
281282
cmd.append('-I')
282283
cmd.extend(requirements)
283-
self.build_env.run(*cmd)
284+
self.build_env.run(
285+
*cmd,
286+
bin_path=self.project.venv_bin(version=self.version.slug, bin=None)
287+
)
284288

285289
# Handle requirements
286290
requirements_file_path = self.project.requirements_file
@@ -298,11 +302,14 @@ def setup_environment(self):
298302

299303
if requirements_file_path:
300304
self.build_env.run(
305+
'python',
301306
self.project.venv_bin(version=self.version.slug, bin='pip'),
302307
'install',
303308
'--exists-action=w',
304309
'-r{0}'.format(requirements_file_path),
305-
cwd=checkout_path
310+
cwd=checkout_path,
311+
bin_path=self.project.venv_bin(version=self.version.slug,
312+
bin=None)
306313
)
307314

308315
# Handle setup.py
@@ -311,21 +318,24 @@ def setup_environment(self):
311318
if os.path.isfile(setup_path):
312319
if getattr(settings, 'USE_PIP_INSTALL', False):
313320
self.build_env.run(
314-
self.project.venv_bin(version=self.version.slug,
315-
bin='pip'),
321+
'python',
322+
self.project.venv_bin(version=self.version.slug, bin='pip'),
316323
'install',
317324
'--ignore-installed',
318325
'.',
319-
cwd=checkout_path
326+
cwd=checkout_path,
327+
bin_path=self.project.venv_bin(version=self.version.slug,
328+
bin=None)
320329
)
321330
else:
322331
self.build_env.run(
323-
self.project.venv_bin(version=self.version.slug,
324-
bin='python'),
332+
'python',
325333
'setup.py',
326334
'install',
327335
'--force',
328-
cwd=checkout_path
336+
cwd=checkout_path,
337+
bin_path=self.project.venv_bin(version=self.version.slug,
338+
bin=None)
329339
)
330340

331341
def build_docs(self):

readthedocs/rtd_tests/tests/test_builds.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ def test_build(self):
4646
# Get command and check first part of command list is a call to sphinx
4747
self.assertEqual(self.mocks.popen.call_count, 1)
4848
cmd = self.mocks.popen.call_args_list[0][0]
49-
self.assertRegexpMatches(cmd[0][0], r'sphinx-build')
49+
self.assertRegexpMatches(cmd[0][0], r'python')
50+
self.assertRegexpMatches(cmd[0][1], r'sphinx-build')
5051

5152
def test_build_respects_pdf_flag(self):
5253
'''Build output format control'''

0 commit comments

Comments
 (0)