Commit 0913c296 authored by Martí Bolívar's avatar Martí Bolívar Committed by Carles Cufi
Browse files

doc: fix extract_content.py dependency tracking



The logic which copies source documentation files into the build
directory could use some improvements to its dependency management, so
that when a source file changes, extract_content.py gets re-run.

Make these changes as follows:

- Add an --outputs flag to extract_content.py, so that the
  sources it depends on and generates can be saved into a file and
  thus made known to the build system

- Change the way the sources and destination are specified in the
  extract_content.py command line so that the entire job can be done
  in a single command, rather than multiple (to avoid having to
  collate multiple --outputs files in CMake)

- Extract the content at configure time with execute_process(),
  tracking all inputs and outputs within the build system itself. Use
  this information to make sure that each individual output depends on
  just its exact input file, ensuring updated inputs produce updated
  outputs without having to call extract_content.py again.

- Ensure that the "content" build system target depends on all the
  outputs, transitively triggering a rebuild any time an input
  file (e.g. .rst documentation file or included image/source file)
  changes.

Signed-off-by: default avatarMarti Bolivar <marti@foundries.io>
parent 9802f883
Loading
Loading
Loading
Loading
+67 −14
Original line number Diff line number Diff line
@@ -68,9 +68,76 @@ set(DOC_LOG ${CMAKE_CURRENT_BINARY_DIR}/doc.log)
set(DOXY_LOG ${CMAKE_CURRENT_BINARY_DIR}/doxy.log)
set(SPHINX_LOG ${CMAKE_CURRENT_BINARY_DIR}/sphinx.log)
set(DOC_WARN ${CMAKE_CURRENT_BINARY_DIR}/doc.warnings)
set(CONTENT_OUTPUTS ${CMAKE_CURRENT_BINARY_DIR}/extracted-content.txt)

configure_file(${DOXYFILE_IN} ${DOXYFILE_OUT} @ONLY)

# This command is used to copy all documentation source files into the
# build directory. Sphinx requires a single documentation root, but
# Zephyr's documentation is scattered around the tree in samples/,
# boards/, and doc/, so we need to copy those files into a single
# place in the build directory.
set(EXTRACT_CONTENT_COMMAND
  ${CMAKE_COMMAND} -E env
  ${PYTHON_EXECUTABLE} scripts/extract_content.py
  # Save the outputs for processing by this list file.
  --outputs ${CONTENT_OUTPUTS}
  # Ignore any files in the output directory.
  --ignore ${CMAKE_CURRENT_BINARY_DIR}
  # Copy all files in doc to the rst folder.
  "*:doc:${RST_OUT}"
  # Copy the .rst files in samples/ and boards/ to the rst folder, and
  # also the doc folder inside rst.
  #
  # Some files refer to items in samples/ and boards/ relative to
  # their actual position in the Zephyr tree. For example, in
  # subsystems/sensor.rst:
  #
  # .. literalinclude:: ../../samples/sensor/mcp9808/src/main.c
  #
  # We make an additional copy as a hackaround so these references
  # work.
  "*.rst:samples:${RST_OUT}" "*.rst:boards:${RST_OUT}"
  "*.rst:samples:${RST_OUT}/doc" "*.rst:boards:${RST_OUT}/doc")

# Run the content extraction command at configure time in order to
# produce lists of input and output files, which are respectively its
# dependencies and outputs. This also causes the content files
# to be copied for the first time.
execute_process(
  COMMAND ${EXTRACT_CONTENT_COMMAND}
  WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
  RESULT_VARIABLE RET)

if (NOT RET EQUAL 0)
  message(FATAL_ERROR "Cannot prepare documentation content extraction")
endif()

# Load the outputs listing from extract_content.py. This is a file
# where each pair of lines is a source file in the Zephyr tree and a
# destination file in the build directory.
file(STRINGS ${CONTENT_OUTPUTS} EXTRACT_CONTENT_RAW)

