def _generate_doxyfile(*, manifest, out_dir, temp_dir, dot): """Creates Doxyfile_CXX from Doxyfile_CXX.in.""" input_filename = manifest.Rlocation( "drake/doc/doxygen_cxx/Doxyfile_CXX.in") assert os.path.exists(input_filename) output_filename = join(temp_dir, "Doxyfile_CXX") cmake_configure_file = manifest.Rlocation( "drake/tools/workspace/cmake_configure_file") assert os.path.exists(cmake_configure_file) definitions = {} definitions["INPUT_ROOT"] = temp_dir definitions["OUTPUT_DIRECTORY"] = out_dir if dot: definitions["DOXYGEN_DOT_FOUND"] = "YES" definitions["DOXYGEN_DOT_EXECUTABLE"] = dot else: definitions["DOXYGEN_DOT_FOUND"] = "NO" definitions["DOXYGEN_DOT_EXECUTABLE"] = "" check_call([ cmake_configure_file, "--input", input_filename, "--output", output_filename, ] + ["-D%s=%s" % (key, value) for key, value in definitions.items()]) assert os.path.exists(output_filename) return output_filename
def _build(*, out_dir, temp_dir): """Callback function that implements the bulk of main(). Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. """ # Create a hermetic copy of our input. This helps ensure that only files # listed in BUILD.bazel will render onto the website. symlink_input("drake/doc/styleguide/jekyll_input.txt", temp_dir, copy=True, strip_prefix=[ "drake/doc/styleguide/", "styleguide/", ]) # Prepare the files for Jekyll. _add_title(temp_dir=temp_dir, filename="pyguide.md", title="Google Python Style Guide for Drake") # Run the documentation generator. check_call([ "/usr/bin/jekyll", "build", "--source", temp_dir, "--destination", out_dir, ]) # The filenames to suggest as the starting points for preview. return ["cppguide.html", "pyguide.html"]
def _build(*, out_dir, temp_dir): """Callback function that implements the bulk of main(). Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. """ # Create a hermetic copy of our input. This helps ensure that only files # listed in BUILD.bazel will render onto the website. symlink_input("drake/doc/pages_input.txt", temp_dir, copy=True) # Run the documentation generator. check_call([ "jekyll", "build", "--source", os.path.join(temp_dir, "drake/doc"), "--destination", out_dir, ]) # Tidy up. perl_cleanup_html_output(out_dir=out_dir) # The filename to suggest as the starting point for preview; in this case, # it's an empty filename (i.e., the index page). return [""]
def _build(*, out_dir, temp_dir, modules, quick): """Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. """ manifest = runfiles.Create() # Find drake's sources. drake_workspace = os.path.dirname( os.path.realpath(manifest.Rlocation("drake/.bazelproject"))) assert os.path.exists(drake_workspace), drake_workspace assert os.path.exists(join(drake_workspace, "WORKSPACE")), drake_workspace # Find doxygen. doxygen = manifest.Rlocation("doxygen/doxygen") assert os.path.exists(doxygen), doxygen # Find dot. dot = "/usr/bin/dot" assert os.path.exists(dot), dot # Configure doxygen. doxyfile = _generate_doxyfile(manifest=manifest, out_dir=out_dir, temp_dir=temp_dir, dot=(dot if not quick else "")) # Prepare our input. symlink_input("drake/doc/doxygen_cxx/doxygen_input.txt", temp_dir) _symlink_headers(drake_workspace=drake_workspace, temp_dir=temp_dir, modules=modules) # Run doxygen. check_call([doxygen, doxyfile], cwd=temp_dir) # Post-process its log, and check for errors. If we are building only a # subset of the docs, we are likely to encounter errors due to the missing # sections, so we'll only enable the promotion of warnings to errors when # we're building all of the C++ documentation. check_for_errors = (len(modules) == 0) with open(f"{temp_dir}/doxygen.log", encoding="utf-8") as f: lines = [ line.strip().replace(f"{temp_dir}/", "") for line in f.readlines() ] _postprocess_doxygen_log(lines, check_for_errors) # The nominal pages to offer for preview. return ["", "classes.html", "modules.html"]
def _build(*, out_dir, temp_dir, modules, quick): """Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. """ manifest = runfiles.Create() # Find drake's sources. drake_workspace = os.path.dirname(os.path.realpath( manifest.Rlocation("drake/.bazelproject"))) assert os.path.exists(drake_workspace), drake_workspace assert os.path.exists(join(drake_workspace, "WORKSPACE")), drake_workspace # Find doxygen. doxygen = manifest.Rlocation("doxygen/doxygen") assert os.path.exists(doxygen), doxygen # Find dot. dot = "/usr/bin/dot" assert os.path.exists(dot), dot # Configure doxygen. doxyfile = _generate_doxyfile( manifest=manifest, out_dir=out_dir, temp_dir=temp_dir, dot=(dot if not quick else "")) # Prepare our input. symlink_input( "drake/doc/doxygen_cxx/doxygen_input.txt", temp_dir) _symlink_headers( drake_workspace=drake_workspace, temp_dir=temp_dir, modules=modules) # Run doxygen. check_call([doxygen, doxyfile], cwd=temp_dir) # The nominal pages to offer for preview. return ["", "classes.html", "modules.html"]
def _generate_doxygen_header(*, doxygen, temp_dir): """Creates Drake's header.html based on a patch to Doxygen's default header template. """ # This matches Doxyfile_CXX. header_path = f"{temp_dir}/drake/doc/doxygen_cxx/header.html" # Extract the default templates from the Doxygen binary. We only want the # header, but it forces us to create all three in this exact order. scratch_files = [ "header.html.orig", "footer.html.orig", "customdoxygen.css.orig", ] check_call([doxygen, "-w", "html"] + scratch_files, cwd=temp_dir) shutil.copy(f"{temp_dir}/header.html.orig", header_path) for orig in scratch_files: os.remove(f"{temp_dir}/{orig}") # Apply our patch. patch_file = f"{header_path}.patch" check_call(["/usr/bin/patch", header_path, patch_file])
def _build(*, out_dir, temp_dir, modules, quick): """Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. """ manifest = runfiles.Create() # Find drake's sources. drake_workspace = os.path.dirname( os.path.realpath(manifest.Rlocation("drake/.bazelproject"))) assert os.path.exists(drake_workspace), drake_workspace assert os.path.exists(join(drake_workspace, "WORKSPACE")), drake_workspace # Find doxygen. doxygen = manifest.Rlocation("doxygen/doxygen") assert os.path.exists(doxygen), doxygen # Find dot. dot = "/usr/bin/dot" assert os.path.exists(dot), dot # Configure doxygen. doxyfile = _generate_doxyfile(manifest=manifest, out_dir=out_dir, temp_dir=temp_dir, dot=(dot if not quick else "")) # Prepare our input. symlink_input("drake/doc/doxygen_cxx/doxygen_input.txt", temp_dir) _symlink_headers(drake_workspace=drake_workspace, temp_dir=temp_dir, modules=modules) # Run doxygen. check_call([doxygen, doxyfile], cwd=temp_dir) # Post-process its log, and check for errors. If we are building only a # subset of the docs, we are likely to encounter errors due to the missing # sections, so we'll only enable the promotion of warnings to errors when # we're building all of the C++ documentation. check_for_errors = (len(modules) == 0) with open(f"{temp_dir}/doxygen.log", encoding="utf-8") as f: lines = [ line.strip().replace(f"{temp_dir}/", "") for line in f.readlines() ] _postprocess_doxygen_log(lines, check_for_errors) # Collect the list of all HTML output files. html_files = [] for dirpath, _, filenames in os.walk(out_dir): for filename in filenames: if filename.endswith(".html"): html_files.append(relpath(join(dirpath, filename), out_dir)) # Fix the formatting of deprecation text (see drake#15619 for an example). perl_statements = [ # Remove quotes around the removal date. r's#(removed from Drake on or after) "(....-..-..)" *\.#\1 \2.#;', # Remove all quotes within the explanation text, i.e., the initial and # final quotes, as well as internal quotes that might be due to C++ # multi-line string literals. # - The quotes must appear after a "_deprecatedNNNNNN" anchor. # - The quotes must appear before a "<br />" end-of-line. # Example lines: # <dl class="deprecated"><dt><b><a class="el" href="deprecated.html#_deprecated000013">Deprecated:</a></b></dt><dd>"Use RotationMatrix::MakeFromOneVector()." <br /> # noqa # <dd><a class="anchor" id="_deprecated000013"></a>"Use RotationMatrix::MakeFromOneVector()." <br /> # noqa r'while (s#(?<=_deprecated\d{6}")([^"]*)"(.*?<br)#\1\2#) {};', ] while html_files: # Work in batches of 100, so we don't overflow the argv limit. first, html_files = html_files[:100], html_files[100:] check_call(["perl", "-pi", "-e", "".join(perl_statements)] + first, cwd=out_dir) # The nominal pages to offer for preview. return ["", "classes.html", "modules.html"]
def _build(*, out_dir, temp_dir, modules): """Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. If modules are provided, only generate those modules and their children. """ assert len(os.listdir(temp_dir)) == 0 assert len(os.listdir(out_dir)) == 0 sphinx_build = "/usr/share/sphinx/scripts/python3/sphinx-build" if not os.path.isfile(sphinx_build): print("Please re-run 'sudo setup/ubuntu/install_prereqs.sh' with the " "'--with-doc-only' flag") sys.exit(1) # Create a hermetic copy of our input. This helps ensure that only files # listed in BUILD.bazel will render onto the website. symlink_input("drake/doc/pydrake/sphinx_input.txt", temp_dir, strip_prefix=["drake/doc/"]) input_dir = join(temp_dir, "pydrake") # Process the command-line request for which modules to document. all_modules = _get_pydrake_modules() if not modules: modules_to_document = set(all_modules) else: modules_to_document = set() for x in modules: if x not in all_modules: print(f"error: Unknown module '{x}'") sys.exit(1) # Add the requested module and its parents. tokens = x.split(".") while tokens: modules_to_document.add(".".join(tokens)) tokens.pop() # Add the requsted module's children. for y in all_modules: if y.startswith(x + "."): modules_to_document.add(y) # Generate tables of contents. for name in sorted(list(modules_to_document)): if name == "pydrake": rst_name = "index.rst" else: rst_name = name + ".rst" _write_module(name, join(input_dir, rst_name)) # Run the documentation generator. os.environ["LANG"] = "en_US.UTF-8" check_call([ sphinx_build, "-b", "html", # HTML output. "-a", "-E", # Don't use caching. "-N", # Disable colored output. "-T", # Traceback (for plugin). "-d", join(temp_dir, "doctrees"), input_dir, out_dir, ]) # The filename to suggest as the starting point for preview; in this case, # it's an empty filename (i.e., the index page). return [""]
def _build(*, out_dir, temp_dir, quick, modules): """Callback function that implements the bulk of main(). Generates into out_dir; writes scratch files into temp_dir. As a precondition, both directories must already exist and be empty. If provided, the given modules can be either API reference modules such as "drake.math" (C++) or "pydrake.math" (Python), or else the name of website sections such as "pages", "styleguide", "pydrake", "doxygen_cxx", or etc. """ # Find all of our helper tools. manifest = runfiles.Create() pages_build = manifest.Rlocation("drake/doc/pages") styleguide_build = manifest.Rlocation("drake/doc/styleguide/build") pydrake_build = manifest.Rlocation("drake/doc/pydrake/build") doxygen_build = manifest.Rlocation("drake/doc/doxygen_cxx/build") for item in [pages_build, styleguide_build, pydrake_build, doxygen_build]: assert item and os.path.exists(item), item # Figure out which modules to ask for from each helper tool. do_pages = True do_styleguide = True do_pydrake = True do_doxygen = True do_sitemap = True pydrake_modules = [] doxygen_modules = [] if modules: do_pages = False do_styleguide = False do_pydrake = False do_doxygen = False do_sitemap = False for module in modules: if module in ["pages"]: do_pages = True elif module in ["styleguide", "cppguide", "pyguide"]: do_styleguide = True elif module in ["pydrake"]: do_pydrake = True elif module in ["doxygen_cxx", "doxygen", "cxx"]: do_doxygen = True elif module in ["sitemap"]: do_sitemap = True elif module.startswith("pydrake."): do_pydrake = True pydrake_modules.append(module) elif module.startswith("drake."): do_doxygen = True doxygen_modules.append(module) else: print(f"error: Unknown module '{module}'") sys.exit(1) # Invoke all of our helper tools. if do_pages: check_call([pages_build, f"--out_dir={out_dir}"]) if do_styleguide: check_call([styleguide_build, f"--out_dir={out_dir}/styleguide"]) if do_pydrake: check_call([pydrake_build, f"--out_dir={out_dir}/pydrake"] + pydrake_modules) if do_doxygen: maybe_quick = ["--quick"] if quick else [] check_call([doxygen_build, f"--out_dir={out_dir}/doxygen_cxx"] + doxygen_modules + maybe_quick) if do_sitemap: _build_sitemap(out_dir) # The filenames to suggest as the starting point for preview. result = [] result.append("") if do_pages else None result.append("styleguide/cppguide.html") if do_styleguide else None result.append("styleguide/pyguide.html") if do_styleguide else None result.append("pydrake/") if do_pydrake else None result.append("doxygen_cxx/") if do_doxygen else None return result