1

2 20
import os
3 20
import pkgutil
4 20
import re
5 20
import shutil
6 20
import subprocess
7 20
import sys
8 20
from distutils.version import LooseVersion
9

10 20
from distutils import log
11

12 20
from sphinx.setup_command import BuildDoc as SphinxBuildDoc
13

14 20
SUBPROCESS_TEMPLATE = """
15
import os
16
import sys
17

18
{build_main}
19

20
os.chdir({srcdir!r})
21

22
{sys_path_inserts}
23

24
for builder in {builders!r}:
25
    retcode = build_main(argv={argv!r} + ['-b', builder, '.', os.path.join({output_dir!r}, builder)])
26
    if retcode != 0:
27
        sys.exit(retcode)
28
"""
29

30

31 20
def ensure_sphinx_astropy_installed():
32
    """
33
    Make sure that sphinx-astropy is available.
34
    """
35

36 20
    try:
37 20
        from sphinx_astropy import __version__ as sphinx_astropy_version  # noqa
38 0
    except ImportError:
39 0
        sphinx_astropy_version = None
40

41 20
    if (sphinx_astropy_version is None
42
            or LooseVersion(sphinx_astropy_version) < LooseVersion('1.2')):
43 0
        raise ImportError("sphinx-astropy 1.2 or later needs to be installed to build "
44
                            "the documentation.")
45

46

47 20
class AstropyBuildDocs(SphinxBuildDoc):
48
    """
49
    A version of the ``build_docs`` command that uses the version of Astropy
50
    that is built by the setup ``build`` command, rather than whatever is
51
    installed on the system.  To build docs against the installed version, run
52
    ``make html`` in the ``astropy/docs`` directory.
53
    """
54

55 20
    description = 'Build Sphinx documentation for Astropy environment'
56 20
    user_options = SphinxBuildDoc.user_options[:]
57 20
    user_options.append(
58
        ('warnings-returncode', 'w',
59
         'Parses the sphinx output and sets the return code to 1 if there '
60
         'are any warnings. Note that this will cause the sphinx log to '
61
         'only update when it completes, rather than continuously as is '
62
         'normally the case.'))
63 20
    user_options.append(
64
        ('clean-docs', 'l',
65
         'Completely clean previous builds, including '
66
         'automodapi-generated files before building new ones'))
67 20
    user_options.append(
68
        ('no-intersphinx', 'n',
69
         'Skip intersphinx, even if conf.py says to use it'))
70 20
    user_options.append(
71
        ('open-docs-in-browser', 'o',
72
         'Open the docs in a browser (using the webbrowser module) if the '
73
         'build finishes successfully.'))
74 20
    user_options.append(
75
        ('parallel=', 'j',
76
         'Build the docs in parallel on the specified number of '
77
         'processes. If "auto", all the cores on the machine will be '
78
         'used.'))
79

80 20
    boolean_options = SphinxBuildDoc.boolean_options[:]
81 20
    boolean_options.append('warnings-returncode')
82 20
    boolean_options.append('clean-docs')
83 20
    boolean_options.append('no-intersphinx')
84 20
    boolean_options.append('open-docs-in-browser')
85

86 20
    _self_iden_rex = re.compile(r"self\.([^\d\W][\w]+)", re.UNICODE)
87

88 20
    def initialize_options(self):
89 20
        SphinxBuildDoc.initialize_options(self)
90 20
        self.clean_docs = False
91 20
        self.no_intersphinx = False
92 20
        self.open_docs_in_browser = False
93 20
        self.warnings_returncode = False
94 20
        self.traceback = False
95 20
        self.parallel = None
96

97 20
    def finalize_options(self):
98

99
        # This has to happen before we call the parent class's finalize_options
100 20
        if self.build_dir is None:
101 20
            self.build_dir = 'docs/_build'
102

103 20
        SphinxBuildDoc.finalize_options(self)
104

105
        # Clear out previous sphinx builds, if requested
106 20
        if self.clean_docs:
107

108 20
            dirstorm = [os.path.join(self.source_dir, 'api'),
109
                        os.path.join(self.source_dir, 'generated')]
110

111 20
            dirstorm.append(self.build_dir)
112

113 20
            for d in dirstorm:
114 20
                if os.path.isdir(d):
115 0
                    log.info('Cleaning directory ' + d)
116 0
                    shutil.rmtree(d)
117
                else:
118 20
                    log.info('Not cleaning directory ' + d + ' because '
119
                             'not present or not a directory')
120

121 20
    def run(self):
122

