Skip to content

Commit 2e0f8b5

Browse files
committed
docs: speed up incremental builds
On each documentation build (‘make html’), doxygen regenerates XML files. In addition to that, gen-dxd.py regenerates API reference files under _build/inc/. This results in Sphinx flagging about half of the input files as modified, and incremental builds taking long time. With this change, XML files generated by Doxygen are copied into docs/xml_in directory only when they are changed. Breathe is pointed to docs/xml_in directory instead of docs/xml. In addition to that, gen-dxd.py is modified to only write to the output file when contents change. Overall, incremental build time (with no source files changed) is reduced from ~7 minutes to ~8 seconds (on a particular OS X computer). Due to the way Breathe includes Doxygen XML files, there is still going to be a massive rebuild every time functions, enums, macros, structures are added or removed from the header files scanned by Doxygen, but at least individual .rst files can be edited at a much faster pace.
1 parent c97b875 commit 2e0f8b5

File tree

6 files changed

+90
-14
lines changed

6 files changed

+90
-14
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ docs/doxygen-warning-log.txt
3232
docs/sphinx-warning-log.txt
3333
docs/sphinx-warning-log-sanitized.txt
3434
docs/xml/
35+
docs/xml_in/
3536
docs/man/
37+
docs/doxygen_sqlite3.db
3638

3739
# Unit test app files
3840
tools/unit-test-app/sdkconfig

docs/conf.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,26 @@
2222
# documentation root, use os.path.abspath to make it absolute, like shown here.
2323
sys.path.insert(0, os.path.abspath('.'))
2424

25-
from repo_util import run_cmd_get_output
25+
from local_util import run_cmd_get_output, copy_if_modified
26+
27+
builddir = '_build'
28+
if 'BUILDDIR' in os.environ:
29+
builddir = os.environ['BUILDDIR']
2630

2731
# Call Doxygen to get XML files from the header files
2832
print "Calling Doxygen to generate latest XML files"
2933
call('doxygen')
34+
# Doxygen has generated XML files in 'xml' directory.
35+
# Copy them to 'xml_in', only touching the files which have changed.
36+
copy_if_modified('xml/', 'xml_in/')
37+
3038
# Generate 'api_name.inc' files using the XML files by Doxygen
31-
os.system("python gen-dxd.py")
39+
os.system('python gen-dxd.py')
40+
3241
# Generate 'kconfig.inc' file from components' Kconfig files
33-
os.system("python gen-kconfig-doc.py > _build/inc/kconfig.inc")
42+
kconfig_inc_path = '{}/inc/kconfig.inc'.format(builddir)
43+
os.system('python gen-kconfig-doc.py > ' + kconfig_inc_path + '.in')
44+
copy_if_modified(kconfig_inc_path + '.in', kconfig_inc_path)
3445

3546
# http://stackoverflow.com/questions/12772927/specifying-an-online-image-in-sphinx-restructuredtext-format
3647
#
@@ -63,7 +74,11 @@
6374
packetdiag_fontpath = '_static/DejaVuSans.ttf'
6475

6576
# Breathe extension variables
66-
breathe_projects = { "esp32-idf": "xml/" }
77+
78+
# Doxygen regenerates files in 'xml/' directory every time,
79+
# but we copy files to 'xml_in/' only when they change, to speed up
80+
# incremental builds.
81+
breathe_projects = { "esp32-idf": "xml_in/" }
6782
breathe_default_project = "esp32-idf"
6883

6984
# Add any paths that contain templates here, relative to this directory.

docs/gen-dxd.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
import os
1111
import re
1212

13+
# Determime build directory
14+
builddir = '_build'
15+
if 'BUILDDIR' in os.environ:
16+
builddir = os.environ['BUILDDIR']
17+
1318
# Script configuration
1419
header_file_path_prefix = "../components/"
1520
"""string: path prefix for header files.
@@ -20,7 +25,7 @@
2025
xml_directory_path = "xml"
2126
"""string: path to directory with XML files by Doxygen.
2227
"""
23-
inc_directory_path = "_build/inc"
28+
inc_directory_path = os.path.join(builddir, 'inc')
2429
"""string: path prefix for header files.
2530
"""
2631
all_kinds = [
@@ -263,9 +268,15 @@ def generate_api_inc_files():
263268
api_name = get_api_name(header_file_path)
264269
inc_file_path = inc_directory_path + "/" + api_name + ".inc"
265270
rst_output = generate_directives(header_file_path)
266-
inc_file = open(inc_file_path, "w")
267-
inc_file.write(rst_output)
268-
inc_file.close()
271+
272+
previous_rst_output = ''
273+
if os.path.isfile(inc_file_path):
274+
with open(inc_file_path, "r") as inc_file_old:
275+
previous_rst_output = inc_file_old.read()
276+
277+
if previous_rst_output != rst_output:
278+
with open(inc_file_path, "w") as inc_file:
279+
inc_file.write(rst_output)
269280

270281

271282
if __name__ == "__main__":

docs/link-roles.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import re
44
from docutils import nodes
5-
from repo_util import run_cmd_get_output
5+
from local_util import run_cmd_get_output
66

77
def get_github_rev():
88
path = run_cmd_get_output('git rev-parse --short HEAD')

docs/local_util.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Utility functions used in conf.py
2+
#
3+
# Copyright 2017 Espressif Systems (Shanghai) PTE LTD
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http:#www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
import re
18+
import os
19+
import shutil
20+
21+
def run_cmd_get_output(cmd):
22+
return os.popen(cmd).read().strip()
23+
24+
def files_equal(path_1, path_2):
25+
if not os.path.exists(path_1) or not os.path.exists(path_2):
26+
return False
27+
file_1_contents = ''
28+
with open(path_1, "r") as f_1:
29+
file_1_contents = f_1.read()
30+
file_2_contents = ''
31+
with open(path_2, "r") as f_2:
32+
file_2_contents = f_2.read()
33+
return file_1_contents == file_2_contents
34+
35+
def copy_file_if_modified(src_file_path, dst_file_path):
36+
if not files_equal(src_file_path, dst_file_path):
37+
dst_dir_name = os.path.dirname(dst_file_path)
38+
if not os.path.isdir(dst_dir_name):
39+
os.makedirs(dst_dir_name)
40+
shutil.copy(src_file_path, dst_file_path)
41+
42+
def copy_if_modified(src_path, dst_path):
43+
if os.path.isfile(src_path):
44+
copy_file_if_modified(src_path, dst_path)
45+
return
46+
47+
src_path_len = len(src_path)
48+
for root, dirs, files in os.walk(src_path):
49+
for src_file_name in files:
50+
src_file_path = os.path.join(root, src_file_name)
51+
dst_file_path = os.path.join(dst_path + root[src_path_len:], src_file_name)
52+
copy_file_if_modified(src_file_path, dst_file_path)
53+

docs/repo_util.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

0 commit comments

Comments
 (0)