source: rtems-docs/common/waf.py @ 8e8094a

5am
Last change on this file since 8e8094a was 8e8094a, checked in by Chris Johns <chrisj@…>, on Oct 13, 2017 at 12:46:00 AM

build: Add extra source to the sphinx build.

Updates #3177.

  • Property mode set to 100644
File size: 15.3 KB
Line 
1import sys, os, re
2from waflib.Build import BuildContext
3
4import latex
5
6sphinx_min_version = (1, 3)
7
8def build_date():
9    import datetime
10    now = datetime.date.today()
11    m = now.strftime('%B')
12    y = now.strftime('%Y')
13    if now.day % 10 == 1:
14        s = 'st'
15    elif now.day % 10 == 2:
16        s = 'nd'
17    elif now.day == 3:
18        s = 'rd'
19    else:
20        s = 'th'
21    d = '%2d%s' % (now.day, s)
22    return '%s %s %s' % (d, m, y)
23
24def version_cmdline(ctx):
25    return '-Drelease="%s" -Dversion="%s"' % (ctx.env.VERSION, ctx.env.VERSION)
26
27def sphinx_cmdline(ctx, build_type, conf_dir, doctrees, source_dir, output_dir):
28    rule = "${BIN_SPHINX_BUILD} %s -b %s -c %s %s -d %s %s %s ${SRC}" % \
29           (sphinx_verbose(ctx), build_type, conf_dir, version_cmdline(ctx),
30            doctrees, source_dir, output_dir)
31    return rule
32
33def cmd_spell(ctx):
34    from waflib import Options
35    from sys import argv
36    from subprocess import call
37
38    Options.commands = None # stop warnings about knowing commands.
39
40    if not ctx.env.BIN_ASPELL:
41        ctx.fatal("'aspell' is required please install and re-run configure.")
42
43    if len(argv) < 3:
44        ctx.fatal("Please supply at least one file name")
45
46    files = argv[2:]
47
48    path = ctx.path.parent.abspath()
49
50    # XXX: add error checking eg check if file exists.
51    for file in files:
52        cmd = ctx.env.BIN_ASPELL + \
53              ["-c",
54               "--personal=%s/common/spell/dict/rtems" % path,
55               "--extra-dicts=%s/common/spell/en_GB-ise-w_accents.multi" % path,
56               file]
57        print("running:", cmd)
58        call(cmd)
59
60def cmd_linkcheck(ctx):
61    conf_dir = ctx.path.get_src()
62    source_dir = ctx.path.get_src()
63    buildtype = 'linkcheck'
64    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
65    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
66    ctx(
67        rule   = rule,
68        cwd    = ctx.path.abspath(),
69        source = ctx.path.ant_glob('**/*.rst'),
70        target = "linkcheck/output.txt"
71    )
72
73class spell(BuildContext):
74    __doc__ = "Check spelling.  Supply a list of files or a glob (*.rst)"
75    cmd = 'spell'
76    fun = 'cmd_spell'
77
78class linkcheck(BuildContext):
79    __doc__ = "Check all external URL references."
80    cmd = 'linkcheck'
81    fun = 'cmd_linkcheck'
82
83def check_sphinx_version(ctx, minver):
84    version = ctx.cmd_and_log(ctx.env.BIN_SPHINX_BUILD +
85                              ['--version']).split(" ")[-1:][0].strip()
86    try:
87        ver = tuple(map(int, re.split('[\D]', version)))
88    except:
89        ctx.fatal("Sphinx version cannot be checked: %s" % version)
90    if ver < minver:
91        ctx.fatal("Sphinx version is too old: %s" % ".".join(map(str, ver)))
92    return ver
93
94def sphinx_verbose(ctx):
95    return ' '.join(ctx.env.SPHINX_VERBOSE)
96
97def is_top_build(ctx):
98    from_top = False
99    if ctx.env['BUILD_FROM_TOP'] and ctx.env['BUILD_FROM_TOP'] == 'yes':
100        from_top = True
101    return from_top
102
103def build_dir_setup(ctx, buildtype):
104    where = buildtype
105    if is_top_build(ctx):
106        where = os.path.join(ctx.path.name, where)
107    bnode = ctx.bldnode.find_node(where)
108    if bnode is None:
109        ctx.bldnode.make_node(where).mkdir()
110    build_dir = ctx.path.get_bld().relpath()
111    output_node = ctx.path.get_bld().make_node(buildtype)
112    output_dir = output_node.abspath()
113    doctrees = os.path.join(os.path.dirname(output_dir), 'doctrees', buildtype)
114    return build_dir, output_node, output_dir, doctrees
115
116def pdf_resources(ctx, buildtype):
117    packages_base = ctx.path.parent.find_dir('common/latex')
118    if packages_base is None:
119        ctx.fatal('Latex package directory not found')
120    base = packages_base.path_from(ctx.path)
121    fnode = ctx.path.get_bld().make_node(buildtype)
122    fnode.mkdir()
123    local_packages = latex.local_packages()
124    if local_packages is not None:
125        srcs = [os.path.join(base, p) for p in local_packages]
126        ctx(
127            features = "subst",
128            is_copy  = True,
129            source   = srcs,
130            target   = [fnode.make_node(p) for p in local_packages]
131        )
132    ctx(
133        features = "subst",
134        is_copy  = True,
135        source   = os.path.join(base, ctx.env.RTEMSEXTRAFONTS),
136        target   = fnode.make_node('rtemsextrafonts.sty')
137    )
138
139def html_resources(ctx, buildtype):
140    for dir_name in ["_static", "_templates"]:
141        files = ctx.path.parent.find_node("common").ant_glob("%s/*" % dir_name)
142        fnode = ctx.path.get_bld().make_node(os.path.join(buildtype, dir_name))
143        fnode.mkdir() # dirs
144        ctx(
145            features = "subst",
146            is_copy  = True,
147            source   = files,
148            target   = [fnode.make_node(x.name) for x in files]
149        )
150
151    # copy images
152#    ctx.path.get_bld().make_node("images").mkdir()
153#    files = ctx.path.parent.ant_glob("images/**")
154#    ctx(
155#        features    = "subst",
156#        is_copy     = True,
157#        source      = files,
158#        target      = [x.srcpath().replace("../", "") for x in files]
159#    )
160
161
162def check_sphinx_extension(ctx, extension):
163    def run_sphinx(bld):
164        rst_node = bld.srcnode.make_node('testbuild/contents.rst')
165        rst_node.parent.mkdir()
166        rst_node.write('.. COMMENT test sphinx\n')
167        bld(rule = bld.kw['rule'], source = rst_node)
168
169    ctx.start_msg("Checking for '%s'" % (extension))
170    try:
171        ctx.run_build(fragment = 'xx',
172                      rule = "${BIN_SPHINX_BUILD} -b html -D extensions=%s -C . out" % (extension),
173                      build_fun = run_sphinx,
174                      env = ctx.env)
175    except ctx.errors.ConfigurationError:
176        ctx.end_msg('not found (see README.txt)', 'RED')
177        ctx.fatal('The configuration failed')
178    ctx.end_msg('found')
179
180
181def cmd_configure(ctx):
182    check_sphinx = not ctx.env.BIN_SPHINX_BUILD
183    if check_sphinx:
184        ctx.msg('Checking version', ctx.env.VERSION)
185
186        ctx.find_program("sphinx-build", var="BIN_SPHINX_BUILD", mandatory = True)
187        ctx.find_program("aspell", var = "BIN_ASPELL", mandatory = False)
188
189        ctx.start_msg("Checking if Sphinx is at least %s.%s" % sphinx_min_version)
190        ver = check_sphinx_version(ctx, sphinx_min_version)
191        ctx.end_msg("yes (%s)" % ".".join(map(str, ver)))
192
193        ctx.start_msg("Checking Sphinx Verbose ")
194        if 'SPHINX_VERBOSE' not in ctx.env:
195            ctx.env.append_value('SPHINX_VERBOSE', ctx.options.sphinx_verbose)
196            level = sphinx_verbose(ctx)
197            if level == '-Q':
198                level = 'quiet'
199            ctx.end_msg(level)
200        #
201        # Check extensions.
202        #
203        check_sphinx_extension(ctx, 'sphinx.ext.autodoc')
204        check_sphinx_extension(ctx, 'sphinx.ext.coverage')
205        check_sphinx_extension(ctx, 'sphinx.ext.doctest')
206        check_sphinx_extension(ctx, 'sphinx.ext.graphviz')
207        check_sphinx_extension(ctx, 'sphinx.ext.intersphinx')
208        check_sphinx_extension(ctx, 'sphinx.ext.mathjax')
209        check_sphinx_extension(ctx, 'sphinxcontrib.bibtex')
210
211    #
212    # Optional builds.
213    #
214    ctx.env.BUILD_PDF = 'no'
215    if ctx.options.pdf:
216        check_tex = not ctx.env.PDFLATEX
217        if check_tex:
218            ctx.load('tex')
219            if not ctx.env.PDFLATEX or not ctx.env.MAKEINDEX:
220                ctx.fatal('The programs pdflatex and makeindex are required for PDF output')
221            if 'PDFLATEXFLAGS' not in ctx.env or \
222               '-shell-escape' not in ctx.env['PDFLATEXFLAGS']:
223                ctx.env.append_value('PDFLATEXFLAGS', '-shell-escape')
224            latex.configure_tests(ctx)
225        ctx.env.BUILD_PDF = 'yes'
226
227    ctx.envBUILD_SINGLEHTML = 'no'
228    if ctx.options.singlehtml:
229        check_inliner = not ctx.env.BIN_INLINER
230        if check_inliner:
231            ctx.env.BUILD_SINGLEHTML = 'yes'
232            ctx.find_program("inliner", var = "BIN_INLINER", mandatory = False)
233            if not ctx.env.BIN_INLINER:
234                ctx.fatal("Node inliner is required install with 'npm install -g inliner' " +
235                          "(https://github.com/remy/inliner)")
236
237def doc_pdf(ctx, source_dir, conf_dir, extra_source):
238    buildtype = 'latex'
239    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
240    pdf_resources(ctx, buildtype)
241    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
242    ctx(
243        rule         = rule,
244        cwd          = ctx.path,
245        source       = ctx.path.ant_glob('**/*.rst') + extra_source,
246        target       = ctx.path.find_or_declare("%s/%s.tex" % (buildtype,
247                                                               ctx.path.name))
248    )
249    ctx(
250        features     = 'tex',
251        cwd          = output_dir,
252        type         = 'pdflatex',
253        source       = "%s/%s.tex" % (buildtype, ctx.path.name),
254        prompt       = 0
255    )
256    ctx.install_files('${PREFIX}',
257                      '%s/%s.pdf' % (buildtype, ctx.path.name),
258                      cwd = output_node,
259                      quiet = True)
260
261def doc_singlehtml(ctx, source_dir, conf_dir, extra_source):
262    #
263    # Use a run command to handle stdout and stderr output from inliner. Using
264    # a standard rule in the build context locks up.
265    #
266    def run(task):
267        src = task.inputs[0].abspath()
268        tgt = task.outputs[0].abspath()
269        cmd = '%s %s' % (task.env.BIN_INLINER[0], src)
270        so = open(tgt, 'w')
271        se = open(tgt + '.err', 'w')
272        r = task.exec_command(cmd, stdout = so, stderr = se)
273        so.close()
274        se.close()
275        #
276        # The inliner does not handle internal href's correctly and places the
277        # input's file name in the href. Strip these.
278        #
279        with open(tgt, 'r') as i:
280            before = i.read()
281            after = before.replace('index.html', '')
282        i.close()
283        with open(tgt, 'w') as o:
284            o.write(after)
285        o.close()
286        return r
287
288    buildtype = 'singlehtml'
289    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
290    html_resources(ctx, buildtype)
291    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
292    ctx(
293        rule         = rule,
294        cwd          = ctx.path,
295        source       = ctx.path.ant_glob('**/*.rst') + extra_source,
296        target       = ctx.path.find_or_declare("%s/index.html" % (buildtype)),
297        install_path = None
298    )
299    ctx(
300        rule         = run,
301        inliner      = ctx.env.BIN_INLINER,
302        source       = "%s/index.html" % buildtype,
303        target       = "%s/%s.html" % (buildtype, ctx.path.name),
304        install_path = '${PREFIX}'
305    )
306
307def doc_html(ctx, source_dir, conf_dir, extra_source):
308    buildtype = 'html'
309    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
310    html_resources(ctx, buildtype)
311    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
312    ctx(
313        rule         = rule,
314        cwd          = ctx.path,
315        source       = ctx.path.ant_glob('**/*.rst') + extra_source,
316        target       = ctx.path.find_or_declare('%s/index.html' % buildtype),
317        install_path = None
318    )
319    ctx.install_files('${PREFIX}/%s' % (ctx.path.name),
320                      output_node.ant_glob('**/*', quiet = True),
321                      cwd = output_node,
322                      relative_trick = True,
323                      quiet = True)
324
325def cmd_build(ctx, extra_source = []):
326    conf_dir = ctx.path.get_src()
327    source_dir = ctx.path.get_src()
328
329    if ctx.env.BUILD_PDF == 'yes':
330        doc_pdf(ctx, source_dir, conf_dir, extra_source)
331
332    if ctx.env.BUILD_SINGLEHTML == 'yes':
333        doc_singlehtml(ctx, source_dir, conf_dir, extra_source)
334
335    doc_html(ctx, source_dir, conf_dir, extra_source)
336
337def cmd_options(ctx):
338    ctx.add_option('--disable-extra-fonts',
339                   action = 'store_true',
340                   default = False,
341                   help = "Disable building with extra fonts for better quality (lower quality).")
342    ctx.add_option('--sphinx-verbose',
343                   action = 'store',
344                   default = "-Q",
345                   help = "Sphinx verbose.")
346    ctx.add_option('--pdf',
347                   action = 'store_true',
348                   default = False,
349                   help = "Build PDF.")
350    ctx.add_option('--singlehtml',
351                   action = 'store_true',
352                   default = False,
353                   help = "Build Single HTML file, requires Node Inliner")
354
355def cmd_options_path(ctx):
356    cmd_options(ctx)
357    ctx.add_option('--rtems-path-py',
358                   type = 'string',
359                   help = "Full path to py/ in RTEMS source repository.")
360
361def cmd_configure_path(ctx):
362    if not ctx.options.rtems_path_py:
363        ctx.fatal("--rtems-path-py is required")
364
365    ctx.env.RTEMS_PATH = ctx.options.rtems_path_py
366
367    cmd_configure(ctx)
368
369def xml_catalogue(ctx, building):
370    #
371    # The following is a hack to find the top_dir because the task does
372    # provided a reference to top_dir like a build context.
373    #
374    top_dir = ctx.get_cwd().find_node('..')
375    #
376    # Read the conf.py files in each directory to gather the doc details.
377    #
378    catalogue = {}
379    sp = sys.path[:]
380    for doc in building:
381        #
382        # Import using the imp API so the module is reloaded for us.
383        #
384        import imp
385        sys.path = [top_dir.find_node(doc).abspath()]
386        mf = imp.find_module('conf')
387        sys.path = sp[:]
388        try:
389            bconf = imp.load_module('bconf', mf[0], mf[1], mf[2])
390        finally:
391            mf[0].close()
392        catalogue[doc] = {
393            'title': bconf.project,
394            'version': str(ctx.env.VERSION),
395            'release': str(ctx.env.VERSION),
396            'pdf': bconf.latex_documents[0][1].replace('.tex', '.pdf'),
397            'html': '%s/index.html' % (doc),
398            'singlehtml': '%s.html' % (doc)
399        }
400        bconf = None
401
402    import xml.dom.minidom as xml
403    cat = xml.Document()
404
405    root = cat.createElement('rtems-docs')
406    root.setAttribute('date', build_date())
407    cat.appendChild(root)
408
409    heading = cat.createElement('catalogue')
410    text = cat.createTextNode(str(ctx.env.VERSION))
411    heading.appendChild(text)
412    root.appendChild(heading)
413
414    builds = ['html']
415    if ctx.env.BUILD_PDF == 'yes':
416        builds += ['pdf']
417    if ctx.env.BUILD_SINGLEHTML == 'yes':
418        builds += ['singlehtml']
419
420    for d in building:
421        doc = cat.createElement('doc')
422        name = cat.createElement('name')
423        text = cat.createTextNode(d)
424        name.appendChild(text)
425        title = cat.createElement('title')
426        text = cat.createTextNode(catalogue[d]['title'])
427        title.appendChild(text)
428        release = cat.createElement('release')
429        text = cat.createTextNode(catalogue[d]['release'])
430        release.appendChild(text)
431        version = cat.createElement('version')
432        text = cat.createTextNode(catalogue[d]['version'])
433        version.appendChild(text)
434        doc.appendChild(name)
435        doc.appendChild(title)
436        doc.appendChild(release)
437        doc.appendChild(version)
438        for b in builds:
439            output = cat.createElement(b)
440            text = cat.createTextNode(catalogue[d][b])
441            output.appendChild(text)
442            doc.appendChild(output)
443        root.appendChild(doc)
444
445    catnode = ctx.get_cwd().make_node('catalogue.xml')
446    catnode.write(cat.toprettyxml(indent = ' ' * 2, newl = os.linesep))
447
448    cat.unlink()
Note: See TracBrowser for help on using the repository browser.