diff --git a/readthedocs/core/symlink.py b/readthedocs/core/symlink.py index f3428d95d4a..9f119cb604b 100644 --- a/readthedocs/core/symlink.py +++ b/readthedocs/core/symlink.py @@ -52,21 +52,27 @@ fabric -> rtd-builds/fabric/en/latest/ # single version """ -from __future__ import absolute_import, unicode_literals -from builtins import object +from __future__ import ( + absolute_import, + division, + print_function, + unicode_literals, +) + +import logging import os import shutil -import logging from collections import OrderedDict +from builtins import object from django.conf import settings from readthedocs.builds.models import Version -from readthedocs.core.utils.extend import SettingsOverrideObject from readthedocs.core.utils import safe_makedirs, safe_unlink +from readthedocs.core.utils.extend import SettingsOverrideObject +from readthedocs.doc_builder.environments import LocalEnvironment from readthedocs.projects import constants from readthedocs.projects.models import Domain -from readthedocs.projects.utils import run log = logging.getLogger(__name__) @@ -83,6 +89,7 @@ def __init__(self, project): self.subproject_root = os.path.join( self.project_root, 'projects' ) + self.environment = LocalEnvironment(project) self.sanity_check() def sanity_check(self): @@ -146,17 +153,27 @@ def symlink_cnames(self, domain=None): else: domains = Domain.objects.filter(project=self.project) for dom in domains: - log_msg = 'Symlinking CNAME: {0} -> {1}'.format(dom.domain, self.project.slug) - log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, - version='', msg=log_msg)) + log_msg = 'Symlinking CNAME: {} -> {}'.format( + dom.domain, self.project.slug + ) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', msg=log_msg + ) + ) # CNAME to doc root symlink = os.path.join(self.CNAME_ROOT, dom.domain) - run(['ln', '-nsf', self.project_root, symlink]) + self.environment.run('ln', '-nsf', self.project_root, symlink) # Project symlink - project_cname_symlink = os.path.join(self.PROJECT_CNAME_ROOT, dom.domain) - run(['ln', '-nsf', self.project.doc_path, project_cname_symlink]) + project_cname_symlink = os.path.join( + self.PROJECT_CNAME_ROOT, dom.domain + ) + self.environment.run( + 'ln', '-nsf', self.project.doc_path, project_cname_symlink + ) def remove_symlink_cname(self, domain): """Remove CNAME symlink.""" @@ -201,10 +218,12 @@ def symlink_subprojects(self): # TODO this should use os.symlink, not a call to shell. For now, # this passes command as a list to be explicit about escaping # characters like spaces. - status, _, stderr = run(['ln', '-nsf', docs_dir, symlink]) - if status > 0: - log.error('Could not symlink path: status=%d error=%s', - status, stderr) + result = self.environment.run('ln', '-nsf', docs_dir, symlink) + if result.exit_code > 0: + log.error( + 'Could not symlink path: status=%d error=%s', + result.exit_code, result.error + ) # Remove old symlinks if os.path.exists(self.subproject_root): @@ -233,12 +252,16 @@ def symlink_translations(self): for (language, slug) in list(translations.items()): - log_msg = 'Symlinking translation: {0}->{1}'.format(language, slug) - log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, - version='', msg=log_msg)) + log_msg = 'Symlinking translation: {}->{}'.format(language, slug) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', msg=log_msg + ) + ) symlink = os.path.join(self.project_root, language) docs_dir = os.path.join(self.WEB_ROOT, slug, language) - run(['ln', '-nsf', docs_dir, symlink]) + self.environment.run('ln', '-nsf', docs_dir, symlink) # Remove old symlinks for lang in os.listdir(self.project_root): @@ -268,9 +291,13 @@ def symlink_single_version(self): # Create symlink if version is not None: - docs_dir = os.path.join(settings.DOCROOT, self.project.slug, - 'rtd-builds', version.slug) - run(['ln', '-nsf', docs_dir, symlink]) + docs_dir = os.path.join( + settings.DOCROOT, + self.project.slug, + 'rtd-builds', + version.slug + ) + self.environment.run('ln', '-nsf', docs_dir, symlink) def symlink_versions(self): """ @@ -280,7 +307,9 @@ def symlink_versions(self): HOME/user_builds//rtd-builds/ """ versions = set() - version_dir = os.path.join(self.WEB_ROOT, self.project.slug, self.project.language) + version_dir = os.path.join( + self.WEB_ROOT, self.project.slug, self.project.language + ) # Include active public versions, # as well as public versions that are built but not active, for archived versions version_queryset = self.get_version_queryset() @@ -289,11 +318,21 @@ def symlink_versions(self): safe_makedirs(version_dir) for version in version_queryset: log_msg = 'Symlinking Version: {}'.format(version) - log.info(constants.LOG_TEMPLATE.format(project=self.project.slug, - version='', msg=log_msg)) + log.info( + constants.LOG_TEMPLATE.format( + project=self.project.slug, + version='', + msg=log_msg + ) + ) symlink = os.path.join(version_dir, version.slug) - docs_dir = os.path.join(settings.DOCROOT, self.project.slug, 'rtd-builds', version.slug) - run(['ln', '-nsf', docs_dir, symlink]) + docs_dir = os.path.join( + settings.DOCROOT, + self.project.slug, + 'rtd-builds', + version.slug + ) + self.environment.run('ln', '-nsf', docs_dir, symlink) versions.add(version.slug) # Remove old symlinks diff --git a/readthedocs/projects/utils.py b/readthedocs/projects/utils.py index aba4f3d413d..840dd17482a 100644 --- a/readthedocs/projects/utils.py +++ b/readthedocs/projects/utils.py @@ -2,16 +2,16 @@ """Utility functions used by projects.""" from __future__ import ( - absolute_import, division, print_function, unicode_literals) + absolute_import, + division, + print_function, + unicode_literals, +) -import fnmatch import logging import os -import subprocess -import traceback -import six -from builtins import object, open +from builtins import open from django.conf import settings log = logging.getLogger(__name__) @@ -32,82 +32,6 @@ def version_from_slug(slug, version): return v -def find_file(filename): - """ - Recursively find matching file from the current working path. - - :param file: Filename to match - :returns: A list of matching filenames. - """ - matches = [] - for root, __, filenames in os.walk('.'): - for match in fnmatch.filter(filenames, filename): - matches.append(os.path.join(root, match)) - return matches - - -def run(*commands): - """ - Run one or more commands. - - Each argument in `commands` can be passed as a string or as a list. Passing - as a list is the preferred method, as space escaping is more explicit and it - avoids the need for executing anything in a shell. - - If more than one command is given, then this is equivalent to - chaining them together with ``&&``; if all commands succeed, then - ``(status, out, err)`` will represent the last successful command. - If one command failed, then ``(status, out, err)`` will represent - the failed command. - - :returns: ``(status, out, err)`` - """ - environment = os.environ.copy() - environment['READTHEDOCS'] = 'True' - if 'DJANGO_SETTINGS_MODULE' in environment: - del environment['DJANGO_SETTINGS_MODULE'] - if 'PYTHONPATH' in environment: - del environment['PYTHONPATH'] - # Remove PYTHONHOME env variable if set, otherwise pip install of requirements - # into virtualenv will install incorrectly - if 'PYTHONHOME' in environment: - del environment['PYTHONHOME'] - cwd = os.getcwd() - if not commands: - raise ValueError('run() requires one or more command-line strings') - - for command in commands: - # If command is a string, split it up by spaces to pass into Popen. - # Otherwise treat the command as an iterable. - if isinstance(command, six.string_types): - run_command = command.split() - else: - try: - run_command = list(command) - command = ' '.join(command) - except TypeError: - run_command = command - log.debug('Running command: cwd=%s command=%s', cwd, command) - try: - p = subprocess.Popen( - run_command, - cwd=cwd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=environment, - ) - - out, err = p.communicate() - ret = p.returncode - except OSError: - out = '' - err = traceback.format_exc() - ret = -1 - log.exception('Command failed') - - return (ret, out, err) - - def safe_write(filename, contents): """ Normalize and write to filename. @@ -126,9 +50,3 @@ def safe_write(filename, contents): with open(filename, 'w', encoding='utf-8', errors='ignore') as fh: fh.write(contents) fh.close() - - -class DictObj(object): - - def __getattr__(self, attr): - return self.__dict__.get(attr)