123
        # TODO: Break this method up into a few more subroutines and
124
        # document them better
125 20
        import webbrowser
126

127 20
        from urllib.request import pathname2url
128

129
        # This is used at the very end of `run` to decide if sys.exit should
130
        # be called. If it's None, it won't be.
131 20
        retcode = None
132

133
        # Now make sure Astropy is built and determine where it was built
134 20
        build_cmd = self.reinitialize_command('build')
135 20
        build_cmd.inplace = 0
136 20
        self.run_command('build')
137 20
        build_cmd = self.get_finalized_command('build')
138 20
        build_cmd_path = os.path.abspath(build_cmd.build_lib)
139

140 20
        ah_importer = pkgutil.get_importer('astropy_helpers')
141 20
        if ah_importer is None:
142 20
            ah_path = '.'
143
        else:
144 0
            ah_path = os.path.abspath(ah_importer.path)
145

146 20
        build_main = 'from sphinx.cmd.build import build_main'
147

148
        # We need to make sure sphinx-astropy is installed
149 20
        ensure_sphinx_astropy_installed()
150

151 20
        sys_path_inserts = [build_cmd_path, ah_path]
152 20
        sys_path_inserts = os.linesep.join(['sys.path.insert(0, {0!r})'.format(path) for path in sys_path_inserts])
153

154 20
        argv = []
155

156 20
        if self.warnings_returncode:
157 20
            argv.append('-W')
158

159 20
        if self.no_intersphinx:
160 0
            argv.extend(['-D', 'disable_intersphinx=1'])
161

162
        # We now need to adjust the flags based on the parent class's options
163

164 20
        if self.fresh_env:
165 0
            argv.append('-E')
166

167 20
        if self.all_files:
168 0
            argv.append('-a')
169

170 20
        if getattr(self, 'pdb', False):
171 0
            argv.append('-P')
172

173 20
        if getattr(self, 'nitpicky', False):
174 0
            argv.append('-n')
175

176 20
        if self.traceback:
177 0
            argv.append('-T')
178

179
        # The default verbosity level is 1, so in that case we just don't add a flag
180 20
        if self.verbose == 0:
181 0
            argv.append('-q')
182 20
        elif self.verbose > 1:
183 0
            argv.append('-v')
184

185 20
        if self.parallel is not None:
186 20
            argv.append(f'-j={self.parallel}')
187

188 20
        if isinstance(self.builder, str):
189 0
            builders = [self.builder]
190
        else:
191 20
            builders = self.builder
192

193 20
        subproccode = SUBPROCESS_TEMPLATE.format(build_main=build_main,
194
                                                 srcdir=self.source_dir,
195
                                                 sys_path_inserts=sys_path_inserts,
196
                                                 builders=builders,
197
                                                 argv=argv,
198
                                                 output_dir=os.path.abspath(self.build_dir))
199

200 20
        log.debug('Starting subprocess of {0} with python code:\n{1}\n'
201
                  '[CODE END])'.format(sys.executable, subproccode))
202

203 20
        proc = subprocess.Popen([sys.executable], stdin=subprocess.PIPE)
204 20
        proc.communicate(subproccode.encode('utf-8'))
205 20
        if proc.returncode != 0:
206 0
            retcode = proc.returncode
207

208 20
        if retcode is None:
209 20
            if self.open_docs_in_browser:
210 0
                if self.builder == 'html':
211 0
                    absdir = os.path.abspath(self.builder_target_dir)
212 0
                    index_path = os.path.join(absdir, 'index.html')
213 0
                    fileurl = 'file://' + pathname2url(index_path)
214 0
                    webbrowser.open(fileurl)
215
                else:
216 0
                    log.warn('open-docs-in-browser option was given, but '
217
                             'the builder is not html! Ignoring.')
218

219
        # Here we explicitly check proc.returncode since we only want to output
220
        # this for cases where the return code really wasn't 0.
221 20
        if proc.returncode:
222 0
            log.warn('Sphinx Documentation subprocess failed with return '
223
                     'code ' + str(proc.returncode))
224

225 20
        if retcode is not None:
226
            # this is potentially dangerous in that there might be something
227
            # after the call to `setup` in `setup.py`, and exiting here will
228
            # prevent that from running.  But there's no other apparent way
229
            # to signal what the return code should be.
230 0
            sys.exit(retcode)
231

232

233
class AstropyBuildSphinx(AstropyBuildDocs):  # pragma: no cover
234
    def run(self):
235
        AstropyBuildDocs.run(self)

Read our documentation on viewing source code .

Loading