source: rtems-docs/common/waf.py @ 19af6b4

5
Last change on this file since 19af6b4 was 19af6b4, checked in by Chris Johns <chrisj@…>, on 03/12/20 at 05:34:16

waf: Get the version numbers from the version file.

  • Property mode set to 100644
File size: 21.2 KB
Line 
1#
2# RTEMS Documentation Project
3#
4# Waf build support.
5#
6
7
8from __future__ import print_function
9
10import os
11import re
12import sys
13
14from waflib.Build import BuildContext
15
16import latex
17import conf
18
19sphinx_min_version = (1, 3)
20
21def version_cmdline(ctx):
22    return '-Drelease="%s" -Dversion="%s" -Drtems_major="%s" ' \
23           '-Drtems_minor="%s" -D rtems_revision="%s"' \
24        % (ctx.env.RELEASE, ctx.env.VERSION, ctx.env.RTEMS_MAJOR,
25           ctx.env.RTEMS_MINOR, ctx.env.RTEMS_REVISION)
26
27def sphinx_cmdline(ctx, build_type, conf_dir, doctrees,
28                   source_dir, output_dir, configs = []):
29    cfgs = ''
30    for c in configs:
31        cfgs += ' -D %s=%s' % (c, configs[c])
32    rule = "${BIN_SPHINX_BUILD} %s -b %s -c %s %s -d %s %s %s %s ${SRC}" % \
33           (sphinx_options(ctx), build_type, conf_dir, version_cmdline(ctx),
34            doctrees, cfgs, source_dir, output_dir)
35    return rule
36
37def cmd_spell(ctx):
38    from waflib import Options
39    from sys import argv
40    from subprocess import call
41
42    Options.commands = None # stop warnings about knowing commands.
43
44    if not ctx.env.BIN_ASPELL:
45        ctx.fatal("'aspell' is required please install and re-run configure.")
46
47    if len(argv) < 3:
48        ctx.fatal("Please supply at least one file name")
49
50    files = argv[2:]
51
52    path = ctx.path.parent.abspath()
53
54    # XXX: add error checking eg check if file exists.
55    for file in files:
56        cmd = ctx.env.BIN_ASPELL + \
57              ["-c",
58               "--personal=%s/common/spell/dict/rtems" % path,
59               "--extra-dicts=%s/common/spell/en_GB-ise-w_accents.multi" % path,
60               file]
61        print("running:", cmd)
62        call(cmd)
63
64def cmd_linkcheck(ctx):
65    conf_dir = ctx.path.get_src()
66    source_dir = ctx.path.get_src()
67    buildtype = 'linkcheck'
68    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
69    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
70    ctx(
71        rule   = rule,
72        cwd    = ctx.path.abspath(),
73        source = ctx.path.ant_glob('**/*.rst'),
74        target = "linkcheck/output.txt"
75    )
76
77class spell(BuildContext):
78    __doc__ = "Check spelling.  Supply a list of files or a glob (*.rst)"
79    cmd = 'spell'
80    fun = 'cmd_spell'
81
82class linkcheck(BuildContext):
83    __doc__ = "Check all external URL references."
84    cmd = 'linkcheck'
85    fun = 'cmd_linkcheck'
86
87def check_sphinx_version(ctx, minver):
88    try:
89        import sphinx
90        # sphinx.version_info was introduced in sphinx ver 1.2
91        version = sphinx.version_info
92        # version looks like (1, 7, 0, 'final', 0))
93        ver = version[0:2]
94    except:
95        try:
96            # sphinx-build returns its version info in stderr
97            (out, err) = ctx.cmd_and_log(ctx.env.BIN_SPHINX_BUILD +
98                              ['--version'], output=Context.BOTH)
99            # err looks like 'sphinx-build 1.7.0\n'
100            version = err.split(" ")[-1:][0].strip()
101            ver = tuple(map(int, re.split('[\D]', version)))
102        except:
103            try:
104                # sphinx-build returns its version info in stdout
105                version = ctx.cmd_and_log(ctx.env.BIN_SPHINX_BUILD +
106                              ['--version']).split(" ")[-1:][0].strip()
107                try:
108                    ver = tuple(map(int, re.split('[\D]', version)))
109                except:
110                    ctx.fatal("Sphinx version cannot be checked or Sphinx is not installed")
111            except:
112                ctx.fatal("Sphinx version cannot be checked or Sphinx is not installed")
113    if ver < minver:
114        ctx.fatal("Sphinx version is too old: %s" % ".".join(map(str, ver)))
115    return ver
116
117def sphinx_options(ctx):
118    return ' '.join(ctx.env.SPHINX_OPTIONS)
119
120def is_top_build(ctx):
121    from_top = False
122    if ctx.env['BUILD_FROM_TOP'] and ctx.env['BUILD_FROM_TOP'] == 'yes':
123        from_top = True
124    return from_top
125
126def build_dir_setup(ctx, buildtype):
127    where = buildtype
128    if is_top_build(ctx):
129        where = os.path.join(ctx.path.name, where)
130    bnode = ctx.bldnode.find_node(where)
131    if bnode is None:
132        ctx.bldnode.make_node(where).mkdir()
133    build_dir = ctx.path.get_bld().relpath()
134    output_node = ctx.path.get_bld().make_node(buildtype)
135    output_dir = output_node.abspath()
136    doctrees = os.path.join(os.path.dirname(output_dir), 'doctrees', buildtype)
137    return build_dir, output_node, output_dir, doctrees
138
139def pdf_resources(ctx, buildtype):
140    packages_base = ctx.path.parent.find_dir('common/latex')
141    if packages_base is None:
142        ctx.fatal('Latex package directory not found')
143    base = packages_base.path_from(ctx.path)
144    fnode = ctx.path.get_bld().make_node(buildtype)
145    fnode.mkdir()
146    local_packages = latex.local_packages()
147    targets = []
148    if local_packages is not None:
149        srcs = [os.path.join(base, p) for p in local_packages]
150        targets += [fnode.make_node(p) for p in local_packages]
151        ctx(features = "subst",
152            is_copy  = True,
153            source   = srcs,
154            target   = targets)
155    targets += [fnode.make_node('rtemsextrafonts.sty')]
156    ctx(features = "subst",
157        is_copy  = True,
158        source   = os.path.join(base, ctx.env.RTEMSEXTRAFONTS),
159        target   = fnode.make_node('rtemsextrafonts.sty'))
160    return targets
161
162def html_resources(ctx, buildtype):
163    resources = []
164    for dir_name in ["_static", "_templates"]:
165        files = ctx.path.parent.find_node("common").ant_glob("%s/*" % dir_name)
166        fnode = ctx.path.get_bld().make_node(os.path.join(buildtype, dir_name))
167        targets = [fnode.make_node(x.name) for x in files]
168        resources += targets
169        fnode.mkdir() # dirs
170        ctx(features = "subst",
171            is_copy  = True,
172            source   = files,
173            target   = targets)
174        ctx.add_group()
175    return resources
176
177def check_sphinx_extension(ctx, extension):
178    def run_sphinx(bld):
179        rst_node = bld.srcnode.make_node('testbuild/contents.rst')
180        rst_node.parent.mkdir()
181        rst_node.write('.. COMMENT test sphinx\n')
182        bld(rule = bld.kw['rule'], source = rst_node)
183
184    ctx.start_msg("Checking for '%s'" % (extension))
185    try:
186        ctx.run_build(fragment = 'xx',
187                      rule = "${BIN_SPHINX_BUILD} -b html -D extensions=%s -C . out" % (extension),
188                      build_fun = run_sphinx,
189                      env = ctx.env)
190    except ctx.errors.ConfigurationError:
191        ctx.end_msg('not found (see README.txt)', 'RED')
192        ctx.fatal('The configuration failed')
193    ctx.end_msg('found')
194
195def cmd_configure(ctx):
196    check_sphinx = not ctx.env.BIN_SPHINX_BUILD
197    if check_sphinx:
198        ctx.find_program("sphinx-build", var="BIN_SPHINX_BUILD", mandatory = True)
199        ctx.find_program("aspell", var = "BIN_ASPELL", mandatory = False)
200
201        ctx.start_msg("Checking if Sphinx is at least %s.%s" % sphinx_min_version)
202        ver = check_sphinx_version(ctx, sphinx_min_version)
203        ctx.end_msg("yes (%s)" % ".".join(map(str, ver)))
204
205        ctx.start_msg("Checking Sphinx Options ")
206        if 'SPHINX_OPTIONS' not in ctx.env:
207            ctx.env.append_value('SPHINX_OPTIONS', ctx.options.sphinx_options)
208            opts = sphinx_options(ctx)
209            if len(opts) == 0:
210                opts = 'none'
211            ctx.end_msg(opts)
212
213        ctx.start_msg("Checking Sphinx Nit-Pick mode ")
214        if ctx.options.sphinx_nit_pick:
215            opt = '-n'
216            msg = 'yes'
217        else:
218            opt = '-Q'
219            msg = 'no'
220        ctx.env.append_value('SPHINX_OPTIONS', opt)
221        ctx.end_msg(msg)
222
223        #
224        # Check extensions.
225        #
226        check_sphinx_extension(ctx, 'sphinx.ext.autodoc')
227        check_sphinx_extension(ctx, 'sphinx.ext.coverage')
228        check_sphinx_extension(ctx, 'sphinx.ext.doctest')
229        check_sphinx_extension(ctx, 'sphinx.ext.graphviz')
230        check_sphinx_extension(ctx, 'sphinx.ext.intersphinx')
231        check_sphinx_extension(ctx, 'sphinx.ext.mathjax')
232        check_sphinx_extension(ctx, 'sphinxcontrib.bibtex')
233
234    #
235    # Optional builds.
236    #
237    ctx.env.BUILD_PDF = 'no'
238    if ctx.options.pdf:
239        if conf.latex_engine == 'xelatex':
240            if not ctx.env.LATEX_CMD:
241                ctx.load('tex')
242                if not ctx.env.XELATEX or not ctx.env.MAKEINDEX:
243                    ctx.fatal('The programs xelatex and makeindex are required for PDF output')
244                ctx.env.LATEX_CMD = 'xelatex'
245                latex.configure_tests(ctx)
246                # Minted needs 'shell-escape'
247                if 'XELATEXFLAGS' not in ctx.env or \
248                   '-shell-escape' not in ctx.env['XELATEXFLAGS']:
249                    ctx.env.append_value('XELATEXFLAGS', '-shell-escape')
250                ctx.env.append_value('MAKEINDEXFLAGS', ['-s', 'python.ist'])
251        elif conf.latex_engine == 'pdflatex':
252            if not ctx.env.LATEX_CMD:
253                ctx.load('tex')
254                if not ctx.env.PDFLATEX or not ctx.env.MAKEINDEX:
255                    ctx.fatal('The programs pdflatex and makeindex are required for PDF output')
256                if 'PDFLATEXFLAGS' not in ctx.env or \
257                   '-shell-escape' not in ctx.env['PDFLATEXFLAGS']:
258                    ctx.env.append_value('PDFLATEXFLAGS', '-shell-escape')
259                ctx.env.append_value('MAKEINDEXFLAGS', ['-s', 'python.ist'])
260                ctx.env.LATEX_CMD = 'pdflatex'
261                latex.configure_tests(ctx)
262        else:
263            ctx.fatal('Unsupported latex engine: %s' % (conf.latex_engine))
264        ctx.env.BUILD_PDF = 'yes'
265
266    ctx.envBUILD_SINGLEHTML = 'no'
267    if ctx.options.singlehtml:
268        check_inliner = not ctx.env.BIN_INLINER
269        if check_inliner:
270            ctx.env.BUILD_SINGLEHTML = 'yes'
271            ctx.find_program("inliner", var = "BIN_INLINER", mandatory = False)
272            if not ctx.env.BIN_INLINER:
273                ctx.fatal("Node.js inliner is required install with 'npm install -g inliner' " +
274                          "(https://github.com/remy/inliner)")
275
276    ctx.envBUILD_PLANTUML = 'no'
277    if ctx.options.plantuml:
278        check_plantuml = not ctx.env.BIN_PUML
279        if check_plantuml:
280            ctx.env.BUILD_PLANTUML = 'yes'
281            ctx.find_program("puml", var = "BIN_PUML", mandatory = False)
282            if not ctx.env.BIN_PUML:
283                ctx.fatal("Node.js puml is required install with 'npm install -g node-plantuml' " +
284                          "(https://www.npmjs.com/package/node-plantuml)")
285
286    ctx.envBUILD_DITAA = 'no'
287    if ctx.options.ditaa:
288        #
289        # We use DITAA via PlantUML
290        #
291        if not ctx.env.BIN_PUML:
292            ctx.find_program("puml", var = "BIN_PUML", mandatory = False)
293            if not ctx.env.BIN_PUML:
294                ctx.fatal("DITAA uses PlantUML; " +
295                          "Node.js puml is required install with 'npm install -g node-plantuml' " +
296                          "(https://www.npmjs.com/package/node-plantuml)")
297        check_ditaa = not ctx.env.BIN_DITAA
298        if check_ditaa:
299            ctx.env.BUILD_DITAA = 'yes'
300            ctx.find_program("ditaa", var = "BIN_DITAA", mandatory = False)
301            if not ctx.env.BIN_DITAA:
302                ctx.fatal("DITAA not found, plase install")
303
304def sources_exclude(ctx, sources):
305    exclude = sources.get('exclude', [])
306    if len(exclude) == 0:
307        return exclude
308    return [ctx.path.find_node(e) for e in exclude]
309
310def sources_extra(ctx, sources):
311    extra = sources.get('extra', [])
312    if len(extra) == 0:
313        extra = [ctx.path.find_node(e) for e in extra]
314    return [e for e in extra if e not in sources_exclude(ctx, sources)]
315
316def sources_source(ctx, sources):
317    extra = sources_extra(ctx, sources)
318    exclude = sources_exclude(ctx, sources)
319    source = ctx.path.ant_glob('**/*.rst')
320    return [s for s in source if s not in exclude] + extra
321
322def doc_pdf(ctx, source_dir, conf_dir, sources):
323    buildtype = 'latex'
324    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
325    resources = pdf_resources(ctx, buildtype)
326    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
327    ctx(
328        rule         = rule,
329        cwd          = ctx.path,
330        source       = sources_source(ctx, sources),
331        depends_on   = sources_extra(ctx, sources),
332        target       = ctx.path.find_or_declare("%s/%s.tex" % (buildtype,
333                                                               ctx.path.name))
334    )
335    ctx(
336        features     = 'tex',
337        cwd          = output_dir,
338        type         = ctx.env.LATEX_CMD,
339        source       = "%s/%s.tex" % (buildtype, ctx.path.name),
340        prompt       = 0
341    )
342    ctx.install_files('${PREFIX}',
343                      '%s/%s.pdf' % (buildtype, ctx.path.name),
344                      cwd = output_node,
345                      quiet = True)
346
347def doc_singlehtml(ctx, source_dir, conf_dir, sources):
348    #
349    # Use a run command to handle stdout and stderr output from inliner. Using
350    # a standard rule in the build context locks up.
351    #
352    def run(task):
353        src = task.inputs[0].abspath()
354        tgt = task.outputs[0].abspath()
355        cmd = '%s %s' % (task.env.BIN_INLINER[0], src)
356        so = open(tgt, 'w')
357        se = open(tgt + '.err', 'w')
358        r = task.exec_command(cmd, stdout = so, stderr = se)
359        so.close()
360        se.close()
361        #
362        # The inliner does not handle internal href's correctly and places the
363        # input's file name in the href. Strip these.
364        #
365        if sys.version_info[0] < 3:
366            with open(tgt, 'r') as i:
367                before = i.read()
368        else:
369            with open(tgt, 'r', encoding = 'ascii', errors = 'surrogateescape') as i:
370                before = i.read()
371        i.close()
372        after = before.replace('index.html', '')
373        if sys.version_info[0] < 3:
374            with open(tgt, 'w') as o:
375                o.write(after)
376        else:
377            with open(tgt, 'w', encoding = 'ascii', errors = 'surrogateescape') as o:
378                o.write(after)
379        o.close()
380        return r
381
382    buildtype = 'singlehtml'
383    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
384    resources = html_resources(ctx, buildtype)
385    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir)
386    ctx(
387        rule         = rule,
388        cwd          = ctx.path,
389        source       = sources_source(ctx, sources),
390        depends_on   = resources,
391        target       = ctx.path.find_or_declare("%s/index.html" % (buildtype)),
392        install_path = None
393    )
394    ctx(
395        rule         = run,
396        inliner      = ctx.env.BIN_INLINER,
397        source       = "%s/index.html" % buildtype,
398        target       = "%s/%s.html" % (buildtype, ctx.path.name),
399        install_path = '${PREFIX}'
400    )
401
402def doc_html(ctx, source_dir, conf_dir, sources):
403    buildtype = 'html'
404    build_dir, output_node, output_dir, doctrees = build_dir_setup(ctx, buildtype)
405    resources = html_resources(ctx, buildtype)
406    templates = os.path.join(str(ctx.path.get_bld()), buildtype, '_templates')
407    configs = { 'templates_path': templates }
408    rule = sphinx_cmdline(ctx, buildtype, conf_dir, doctrees, source_dir, output_dir, configs)
409    ctx(
410        rule         = rule,
411        cwd          = ctx.path,
412        source       = sources_source(ctx, sources),
413        depends_on   = resources,
414        target       = ctx.path.find_or_declare('%s/index.html' % buildtype),
415        install_path = None
416    )
417    ctx.install_files('${PREFIX}/%s' % (ctx.path.name),
418                      output_node.ant_glob('**/*', quiet = True),
419                      cwd = output_node,
420                      relative_trick = True,
421                      quiet = True)
422
423def images_plantuml(ctx, source_dir, conf_dir, ext):
424    #
425    # Use a run command to handle stdout and stderr output from puml.
426    #
427    def run(task):
428        src = task.inputs[0].abspath()
429        tgt = task.outputs[0].abspath()
430        cmd = '%s generate %s -o %s' % (task.env.BIN_PUML[0], src, tgt)
431        so = open(tgt + '.out', 'w')
432        r = task.exec_command(cmd, stdout = so, stderr = so)
433        so.close()
434        return r
435
436    for src in ctx.path.ant_glob('**/*' + ext):
437        tgt = src.change_ext('.png')
438        ctx(
439            rule         = run,
440            inliner      = ctx.env.BIN_PUML,
441            source       = src,
442            target       = tgt,
443            install_path = None
444        )
445
446def cmd_build(ctx, sources = {}):
447    conf_dir = ctx.path.get_src()
448    source_dir = ctx.path.get_src()
449
450    if ctx.env.BUILD_PDF == 'yes':
451        doc_pdf(ctx, source_dir, conf_dir, sources)
452
453    if ctx.env.BUILD_SINGLEHTML == 'yes':
454        doc_singlehtml(ctx, source_dir, conf_dir, sources)
455
456    doc_html(ctx, source_dir, conf_dir, sources)
457
458def cmd_build_images(ctx):
459    conf_dir = ctx.path.get_src()
460    source_dir = ctx.path.get_src()
461    if ctx.env.BUILD_PLANTUML == 'yes':
462        images_plantuml(ctx, source_dir, conf_dir, '.puml')
463    if ctx.env.BUILD_DITAA == 'yes':
464        images_plantuml(ctx, source_dir, conf_dir, '.ditaa')
465
466def cmd_options(ctx):
467    ctx.add_option('--disable-extra-fonts',
468                   action = 'store_true',
469                   default = False,
470                   help = "Disable building with extra fonts for better quality (lower quality).")
471    ctx.add_option('--sphinx-options',
472                   action = 'store',
473                   default = "",
474                   help = "Additional Sphinx options.")
475    ctx.add_option('--sphinx-nit-pick',
476                   action = 'store_true',
477                   default = False,
478                   help = "Enable Sphinx nit-picky mode else be quiet")
479    ctx.add_option('--pdf',
480                   action = 'store_true',
481                   default = False,
482                   help = "Build PDF.")
483    ctx.add_option('--singlehtml',
484                   action = 'store_true',
485                   default = False,
486                   help = "Build Single HTML file, requires Node Inliner")
487    ctx.add_option('--plantuml',
488                   action = 'store_true',
489                   default = False,
490                   help = "Build PlantUML images from source, need puml from npm")
491    ctx.add_option('--ditaa',
492                   action = 'store_true',
493                   default = False,
494                   help = "Build DITAA images using PlantUML from source, need ditaa and puml")
495
496def cmd_options_path(ctx):
497    cmd_options(ctx)
498    ctx.add_option('--rtems-path-py',
499                   type = 'string',
500                   help = "Full path to py/ in RTEMS source repository.")
501
502def cmd_configure_path(ctx):
503    if not ctx.options.rtems_path_py:
504        ctx.fatal("--rtems-path-py is required")
505
506    ctx.env.RTEMS_PATH = ctx.options.rtems_path_py
507
508    cmd_configure(ctx)
509
510def xml_catalogue(ctx, building):
511    #
512    # The following is a hack to find the top_dir because the task does
513    # provided a reference to top_dir like a build context.
514    #
515    top_dir = ctx.get_cwd().find_node('..')
516    #
517    # Read the conf.py files in each directory to gather the doc details.
518    #
519    catalogue = {}
520    sp = sys.path[:]
521    for doc in building:
522        #
523        # Import using the imp API so the module is reloaded for us.
524        #
525        import imp
526        sys.path = [top_dir.find_node(doc).abspath()]
527        mf = imp.find_module('conf')
528        sys.path = sp[:]
529        try:
530            bconf = imp.load_module('bconf', mf[0], mf[1], mf[2])
531        finally:
532            mf[0].close()
533        catalogue[doc] = {
534            'title': bconf.project,
535            'version': str(ctx.env.VERSION),
536            'release': str(ctx.env.VERSION),
537            'pdf': bconf.latex_documents[0][1].replace('.tex', '.pdf'),
538            'html': '%s/index.html' % (doc),
539            'singlehtml': '%s.html' % (doc)
540        }
541        bconf = None
542
543    import xml.dom.minidom as xml
544    cat = xml.Document()
545
546    root = cat.createElement('rtems-docs')
547    root.setAttribute('date', ctx.env.DATE)
548    cat.appendChild(root)
549
550    heading = cat.createElement('catalogue')
551    text = cat.createTextNode(str(ctx.env.VERSION))
552    heading.appendChild(text)
553    root.appendChild(heading)
554
555    builds = ['html']
556    if ctx.env.BUILD_PDF == 'yes':
557        builds += ['pdf']
558    if ctx.env.BUILD_SINGLEHTML == 'yes':
559        builds += ['singlehtml']
560
561    for d in building:
562        doc = cat.createElement('doc')
563        name = cat.createElement('name')
564        text = cat.createTextNode(d)
565        name.appendChild(text)
566        title = cat.createElement('title')
567        text = cat.createTextNode(catalogue[d]['title'])
568        title.appendChild(text)
569        release = cat.createElement('release')
570        text = cat.createTextNode(catalogue[d]['release'])
571        release.appendChild(text)
572        version = cat.createElement('version')
573        text = cat.createTextNode(catalogue[d]['version'])
574        version.appendChild(text)
575        doc.appendChild(name)
576        doc.appendChild(title)
577        doc.appendChild(release)
578        doc.appendChild(version)
579        for b in builds:
580            output = cat.createElement(b)
581            text = cat.createTextNode(catalogue[d][b])
582            output.appendChild(text)
583            doc.appendChild(output)
584        root.appendChild(doc)
585
586    catnode = ctx.get_cwd().make_node('catalogue.xml')
587    catnode.write(cat.toprettyxml(indent = ' ' * 2, newl = os.linesep))
588
589    cat.unlink()
Note: See TracBrowser for help on using the repository browser.