# Build up a list of output files for later use in target/command
# DEPENDS. Make sure each output file gets updated if the
# corresponding input file changes.
set(EXTRACT_CONTENT_OUTPUTS)
while(EXTRACT_CONTENT_RAW)
  list(GET EXTRACT_CONTENT_RAW 0 SRC)
  list(GET EXTRACT_CONTENT_RAW 1 DST)

  add_custom_command(
    COMMAND ${CMAKE_COMMAND} -E copy ${SRC} ${DST}
    DEPENDS ${SRC}
    OUTPUT ${DST}
    )

  list(REMOVE_AT EXTRACT_CONTENT_RAW 0 1)
  list(APPEND EXTRACT_CONTENT_OUTPUTS ${DST})
endwhile()

add_custom_target(content DEPENDS ${EXTRACT_CONTENT_OUTPUTS})

set(ARGS ${DOXYFILE_OUT})

add_custom_target(
@@ -89,20 +156,6 @@ add_custom_target(
  COMMAND ${CMAKE_COMMAND} -P ${ZEPHYR_BASE}/cmake/pristine.cmake
)

add_custom_target(
  content
  # Copy all files in doc/ to the rst folder
  COMMAND ${CMAKE_COMMAND} -E env
  ${PYTHON_EXECUTABLE} scripts/extract_content.py --ignore ${CMAKE_CURRENT_BINARY_DIR} -a ${RST_OUT} doc
  # Copy the .rst files in samples/ and boards/ to the rst folder
  COMMAND ${CMAKE_COMMAND} -E env
  ${PYTHON_EXECUTABLE} scripts/extract_content.py --ignore ${CMAKE_CURRENT_BINARY_DIR} ${RST_OUT} samples boards
  # Copy the .rst files in samples/ and boards/ to the doc folder inside rst
  COMMAND ${CMAKE_COMMAND} -E env
  ${PYTHON_EXECUTABLE} scripts/extract_content.py --ignore ${CMAKE_CURRENT_BINARY_DIR} ${RST_OUT}/doc samples boards
  WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)

if(WIN32)
  set(SEP ;)
else()
+21 −15
Original line number Diff line number Diff line
@@ -154,39 +154,45 @@ def extract_content(content):

def main():
    parser = argparse.ArgumentParser(
        description='''Recursively copy .rst files from the origin folder(s) to
        the destination folder, plus files referenced in those .rst files by a
        configurable list of directives: {}. The ZEPHYR_BASE environment
        description='''Recursively copy documentation files from ZEPHYR_BASE to
        a destination folder, along with files referenced in those .rst files
        by a configurable list of directives: {}. The ZEPHYR_BASE environment
        variable is used to determine source directories to copy files
        from.'''.format(DIRECTIVES))

    parser.add_argument('-a', '--all', action='store_true',
                        help='''Copy all files (recursively) in the specified
                        source folder(s).''')
    parser.add_argument('--outputs',
                        help='If given, save input/output files to this path')
    parser.add_argument('--ignore', action='append',
                        help='''Source directories to ignore when copying
                        files. This may be given multiple times.''')
    parser.add_argument('dest', nargs=1)
    parser.add_argument('src', nargs='+')
    parser.add_argument('content_config', nargs='+',
                        help='''A glob:source:destination specification
                        for content to extract. The "glob" is a documentation
                        file name pattern to include, "source" is a source
                        directory to search for such files in, and
                        "destination" is the directory to copy it into.''')
    args = parser.parse_args()

    if "ZEPHYR_BASE" not in os.environ:
        sys.exit("ZEPHYR_BASE environment variable undefined.")
    zephyr_base = os.environ["ZEPHYR_BASE"]

    dest = args.dest[0]
    if not args.ignore:
        ignore = ()
    else:
        ignore = tuple(path.normpath(ign) for ign in args.ignore)
    if args.all:
        fnfilter = '*'
    else:
        fnfilter = '*.rst'

    for d in args.src:
        content = find_content(zephyr_base, d, dest, fnfilter, ignore)
    content_config = [cfg.split(':', 2) for cfg in args.content_config]
    outputs = set()
    for fnfilter, source, dest in content_config:
        content = find_content(zephyr_base, source, dest, fnfilter, ignore)
        extract_content(content)
        outputs |= set(content.outputs)
    if args.outputs:
        with open(args.outputs, 'w') as f:
            for o in outputs:
                print(o.src, file=f, end='\n')
                print(o.dst, file=f, end='\n')


if __name__ == "__main__":