1# Sphinx extension to integrate IDF build system information
2# into the Sphinx Build
3#
4# Runs early in the Sphinx process, runs CMake to generate the dummy IDF project
5# in this directory - including resolving paths, etc.
6#
7# Then emits the new 'idf-info' event which has information read from IDF
8# build system, that other extensions can use to generate relevant data.
9import json
10import os.path
11import shutil
12import subprocess
13import sys
14
15# this directory also contains the dummy IDF project
16project_path = os.path.abspath(os.path.dirname(__file__))
17
18# Targets which needs --preview to build
19PREVIEW_TARGETS = []
20
21
22def setup(app):
23    # Setup some common paths
24
25    try:
26        build_dir = os.environ['BUILDDIR']  # TODO see if we can remove this
27    except KeyError:
28        build_dir = os.path.dirname(app.doctreedir.rstrip(os.sep))
29
30    try:
31        os.mkdir(build_dir)
32    except OSError:
33        pass
34
35    try:
36        os.mkdir(os.path.join(build_dir, 'inc'))
37    except OSError:
38        pass
39
40    # Fill in a default IDF_PATH if it's missing (ie when Read The Docs is building the docs)
41    try:
42        idf_path = os.environ['IDF_PATH']
43    except KeyError:
44        idf_path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
45
46    app.add_config_value('docs_root', os.path.join(idf_path, 'docs'), 'env')
47    app.add_config_value('idf_path', idf_path, 'env')
48    app.add_config_value('build_dir', build_dir, 'env')  # not actually an IDF thing
49    app.add_event('idf-info')
50
51    # we want this to run early in the docs build but unclear exactly when
52    app.connect('config-inited', generate_idf_info)
53
54    return {'parallel_read_safe': True, 'parallel_write_safe': True, 'version': '0.1'}
55
56
57def generate_idf_info(app, config):
58    print('Running CMake on dummy project to get build info...')
59    build_dir = os.path.dirname(app.doctreedir.rstrip(os.sep))
60    cmake_build_dir = os.path.join(build_dir, 'build_dummy_project')
61    idf_py_path = os.path.join(app.config.idf_path, 'tools', 'idf.py')
62    print('Running idf.py...')
63    idf_py = [sys.executable,
64              idf_py_path,
65              '-B',
66              cmake_build_dir,
67              '-C',
68              project_path,
69              '-D',
70              'SDKCONFIG={}'.format(os.path.join(build_dir, 'dummy_project_sdkconfig'))
71              ]
72
73    # force a clean idf.py build w/ new sdkconfig each time
74    # (not much slower than 'reconfigure', avoids any potential config & build versioning problems
75    shutil.rmtree(cmake_build_dir, ignore_errors=True)
76    print('Starting new dummy IDF project... ')
77
78    if (app.config.idf_target in PREVIEW_TARGETS):
79        subprocess.check_call(idf_py + ['--preview', 'set-target', app.config.idf_target])
80    else:
81        subprocess.check_call(idf_py + ['set-target', app.config.idf_target])
82
83    print('Running CMake on dummy project...')
84    subprocess.check_call(idf_py + ['reconfigure'])
85
86    with open(os.path.join(cmake_build_dir, 'project_description.json')) as f:
87        project_description = json.load(f)
88    if project_description['target'] != app.config.idf_target:
89        # this shouldn't really happen unless someone has been moving around directories inside _build, as
90        # the cmake_build_dir path should be target-specific
91        raise RuntimeError(('Error configuring the dummy IDF project for {}. ' +
92                            'Target in project description is {}. ' +
93                            'Is build directory contents corrupt?')
94                           .format(app.config.idf_target, project_description['target']))
95    app.emit('idf-info', project_description)
96
97    return []
98