Skip to content

Commit 03e9fb1

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 8871446 commit 03e9fb1

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
@@ -262,6 +262,7 @@ def setup_environment(self):
262262
]
263263

264264
cmd = [
265+
'python',
265266
self.project.venv_bin(version=self.version.slug, bin='pip'),
266267
'install',
267268
'--use-wheel',
@@ -275,7 +276,10 @@ def setup_environment(self):
275276
# --system-site-packages is used)
276277
cmd.append('-I')
277278
cmd.extend(requirements)
278-
self.build_env.run(*cmd)
279+
self.build_env.run(
280+
*cmd,
281+
bin_path=self.project.venv_bin(version=self.version.slug, bin=None)
282+
)
279283

280284
# Handle requirements
281285
requirements_file_path = self.project.requirements_file
@@ -293,11 +297,14 @@ def setup_environment(self):
293297

294298
if requirements_file_path:
295299
self.build_env.run(
300+
'python',
296301
self.project.venv_bin(version=self.version.slug, bin='pip'),
297302
'install',
298303
'--exists-action=w',
299304
'-r{0}'.format(requirements_file_path),
300-
cwd=checkout_path
305+
cwd=checkout_path,
306+
bin_path=self.project.venv_bin(version=self.version.slug,
307+
bin=None)
301308
)
302309

303310
# Handle setup.py
@@ -306,21 +313,24 @@ def setup_environment(self):
306313
if os.path.isfile(setup_path):
307314
if getattr(settings, 'USE_PIP_INSTALL', False):
308315
self.build_env.run(
309-
self.project.venv_bin(version=self.version.slug,
310-
bin='pip'),
316+
'python',
317+
self.project.venv_bin(version=self.version.slug, bin='pip'),
311318
'install',
312319
'--ignore-installed',
313320
'.',
314-
cwd=checkout_path
321+
cwd=checkout_path,
322+
bin_path=self.project.venv_bin(version=self.version.slug,
323+
bin=None)
315324
)
316325
else:
317326
self.build_env.run(
318-
self.project.venv_bin(version=self.version.slug,
319-
bin='python'),
327+
'python',
320328
'setup.py',
321329
'install',
322330
'--force',
323-
cwd=checkout_path
331+
cwd=checkout_path,
332+
bin_path=self.project.venv_bin(version=self.version.slug,
333+
bin=None)
324334
)
325335

326336
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)