Skip to content

Commit ff43236

Browse files
committed
Encapsulate command execution inside build environment state objects
These objects will allow for multiple command execution patterns without changing the actual commands we're executing. This allows us to use Docker as a thin wrapper around container execution, instead of treating the Docker containers as a black box. This works towards per-command tracking of commands as well, giving us a far better UI around build state.
1 parent eaa5d5b commit ff43236

18 files changed

+888
-900
lines changed

readthedocs/builds/constants.py

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
from django.utils.translation import ugettext_lazy as _
22

3+
BUILD_STATE_TRIGGERED = 'triggered'
4+
BUILD_STATE_CLONING = 'cloning'
5+
BUILD_STATE_INSTALLING = 'installing'
6+
BUILD_STATE_BUILDING = 'building'
7+
BUILD_STATE_FINISHED = 'finished'
8+
39
BUILD_STATE = (
4-
('triggered', _('Triggered')),
5-
('cloning', _('Cloning')),
6-
('installing', _('Installing')),
7-
('building', _('Building')),
8-
('finished', _('Finished')),
10+
(BUILD_STATE_TRIGGERED, _('Triggered')),
11+
(BUILD_STATE_CLONING, _('Cloning')),
12+
(BUILD_STATE_INSTALLING, _('Installing')),
13+
(BUILD_STATE_BUILDING, _('Building')),
14+
(BUILD_STATE_FINISHED, _('Finished')),
915
)
1016

1117
BUILD_TYPES = (

readthedocs/core/management/commands/update_api.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ def handle(self, *args, **options):
2020
project_data = api.project(slug).get()
2121
p = tasks.make_api_project(project_data)
2222
log.info("Building %s" % p)
23-
tasks.update_docs(pk=p.pk)
23+
update_docs = tasks.UpdateDocsTask()
24+
update_docs.run(pk=p.pk)

readthedocs/core/management/commands/update_docker.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ def handle(self, *args, **options):
2020
project_data = api.project(slug).get()
2121
p = tasks.make_api_project(project_data)
2222
log.info("Building %s" % p)
23-
tasks.update_docs(pk=p.pk, docker=True)
23+
# TODO just move this to update_api.py
24+
update_docs = tasks.UpdateDocsTask()
25+
update_docs.run(pk=p.pk, docker=True)

readthedocs/core/management/commands/update_repos.py

+11-9
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ def handle(self, *args, **options):
4848
for version in Version.objects.filter(project__slug=slug,
4949
active=True,
5050
uploaded=False):
51-
tasks.update_docs(pk=version.project_id,
52-
record=False,
53-
version_pk=version.pk)
51+
update_docs = tasks.UpdateDocsTask()
52+
update_docs.run(pk=version.project_id,
53+
record=False,
54+
version_pk=version.pk)
5455
else:
5556
p = Project.all_objects.get(slug=slug)
5657
log.info("Building %s" % p)
@@ -60,14 +61,15 @@ def handle(self, *args, **options):
6061
log.info("Updating all versions")
6162
for version in Version.objects.filter(active=True,
6263
uploaded=False):
63-
tasks.update_docs(pk=version.project_id,
64-
record=record,
65-
force=force,
66-
version_pk=version.pk)
64+
update_tasks = tasks.UpdateDocsTask()
65+
update_tasks.run(pk=version.project_id,
66+
record=record,
67+
force=force,
68+
version_pk=version.pk)
6769
else:
6870
log.info("Updating all docs")
69-
tasks.update_docs_pull(record=record,
70-
force=force)
71+
update_docs = tasks.UpdateDocsTask()
72+
update_docs.run(record=record, force=force)
7173

7274
@property
7375
def help(self):
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
from readthedocs.builds.models import Version
21
from django.core.management.base import BaseCommand
3-
from readthedocs.projects.tasks import update_docs
2+
3+
from readthedocs.builds.models import Version
4+
from readthedocs.projects.tasks import UpdateDocsTask
45

56

67
class Command(BaseCommand):
@@ -10,5 +11,6 @@ class Command(BaseCommand):
1011

1112
def handle(self, *args, **options):
1213
for version in Version.objects.filter(active=True, built=False):
13-
update_docs(version.project_id, record=False,
14-
version_pk=version.pk)
14+
update_docs = UpdateDocsTask()
15+
update_docs.run(version.project_id, record=False,
16+
version_pk=version.pk)

readthedocs/core/utils/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def trigger_build(project, version=None, record=True, force=False, basic=False):
5959
An API to wrap the triggering of a build.
6060
"""
6161
# Avoid circular import
62-
from readthedocs.projects.tasks import update_docs
62+
from readthedocs.projects.tasks import UpdateDocsTask
6363

6464
if project.skip:
6565
return None
@@ -75,10 +75,12 @@ def trigger_build(project, version=None, record=True, force=False, basic=False):
7575
state='triggered',
7676
success=True,
7777
)
78+
update_docs = UpdateDocsTask()
7879
update_docs.delay(pk=project.pk, version_pk=version.pk, record=record,
7980
force=force, basic=basic, build_pk=build.pk)
8081
else:
8182
build = None
83+
update_docs = UpdateDocsTask()
8284
update_docs.delay(pk=project.pk, version_pk=version.pk, record=record,
8385
force=force, basic=basic)
8486

readthedocs/doc_builder/backends/mkdocs.py

+10-14
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from django.template import Context, loader as template_loader
88

99
from readthedocs.doc_builder.base import BaseBuilder, restoring_chdir
10-
from readthedocs.projects.utils import run
1110

1211
log = logging.getLogger(__name__)
1312

@@ -127,21 +126,18 @@ def append_conf(self, **kwargs):
127126
include_file.write(include_string)
128127
include_file.close()
129128

130-
@restoring_chdir
131129
def build(self, **kwargs):
132130
checkout_path = self.version.project.checkout_path(self.version.slug)
133-
# site_path = os.path.join(checkout_path, 'site')
134-
os.chdir(checkout_path)
135-
# Actual build
136-
build_command = (
137-
"{command} {builder} --clean --site-dir={build_dir} --theme=readthedocs"
138-
.format(
139-
command=self.version.project.venv_bin(version=self.version.slug, bin='mkdocs'),
140-
builder=self.builder,
141-
build_dir=self.build_dir,
142-
))
143-
results = run(build_command, shell=True)
144-
return results
131+
# TODO does this have the self.project?
132+
# TODO --theme fails on the JSON builder
133+
build_command = [
134+
self.project.venv_bin(version=self.version.slug, bin='mkdocs'),
135+
self.builder,
136+
'--clean',
137+
'--site-dir', self.build_dir,
138+
'--theme', 'readthedocs',
139+
]
140+
self.run(*build_command, cwd=checkout_path)
145141

146142

147143
class MkdocsHTML(BaseMkdocs):

readthedocs/doc_builder/backends/sphinx.py

+59-50
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from readthedocs.builds import utils as version_utils
1414
from readthedocs.doc_builder.base import BaseBuilder, restoring_chdir
15-
from readthedocs.projects.utils import run, safe_write
15+
from readthedocs.projects.utils import safe_write
1616
from readthedocs.projects.exceptions import ProjectImportError
1717
from readthedocs.restapi.client import api
1818

@@ -123,18 +123,20 @@ def append_conf(self, **kwargs):
123123
def build(self, **kwargs):
124124
self.clean()
125125
project = self.version.project
126-
os.chdir(project.conf_dir(self.version.slug))
127-
force_str = " -E " if self._force else ""
128-
build_command = "%s -T %s -b %s -d _build/doctrees -D language=%s . %s " % (
129-
project.venv_bin(version=self.version.slug,
130-
bin='sphinx-build'),
131-
force_str,
132-
self.sphinx_builder,
133-
project.language,
134-
self.sphinx_build_dir,
135-
)
136-
results = run(build_command, shell=True)
137-
return results
126+
build_command = [
127+
project.venv_bin(version=self.version.slug, bin='sphinx-build'),
128+
'-T'
129+
]
130+
if self._force:
131+
build_command.append('-E')
132+
build_command.extend([
133+
'-b', self.sphinx_builder,
134+
'-d', '_build/doctrees',
135+
'-D', 'language={lang}'.format(lang=project.language),
136+
'.',
137+
self.sphinx_build_dir
138+
])
139+
self.run(*build_command, cwd=project.conf_dir(self.version.slug))
138140

139141

140142
class HtmlBuilder(BaseSphinx):
@@ -212,7 +214,7 @@ def move(self, **kwargs):
212214
if from_globs:
213215
from_file = from_globs[0]
214216
to_file = os.path.join(self.target, "%s.epub" % self.version.project.slug)
215-
run('mv -f %s %s' % (from_file, to_file))
217+
self.run('mv', '-f', from_file, to_file)
216218

217219

218220
class PdfBuilder(BaseSphinx):
@@ -223,42 +225,49 @@ class PdfBuilder(BaseSphinx):
223225
@restoring_chdir
224226
def build(self, **kwargs):
225227
self.clean()
226-
project = self.version.project
227-
os.chdir(project.conf_dir(self.version.slug))
228+
cwd = self.project.conf_dir(self.version.slug)
228229
# Default to this so we can return it always.
229-
results = {}
230-
latex_results = run('%s -b latex -D language=%s -d _build/doctrees . _build/latex'
231-
% (project.venv_bin(version=self.version.slug,
232-
bin='sphinx-build'), project.language))
233-
234-
if latex_results[0] == 0:
235-
os.chdir('_build/latex')
236-
tex_files = glob('*.tex')
237-
238-
if tex_files:
239-
# Run LaTeX -> PDF conversions
240-
pdflatex_cmds = [('pdflatex -interaction=nonstopmode %s'
241-
% tex_file) for tex_file in tex_files]
242-
makeindex_cmds = [('makeindex -s python.ist %s.idx'
243-
% os.path.splitext(tex_file)[0]) for tex_file in tex_files]
244-
pdf_results = run(*pdflatex_cmds)
245-
ind_results = run(*makeindex_cmds)
246-
pdf_results = run(*pdflatex_cmds)
247-
else:
248-
pdf_results = (0, "No tex files found", "No tex files found")
249-
ind_results = (0, "No tex files found", "No tex files found")
250-
251-
results = [
252-
latex_results[0] + ind_results[0] + pdf_results[0],
253-
latex_results[1] + ind_results[1] + pdf_results[1],
254-
latex_results[2] + ind_results[2] + pdf_results[2],
255-
]
256-
pdf_match = PDF_RE.search(results[1])
257-
if pdf_match:
258-
self.pdf_file_name = pdf_match.group(1).strip()
259-
else:
260-
results = latex_results
261-
return results
230+
self.run(
231+
self.project.venv_bin(version=self.version.slug, bin='sphinx-build'),
232+
'-b', 'latex',
233+
'-D', 'language={lang}'.format(lang=self.project.language),
234+
'-d' ,'_build/doctrees',
235+
'.',
236+
'_build/latex',
237+
cwd=cwd
238+
)
239+
latex_cwd = os.path.join(cwd, '_build', 'latex')
240+
tex_files = glob(os.path.join(latex_cwd, '*.tex'))
241+
242+
if not tex_files:
243+
# TODO status code here
244+
raise BuildEnvironmentError('No TeX files were found')
245+
246+
# Run LaTeX -> PDF conversions
247+
pdflatex_cmds = [
248+
['pdflatex',
249+
'-interaction=nonstopmode',
250+
tex_file]
251+
for tex_file in tex_files]
252+
makeindex_cmds = [
253+
['makeindex',
254+
'-s',
255+
'python.ist',
256+
'{0}.idx'.format(os.path.splitext(tex_file)[0])]
257+
for tex_file in tex_files]
258+
for cmd in pdflatex_cmds:
259+
self.run(*cmd, cwd=latex_cwd)
260+
for cmd in makeindex_cmds:
261+
self.run(*cmd, cwd=latex_cwd)
262+
for cmd in pdflatex_cmds:
263+
self.run(*cmd, cwd=latex_cwd)
264+
265+
# TODO this ain't going to work without having access to the command
266+
# returned here. We should probably return the command from run
267+
# TODO pdf_match = PDF_RE.search(results[1])
268+
pdf_match = PDF_RE.search('')
269+
if pdf_match:
270+
self.pdf_file_name = pdf_match.group(1).strip()
262271

263272
def move(self, **kwargs):
264273
if not os.path.exists(self.target):
@@ -283,4 +292,4 @@ def move(self, **kwargs):
283292
from_file = None
284293
if from_file:
285294
to_file = os.path.join(self.target, "%s.pdf" % self.version.project.slug)
286-
run('mv -f %s %s' % (from_file, to_file))
295+
self.run('mv', '-f', from_file, to_file)

readthedocs/doc_builder/base.py

+12-2
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,16 @@ class BaseBuilder(object):
3030
_force = False
3131
# old_artifact_path = ..
3232

33-
def __init__(self, version, force=False):
33+
def __init__(self, version, force=False, build_env=None):
34+
self.project = build_env.project
35+
# TODO do we really need to push the version in here?
3436
self.version = version
3537
self._force = force
36-
self.target = self.version.project.artifact_path(version=self.version.slug, type=self.type)
38+
self.target = self.version.project.artifact_path(
39+
version=self.version.slug,
40+
type=self.type
41+
)
42+
self.build_env = build_env
3743

3844
def force(self, **kwargs):
3945
"""
@@ -115,3 +121,7 @@ def create_index(self, extension='md', **kwargs):
115121

116122
index_file.write(index_text.format(dir=docs_dir, ext=extension))
117123
index_file.close()
124+
125+
def run(self, *args, **kwargs):
126+
'''Proxy run to build environment'''
127+
self.build_env.run(*args, **kwargs)

0 commit comments

Comments
 (0)