From 877f4f1704161eb65a7587e2b1169308e2b5b284 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 15:11:06 +0200 Subject: [PATCH 01/16] Initial commit Copied with minor adjustments from mpi_cmake_modules. --- .gitignore | 3 + LICENSE | 29 + README.md | 0 mpiis_doc_build/__init__.py | 0 mpiis_doc_build/__main__.py | 62 ++ mpiis_doc_build/build.py | 663 ++++++++++++++++++ mpiis_doc_build/resources/Doxyfile.in | 69 ++ .../resources/__init__.py.empty.in | 4 + mpiis_doc_build/resources/py_filter.sh | 2 + .../resources/sphinx/doxygen/Doxyfile.in | 52 ++ .../resources/sphinx/sphinx/cmake_doc.rst.in | 4 + .../resources/sphinx/sphinx/conf.py | 1 + .../resources/sphinx/sphinx/conf.py.in | 240 +++++++ .../sphinx/sphinx/doxygen_index.rst.in | 10 + .../sphinx/doxygen_index_one_page.rst.in | 14 + .../sphinx/general_documentation.rst.in | 8 + .../resources/sphinx/sphinx/index.rst.in | 22 + pyproject.toml | 33 + 18 files changed, 1216 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 mpiis_doc_build/__init__.py create mode 100644 mpiis_doc_build/__main__.py create mode 100644 mpiis_doc_build/build.py create mode 100644 mpiis_doc_build/resources/Doxyfile.in create mode 100644 mpiis_doc_build/resources/__init__.py.empty.in create mode 100755 mpiis_doc_build/resources/py_filter.sh create mode 100644 mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in create mode 120000 mpiis_doc_build/resources/sphinx/sphinx/conf.py create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/conf.py.in create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in create mode 100644 mpiis_doc_build/resources/sphinx/sphinx/index.rst.in create mode 100644 pyproject.toml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c9a331 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build/ +venv/ +*.egg-info diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..56e2937 --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2022, New York University and Max Planck Gesellschaft. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/mpiis_doc_build/__init__.py b/mpiis_doc_build/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mpiis_doc_build/__main__.py b/mpiis_doc_build/__main__.py new file mode 100644 index 0000000..74f3487 --- /dev/null +++ b/mpiis_doc_build/__main__.py @@ -0,0 +1,62 @@ +import argparse +import pathlib + +from .build import build_documentation + + +def main(): + def AbsolutePath(path): + return pathlib.Path(path).absolute() + + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--output-dir", + required=True, + type=AbsolutePath, + help="Build directory", + ) + parser.add_argument( + "--package-dir", + required=True, + type=AbsolutePath, + help="Package directory", + ) + parser.add_argument( + "--python-dir", + type=AbsolutePath, + help="""Directory containing the Python package. If not set, it is + auto-detected inside the package directory + """, + ) + parser.add_argument( + "--project-version", required=True, type=str, help="Package version" + ) + parser.add_argument( + "--force", + "-f", + action="store_true", + help="Do not ask before deleting files.", + ) + args = parser.parse_args() + + if not args.force and args.output_dir.exists(): + print( + "Output directory {} already exists." + " It will be deleted if you proceed!".format(args.output_dir) + ) + c = input("Continue? [y/N] ") + + if c not in ["y", "Y", "yes"]: + print("Abort.") + return + + build_documentation( + args.output_dir, + args.package_dir, + args.project_version, + python_pkg_path=args.python_dir, + ) + + +if __name__ == "__main__": + main() diff --git a/mpiis_doc_build/build.py b/mpiis_doc_build/build.py new file mode 100644 index 0000000..1be9af1 --- /dev/null +++ b/mpiis_doc_build/build.py @@ -0,0 +1,663 @@ +"""documentation_builder.py + +Build the documentation based on sphinx and the read_the_doc layout. + +License BSD-3-Clause +Copyright (c) 2021, New York University and Max Planck Gesellschaft. +""" + +import subprocess +import shutil +import fnmatch +import textwrap +import os +import sys +import typing +from pathlib import Path + + +PathLike = typing.Union[str, os.PathLike] + + +# Check that a given file can be accessed with the correct mode. +# Additionally check that `file` is not a directory, as on Windows +# directories pass the os.access check. +def _access_check(fn, mode): + return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) + + +_WIN_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC" + + +# TODO is which really needed? +def which(cmd, mode=os.F_OK | os.X_OK, path=None): + """Given a command, mode, and a PATH string, return the path which + conforms to the given mode on the PATH, or None if there is no such + file. + + `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result + of os.environ.get("PATH"), or can be overridden with a custom search + path. + + """ + # If we're given a path with a directory part, look it up directly rather + # than referring to PATH directories. This includes checking relative to the + # current directory, e.g. ./script + if os.path.dirname(cmd): + if _access_check(cmd, mode): + return cmd + return None + + use_bytes = isinstance(cmd, bytes) + + if path is None: + path = os.environ.get("PATH", None) + if path is None: + try: + path = os.confstr("CS_PATH") + except (AttributeError, ValueError): + # os.confstr() or CS_PATH is not available + path = os.defpath + # bpo-35755: Don't use os.defpath if the PATH environment variable is + # set to an empty string + + # PATH='' doesn't match, whereas PATH=':' looks in the current directory + if not path: + return None + + if use_bytes: + path = os.fsencode(path) + path = path.split(os.fsencode(os.pathsep)) + else: + path = os.fsdecode(path) + path = path.split(os.pathsep) + + if sys.platform == "win32": + # The current directory takes precedence on Windows. + curdir = os.curdir + if use_bytes: + curdir = os.fsencode(curdir) + if curdir not in path: + path.insert(0, curdir) + + # PATHEXT is necessary to check on Windows. + pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT + pathext = [ext for ext in pathext_source.split(os.pathsep) if ext] + + if use_bytes: + pathext = [os.fsencode(ext) for ext in pathext] + # See if the given file matches any of the expected path extensions. + # This will allow us to short circuit when given "python.exe". + # If it does match, only test that one, otherwise we have to try + # others. + if any(cmd.lower().endswith(ext.lower()) for ext in pathext): + files = [cmd] + else: + files = [cmd + ext for ext in pathext] + else: + # On other platforms you don't have things like PATHEXT to tell you + # what file suffixes are executable, so just pass on cmd as-is. + files = [cmd] + + seen = set() + for dir in path: + normdir = os.path.normcase(dir) + if normdir not in seen: + seen.add(normdir) + for thefile in files: + name = os.path.join(dir, thefile) + if _access_check(name, mode): + return name + return None + + +def _get_cpp_file_patterns() -> typing.List[str]: + return ["*.h", "*.hh", "*.hpp", "*.hxx", "*.cpp", "*.c", "*.cc"] + + +def _find_doxygen() -> str: + """Find the full path to the doxygen executable. + + Raises: + Exception: if the doxygen executable is not found. + + Returns: + The full path to the doxygen executable. + """ + exec_path = which("doxygen") + if exec_path is not None: + return exec_path + raise Exception( + "doxygen executable not found. You may try " "'(sudo ) apt install doxygen*'" + ) + + +def _find_breathe_apidoc() -> str: + """Find the full path to the breathe-apidoc executable. + + Raises: + Exception: if the breathe-apidoc executable is not found. + + Returns: + The full path to the black executable. + """ + exec_path = which("breathe-apidoc") + if exec_path is not None: + return exec_path + raise Exception( + "breathe-apidoc executable not found. You may try " + "'(sudo -H) pip3 install breathe'" + ) + + +def _find_sphinx_apidoc() -> str: + """Find the full path to the sphinx-apidoc executable. + + Raises: + Exception: if the sphinx-apidoc executable is not found. + + Returns: + The full path to the black executable. + """ + exec_path = which("sphinx-apidoc") + if exec_path is not None: + return exec_path + raise Exception( + "sphinx-apidoc executable not found. You may try " + "'(sudo -H) pip3 install sphinx'" + ) + + +def _find_sphinx_build() -> str: + """Find the full path to the sphinx-build executable. + + Raises: + Exception: if the sphinx-build executable is not found. + + Returns: + The full path to the black executable. + """ + exec_path = which("sphinx-build") + if exec_path is not None: + return exec_path + + raise Exception( + "sphinx-build executable not found. You may try " + "'(sudo -H) pip3 install sphinx'" + ) + + +def _resource_path(project_source_dir: Path) -> Path: + """ + Fetch the resources path. This will contains all the configuration files + for the different executables: Doxyfile, conf.py, etc. + + Args: + project_source_dir (str): Path to the source file of the project. + + Raises: + Exception: if the resources folder is not found. + + Returns: + pathlib.Path: Path to the configuration files. + """ + # TODO project_source_dir is unused + + this_dir = Path(__file__).parent + resource_path = this_dir / "resources" + + assert resource_path.is_dir() + + return resource_path + + # assert project_source_dir.is_dir() + + # # Find the resources from the package. + # project_name = project_source_dir.name + # # TODO: can this special case be avoided? + # if project_name == "mpi_cmake_modules": + # resource_path = project_source_dir / "resources" + # if not resource_path.is_dir(): + # raise Exception( + # "failed to find the resource directory in " + # + str(mpi_cmake_modules.__path__) + # ) + # return resource_path + + # # Find the resources from the installation of this package. + # for module_path in mpi_cmake_modules.__path__: + # resource_path = Path(module_path) / "resources" + # if resource_path.is_dir(): + # return resource_path + + # raise Exception( + # "failed to find the resource directory in " + str(mpi_cmake_modules.__path__) + # ) + + +def _build_doxygen_xml(doc_build_dir: Path, project_source_dir: Path): + """ + Use doxygen to parse the C++ source files and generate a corresponding xml + entry. + + Args: + doc_build_dir (str): Path to where the doc should be built + project_source_dir (str): Path to the source file of the project. + """ + # Get project_name + project_name = project_source_dir.name + + # Get the doxygen executable. + doxygen = _find_doxygen() + + # Get the resources path. + resource_path = _resource_path(project_source_dir) + + # get the Doxyfile.in file + doxyfile_in = resource_path / "sphinx" / "doxygen" / "Doxyfile.in" + assert doxyfile_in.is_file() + + # Which files are going to be parsed. + doxygen_file_patterns = " ".join(_get_cpp_file_patterns()) + + # Where to put the doxygen output. + doxygen_output = doc_build_dir / "doxygen" + + # Parse the Doxyfile.in and replace the value between '@' + with open(doxyfile_in, "rt") as f: + doxyfile_out_text = ( + f.read() + .replace("@PROJECT_NAME@", project_name) + .replace("@PROJECT_SOURCE_DIR@", os.fspath(project_source_dir)) + .replace("@DOXYGEN_FILE_PATTERNS@", doxygen_file_patterns) + .replace("@DOXYGEN_OUTPUT@", str(doxygen_output)) + ) + doxyfile_out = doxygen_output / "Doxyfile" + doxyfile_out.parent.mkdir(parents=True, exist_ok=True) + with open(doxyfile_out, "wt") as f: + f.write(doxyfile_out_text) + + bashCommand = doxygen + " " + str(doxyfile_out) + process = subprocess.Popen( + bashCommand.split(), stdout=subprocess.PIPE, cwd=str(doxygen_output) + ) + output, error = process.communicate() + print("Doxygen output:\n", output.decode("UTF-8")) + print("Doxygen error:\n", error) + print("") + + +def _build_breath_api_doc(doc_build_dir: Path): + """ + Use breathe_apidoc to parse the xml output from Doxygen and generate + '.rst' files. + + Args: + doc_build_dir (str): Path where to create the temporary output. + """ + breathe_apidoc = _find_breathe_apidoc() + breathe_input = doc_build_dir / "doxygen" / "xml" + breathe_output = doc_build_dir / "breathe_apidoc" + breathe_option = "-f -g class,interface,struct,union,file,namespace,group" + + bashCommand = ( + breathe_apidoc + + " -o " + + str(breathe_output) + + " " + + str(breathe_input) + + " " + + str(breathe_option) + ) + process = subprocess.Popen( + bashCommand.split(), stdout=subprocess.PIPE, cwd=str(doc_build_dir) + ) + output, error = process.communicate() + print("breathe-apidoc output:\n", output.decode("UTF-8")) + print("breathe-apidoc error:\n", error) + print("") + + +def _build_sphinx_api_doc(doc_build_dir: Path, python_source_dir: Path): + """ + Use sphinx_apidoc to parse the python files output from Doxygen and + generate '.rst' files. + + Args: + doc_build_dir (str): Path where to create the temporary output. + project_source_dir (str): Path to the source file of the project. + """ + # define input folder + if python_source_dir.is_dir(): + sphinx_apidoc = _find_sphinx_apidoc() + sphinx_apidoc_input = str(python_source_dir) + sphinx_apidoc_output = str(doc_build_dir) + + bashCommand = ( + sphinx_apidoc + + " --separate " + + " -o " + + sphinx_apidoc_output + + " " + + sphinx_apidoc_input + ) + process = subprocess.Popen( + bashCommand.split(), stdout=subprocess.PIPE, cwd=str(doc_build_dir) + ) + output, error = process.communicate() + print("sphinx-apidoc output:\n", output.decode("UTF-8")) + print("sphinx-apidoc error:\n", error) + + else: + print("No python module for sphinx-apidoc to parse.") + print("") + + +def _build_sphinx_build(doc_build_dir: Path): + """ + Use sphinx_build to parse the cmake and rst files previously generated and + generate the final html layout. + + Args: + doc_build_dir (str): Path where to create the temporary output. + """ + sphinx_build = _find_sphinx_build() + bashCommand = ( + sphinx_build + " -M html " + str(doc_build_dir) + " " + str(doc_build_dir) + ) + process = subprocess.Popen( + bashCommand.split(), stdout=subprocess.PIPE, cwd=str(doc_build_dir) + ) + output, error = process.communicate() + print("sphinx-apidoc output:\n", output.decode("UTF-8")) + print("sphinx-apidoc error:\n", error) + + +def _search_for_cpp_api( + doc_build_dir: Path, project_source_dir: Path, resource_dir: Path +) -> str: + """Search if there is a C++ api do document, and document it. + + Args: + doc_build_dir (str): Path where to create the temporary output. + project_source_dir (str): Path to the source file of the project. + resource_dir (str): Path to the resources files for the build. + + Returns: + str: String added to the main index.rst in case there is a C++ api. + """ + cpp_api = "" + + # Search for C++ files + has_cpp = False + for p in project_source_dir.glob("**/*"): + if any( + fnmatch.fnmatch(str(p), pattern) for pattern in _get_cpp_file_patterns() + ): + has_cpp = True + break + + if has_cpp: + print("Found C++ files, add C++ API documentation") + + # Introduce this toc tree in the main index.rst + cpp_api = textwrap.dedent( + """ + .. toctree:: + :caption: C++ API + :maxdepth: 2 + + doxygen_index + + """ + ) + # Copy the index of the C++ API. + shutil.copy( + resource_dir / "sphinx" / "sphinx" / "doxygen_index_one_page.rst.in", + doc_build_dir / "doxygen_index_one_page.rst", + ) + shutil.copy( + resource_dir / "sphinx" / "sphinx" / "doxygen_index.rst.in", + doc_build_dir / "doxygen_index.rst", + ) + + # Build the doxygen xml files. + _build_doxygen_xml(doc_build_dir, project_source_dir) + # Generate the .rst corresponding to the doxygen xml + _build_breath_api_doc(doc_build_dir) + + else: + print("No C++ files found.") + + return cpp_api + + +def _search_for_python_api( + doc_build_dir: Path, + project_source_dir: Path, + package_path: typing.Optional[Path] = None, +) -> str: + """Search for a Python API and build it's documentation. + + Args: + doc_build_dir (str): Path where to create the temporary output. + project_source_dir (str): Path to the source file of the project. + + Returns: + str: String added to the main index.rst in case there is a Python api. + """ + python_api = "" + + # Get the project name form the source path. + project_name = project_source_dir.name + + if package_path is None: + package_path_candidates = [ + project_source_dir / project_name, + project_source_dir / "python" / project_name, + project_source_dir / "src" / project_name, + ] + for p in package_path_candidates: + if p.is_dir(): + package_path = p + break + + # Search for Python API. + if package_path: + # Introduce this toc tree in the main index.rst + python_api = textwrap.dedent( + """ + .. toctree:: + :caption: Python API + :maxdepth: 3 + + modules + + * :ref:`modindex` + + """ + ) + _build_sphinx_api_doc(doc_build_dir, package_path) + return python_api + + +def _search_for_cmake_api( + doc_build_dir: Path, project_source_dir: Path, resource_dir: Path +) -> str: + cmake_api = "" + + # Search for CMake API. + cmake_files = [ + p.resolve() + for p in project_source_dir.glob("cmake/*") + if p.suffix in [".cmake"] or p.name == "CMakeLists.txt" + ] + if cmake_files: + # Introduce this toc tree in the main index.rst + cmake_api = textwrap.dedent( + """ + .. toctree:: + :caption: CMake API + :maxdepth: 3 + + cmake_doc + + """ + ) + doc_cmake_module = "" + for cmake_file in cmake_files: + doc_cmake_module += cmake_file.stem + "\n" + doc_cmake_module += len(cmake_file.stem) * "-" + "\n\n" + doc_cmake_module += ".. cmake-module:: cmake/" + cmake_file.name + "\n\n" + with open(resource_dir / "sphinx" / "sphinx" / "cmake_doc.rst.in", "rt") as f: + out_text = f.read().replace("@DOC_CMAKE_MODULE@", doc_cmake_module) + with open(str(doc_build_dir / "cmake_doc.rst"), "wt") as f: + f.write(out_text) + + shutil.copytree( + project_source_dir / "cmake", + doc_build_dir / "cmake", + ) + + return cmake_api + + +def _search_for_general_documentation( + doc_build_dir: Path, project_source_dir: Path, resource_dir: Path +) -> str: + general_documentation = "" + + doc_path_candidates = [ + project_source_dir / "doc", + project_source_dir / "docs", + ] + doc_path = None + for p in doc_path_candidates: + if p.is_dir(): + doc_path = p + break + + # Search for additional doc. + if doc_path: + general_documentation = textwrap.dedent( + """ + .. toctree:: + :caption: General Documentation + :maxdepth: 2 + + general_documentation + + """ + ) + shutil.copy( + resource_dir / "sphinx" / "sphinx" / "general_documentation.rst.in", + doc_build_dir / "general_documentation.rst", + ) + shutil.copytree( + doc_path, + doc_build_dir / "doc", + ) + return general_documentation + + +def build_documentation( + build_dir: PathLike, + project_source_dir: PathLike, + project_version, + python_pkg_path: typing.Optional[PathLike] = None, +): + # make sure all paths are of type Path + doc_build_dir = Path(build_dir) + project_source_dir = Path(project_source_dir) + if python_pkg_path is not None: + python_pkg_path = Path(python_pkg_path) + + # + # Initialize the paths + # + + # Get the project name form the source path. + project_name = project_source_dir.name + + # Create the folder architecture inside the build folder. + shutil.rmtree(doc_build_dir, ignore_errors=True) + doc_build_dir.mkdir(parents=True, exist_ok=True) + + # Get the path to resource files. + resource_dir = Path(_resource_path(project_source_dir)) + + # + # Parametrize the final doc layout depending we have CMake/Python/C++ api. + # + + # String to replace in the main index.rst + + cpp_api = _search_for_cpp_api(doc_build_dir, project_source_dir, resource_dir) + + python_api = _search_for_python_api( + doc_build_dir, project_source_dir, python_pkg_path + ) + + cmake_api = _search_for_cmake_api(doc_build_dir, project_source_dir, resource_dir) + + general_documentation = _search_for_general_documentation( + doc_build_dir, project_source_dir, resource_dir + ) + + # + # Configure the config.py and the index.rst. + # + + # configure the index.rst.in. + header = "Welcome to " + project_name + "'s documentation!" + header += "\n" + len(header) * "=" + "\n" + with open(resource_dir / "sphinx" / "sphinx" / "index.rst.in", "rt") as f: + out_text = ( + f.read() + .replace("@HEADER@", header) + .replace("@GENERAL_DOCUMENTATION@", general_documentation) + .replace("@CPP_API@", cpp_api) + .replace("@PYTHON_API@", python_api) + .replace("@CMAKE_API@", cmake_api) + ) + with open(doc_build_dir / "index.rst", "wt") as f: + f.write(out_text) + + # configure the config.py.in. + with open(resource_dir / "sphinx" / "sphinx" / "conf.py.in", "rt") as f: + out_text = ( + f.read() + .replace("@PROJECT_SOURCE_DIR@", os.fspath(project_source_dir)) + .replace("@PROJECT_NAME@", project_name) + .replace("@PROJECT_VERSION@", project_version) + .replace("@DOXYGEN_XML_OUTPUT@", str(doc_build_dir / "doxygen" / "xml")) + ) + with open(doc_build_dir / "conf.py", "wt") as f: + f.write(out_text) + + # + # Copy the license and readme file. + # + readme = [ + p.resolve() + for p in project_source_dir.glob("*") + if p.name.lower() in ["readme.md", "readme.rst"] + ] + # sort alphabetically so that "readme.md" is preferred in case both are + # found + readme = sorted(readme) + if readme: + shutil.copy(readme[0], doc_build_dir / "readme.md") + + license_file = [ + p.resolve() + for p in project_source_dir.glob("*") + if p.name in ["LICENSE", "license.txt"] + ] + if license_file: + shutil.copy(license_file[0], doc_build_dir / "license.txt") + + # + # Generate the html doc + # + _build_sphinx_build(doc_build_dir) diff --git a/mpiis_doc_build/resources/Doxyfile.in b/mpiis_doc_build/resources/Doxyfile.in new file mode 100644 index 0000000..ffc39f4 --- /dev/null +++ b/mpiis_doc_build/resources/Doxyfile.in @@ -0,0 +1,69 @@ +# +# @file Doxyfile.in +# @author Maximilien Naveau (maximilien.naveau@gmail.com) +# @copyright Copyright (c) 2019, New York University and Max Planck Gesellschaft. +# @license License BSD-3 clause +# @date 2019-05-06 +# +# @brief This file is the parameter file for the Doxygen ouput. All the variables +# surrounded by "@" are CMake variable. See doxygen.cmake for details. +# + +PROJECT_NAME = "@PROJECT_NAME@" +OUTPUT_DIRECTORY = . +JAVADOC_AUTOBRIEF = YES +TAB_SIZE = 4 +EXTRACT_ALL = NO +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES + +# Add examples from this folder. +EXAMPLE_PATH = @PROJECT_SOURCE_DIR@/demos + +# Look for any files in this folder ...(see RECURSIVE) +INPUT = @PROJECT_SOURCE_DIR@ +# (see INPUT)... and recursively. +RECURSIVE = YES + +# Set the root path for the images. +IMAGE_PATH = @PROJECT_SOURCE_DIR@ + +# Filter the python docstring as Doxygen ones. +FILTER_PATTERNS = *.py=@MPI_CMAKE_MODULES_RESOURCES_DIR@/py_filter + +# Remove the absolute path to the package from the file path. +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ + +# Get all the intersting files by looking for them recursively and according to +# their extension +FILE_PATTERNS = *.h *.hpp *.hh *.cpp *.c *.cc *.hxx *.dox *.md *.py + +# Side bar with the tree. +GENERATE_TREEVIEW = YES + +# remove the useless folders from the documentation. +EXCLUDE_PATTERNS = */*deprecated*/* +EXCLUDE_PATTERNS += */*build*/* +EXCLUDE_PATTERNS += */tests/* +EXCLUDE_PATTERNS += */internal/* + +# removing joss paper +EXCLUDE_PATTERNS += */paper.md + +GENERATE_TAGFILE = @PROJECT_NAME@.tag +GENERATE_HTML = YES +GENERATE_XML = YES + +# Include and resize and image. +ALIASES += imageSize{3}="\htmlonly \endhtmlonly \image html \1 \"\3\"" +# add a `@license` tag +ALIASES += "license=\xrefitem license \"License\" \"License\" " + +# Allow searching from teh client side. +SERVER_BASED_SEARCH = NO +SEARCHENGINE = YES + +# TODO manage this tag file with the cmake macro: +# e.g. "@CMAKE_INSTALL_PREFIX@/share/doc/@DEPENDENCY_PROJECT@.doxytag = @CMAKE_INSTALL_PREFIX@/share/doc/@DEPENDENCY_PROJECT_NAME" +# TAGFILES = @@PROJECT_NAME@_TAGFILES@ diff --git a/mpiis_doc_build/resources/__init__.py.empty.in b/mpiis_doc_build/resources/__init__.py.empty.in new file mode 100644 index 0000000..ac01ec0 --- /dev/null +++ b/mpiis_doc_build/resources/__init__.py.empty.in @@ -0,0 +1,4 @@ +""" +License BSD-3-Clause +Copyright (c) 2019, New York University and Max Planck Gesellschaft. +""" \ No newline at end of file diff --git a/mpiis_doc_build/resources/py_filter.sh b/mpiis_doc_build/resources/py_filter.sh new file mode 100755 index 0000000..da843b7 --- /dev/null +++ b/mpiis_doc_build/resources/py_filter.sh @@ -0,0 +1,2 @@ +#!/bin/bash +doxypypy -a -c $1 \ No newline at end of file diff --git a/mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in b/mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in new file mode 100644 index 0000000..f2f1e18 --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in @@ -0,0 +1,52 @@ +# +# Doxyfile for the C++ API +# + +PROJECT_NAME = @PROJECT_NAME@ + +# Next, choose the location of the resulting XML database: +OUTPUT_DIRECTORY = . + +# Program listing vastly increases the size of XML so it's recommended +# to turning it OFF: +XML_PROGRAMLISTING = YES + +# The next one is essential! Sphinx uses lowercase reference IDs, +# so Doxygen can't use mixed-case IDs: +CASE_SENSE_NAMES = NO + +# The next one is important for C++ projects -- otherwise Doxygen +# may generate lots of bogus links to template arguments: +HIDE_UNDOC_RELATIONS = YES + +JAVADOC_AUTOBRIEF = YES +TAB_SIZE = 4 + +# Extract as much as we can +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES + +EXAMPLE_PATH = @PROJECT_SOURCE_DIR@/demos +INPUT = @PROJECT_SOURCE_DIR@ +IMAGE_PATH = @PROJECT_SOURCE_DIR@ +FILE_PATTERNS = @DOXYGEN_FILE_PATTERNS@ +EXCLUDE_PATTERNS = @DOXYGEN_EXCLUDE_PATTERNS@ +EXCLUDE_PATTERNS += @PROJECT_SOURCE_DIR@/src/* +EXCLUDE_PATTERNS += @PROJECT_SOURCE_DIR@/test/* +EXCLUDE_PATTERNS += @PROJECT_SOURCE_DIR@/tests/* +EXCLUDE_PATTERNS += @PROJECT_SOURCE_DIR@/*/*deprecated*/* +EXCLUDE_PATTERNS += @PROJECT_SOURCE_DIR@/*/internal/* +RECURSIVE = YES +GENERATE_LATEX = No +GENERATE_TAGFILE = @DOXYGEN_OUTPUT@/@PROJECT_NAME@.tag +GENERATE_TREEVIEW = YES +GENERATE_HTML = No +GENERATE_XML = YES +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ + +# Include and resize and image. +ALIASES += imageSize{3}="\htmlonly \endhtmlonly \image html \1 \"\3\"" +# add a `@license` tag +ALIASES += "license=\xrefitem license \"License\" \"License\" " diff --git a/mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in b/mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in new file mode 100644 index 0000000..2d7c25c --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in @@ -0,0 +1,4 @@ +CMake API +========= + +@DOC_CMAKE_MODULE@ diff --git a/mpiis_doc_build/resources/sphinx/sphinx/conf.py b/mpiis_doc_build/resources/sphinx/sphinx/conf.py new file mode 120000 index 0000000..b3dd357 --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/conf.py @@ -0,0 +1 @@ +conf.py.in \ No newline at end of file diff --git a/mpiis_doc_build/resources/sphinx/sphinx/conf.py.in b/mpiis_doc_build/resources/sphinx/sphinx/conf.py.in new file mode 100644 index 0000000..84675d9 --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/conf.py.in @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import datetime +import os +import sys +from m2r import MdInclude +from recommonmark.transform import AutoStructify + +sys.path.insert(0, os.path.abspath("@PYTHON_PACKAGE_LOCATION@")) + +# cmake parser custom module +currentmode = "user" + +# on_rtd is whether we are on readthedocs.org, this line of code grabbed from docs.readthedocs.org +on_rtd = os.environ.get('READTHEDOCS', None) == 'True' + + +# -- Project information ----------------------------------------------------- + +project = u'@PROJECT_NAME@' +now = datetime.datetime.now() +copyright = ('Copyright (c) ' + str(now.year) + + ', See license.txt file or the license section.') +author = u'See the readme file.' + +# Version control +version_cmake = u'@PROJECT_VERSION@' + +# The short X.Y version +version = version_cmake +# The full version, including alpha/beta/rc tags +release = version_cmake + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.napoleon', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', # doxygen math in doc + 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', + 'sphinx.ext.githubpages', + 'sphinxcontrib.moderncmakedomain', + 'recommonmark', + 'breathe', # to define the C++ api with breathe-apidoc +] + +# Root of the github page: +github_doc_root = u'https://machines-in-motion.github.io/code_documentation/@PROJECT_NAME@' +html_baseurl = github_doc_root + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# breath extension management +breathe_projects = {project: u'@DOXYGEN_XML_OUTPUT@'} +breathe_default_project = project +breathe_default_members = ('members', 'private-members', 'undoc-members') + +# cmake parsing +# primary_domain = 'cmake' +# highlight_language = 'cmake' + +# The suffix(es) of source filenames. +source_suffix = ['.rst', '.md', '.cmake'] + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = None + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["internal/*"] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = None + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +if not on_rtd: + import sphinx_rtd_theme + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = '@PROJECT_NAME@_doc' + + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc.encode('utf-8'), u'@PROJECT_NAME@.tex', u'@PROJECT_NAME@ Documentation', + author, u'manual'), +] + + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + (master_doc, '@PROJECT_NAME@', u'@PROJECT_NAME@ Documentation', + [author], 1) +] + + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, u'@PROJECT_NAME@', u'@PROJECT_NAME@ C++', + author, u'@PROJECT_NAME@', u'One line description of project.', + u'Robotics'), +] + + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + + +# -- Extension configuration ------------------------------------------------- + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True + + +def setup(app): + """ some tools for markdown parsing """ + config = { + # 'url_resolver': lambda url: github_doc_root + url, + 'auto_toc_tree_section': 'Contents', + 'enable_eval_rst': True, + } + app.add_config_value('recommonmark_config', config, True) + app.add_transform(AutoStructify) + + # from m2r to make `mdinclude` work + app.add_config_value('no_underscore_emphasis', False, 'env') + app.add_config_value('m2r_parse_relative_links', False, 'env') + app.add_config_value('m2r_anonymous_references', False, 'env') + app.add_config_value('m2r_disable_inline_math', False, 'env') + app.add_directive('mdinclude', MdInclude) diff --git a/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in b/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in new file mode 100644 index 0000000..eccc7de --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in @@ -0,0 +1,10 @@ +C++ API +======= + +.. toctree:: + :glob: + :maxdepth: 2 + :caption: Table of Content: Index + + breathe_apidoc/* + doxygen_index_one_page.rst diff --git a/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in b/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in new file mode 100644 index 0000000..0a7d735 --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in @@ -0,0 +1,14 @@ +C++ API and example +=================== + +1. Introduction +############### + +This page exist in order to extract the examples from the Doxygen +documentation, Please have look at the end of this page there are all the +examples. + +2. C++ API and example +###################### + +.. doxygenindex:: diff --git a/mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in b/mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in new file mode 100644 index 0000000..92a6fdf --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in @@ -0,0 +1,8 @@ +General Documentation +===================== + +.. toctree:: + :caption: Table of Content: Index + :glob: + + doc/* diff --git a/mpiis_doc_build/resources/sphinx/sphinx/index.rst.in b/mpiis_doc_build/resources/sphinx/sphinx/index.rst.in new file mode 100644 index 0000000..ae21c91 --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/sphinx/index.rst.in @@ -0,0 +1,22 @@ +@HEADER@ + +.. mdinclude:: readme.md + +@GENERAL_DOCUMENTATION@ + +@CPP_API@ + +@PYTHON_API@ + +@CMAKE_API@ + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`search` + +License and Copyrights +---------------------- + +.. mdinclude:: license.txt diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..29a93c7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,33 @@ +[build-system] +requires = ["setuptools>=61.0.0"] +build-backend = "setuptools.build_meta" + + +[project] +name = "mpiis_doc_build" +version = "0.1.0" +description = "Tool to build documentation for C++/Python-Packages used at MPI-IS" +authors = [ + {name = "Maximilien Naveau"}, + {name = "Felix Widmaier"}, +] +maintainers = [ + {name = "Felix Widmaier", email = "felix.widmaier@tuebingen.mpg.de"} +] +readme = "README.md" +license = {file = "LICENSE"} + +dependencies = [ + "breathe", + "doxypypy", + "m2r", + "mistune<2.0.0", + "recommonmark", + "sphinx", + "sphinx-rtd-theme", + "sphinxcontrib-moderncmakedomain", +] + + +[tool.setuptools.package-data] +mpiis_doc_build = ["resources/*.sh", "resources/**/*.in"] From ccdec516db9ba1248b31ba2830db496adc31f4aa Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 15:17:29 +0200 Subject: [PATCH 02/16] Use shutil.which instead of own implementation --- mpiis_doc_build/build.py | 107 +++------------------------------------ 1 file changed, 7 insertions(+), 100 deletions(-) diff --git a/mpiis_doc_build/build.py b/mpiis_doc_build/build.py index 1be9af1..13d589e 100644 --- a/mpiis_doc_build/build.py +++ b/mpiis_doc_build/build.py @@ -6,12 +6,11 @@ Copyright (c) 2021, New York University and Max Planck Gesellschaft. """ -import subprocess -import shutil import fnmatch -import textwrap import os -import sys +import shutil +import subprocess +import textwrap import typing from pathlib import Path @@ -19,98 +18,6 @@ PathLike = typing.Union[str, os.PathLike] -# Check that a given file can be accessed with the correct mode. -# Additionally check that `file` is not a directory, as on Windows -# directories pass the os.access check. -def _access_check(fn, mode): - return os.path.exists(fn) and os.access(fn, mode) and not os.path.isdir(fn) - - -_WIN_DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD;.VBS;.JS;.WS;.MSC" - - -# TODO is which really needed? -def which(cmd, mode=os.F_OK | os.X_OK, path=None): - """Given a command, mode, and a PATH string, return the path which - conforms to the given mode on the PATH, or None if there is no such - file. - - `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result - of os.environ.get("PATH"), or can be overridden with a custom search - path. - - """ - # If we're given a path with a directory part, look it up directly rather - # than referring to PATH directories. This includes checking relative to the - # current directory, e.g. ./script - if os.path.dirname(cmd): - if _access_check(cmd, mode): - return cmd - return None - - use_bytes = isinstance(cmd, bytes) - - if path is None: - path = os.environ.get("PATH", None) - if path is None: - try: - path = os.confstr("CS_PATH") - except (AttributeError, ValueError): - # os.confstr() or CS_PATH is not available - path = os.defpath - # bpo-35755: Don't use os.defpath if the PATH environment variable is - # set to an empty string - - # PATH='' doesn't match, whereas PATH=':' looks in the current directory - if not path: - return None - - if use_bytes: - path = os.fsencode(path) - path = path.split(os.fsencode(os.pathsep)) - else: - path = os.fsdecode(path) - path = path.split(os.pathsep) - - if sys.platform == "win32": - # The current directory takes precedence on Windows. - curdir = os.curdir - if use_bytes: - curdir = os.fsencode(curdir) - if curdir not in path: - path.insert(0, curdir) - - # PATHEXT is necessary to check on Windows. - pathext_source = os.getenv("PATHEXT") or _WIN_DEFAULT_PATHEXT - pathext = [ext for ext in pathext_source.split(os.pathsep) if ext] - - if use_bytes: - pathext = [os.fsencode(ext) for ext in pathext] - # See if the given file matches any of the expected path extensions. - # This will allow us to short circuit when given "python.exe". - # If it does match, only test that one, otherwise we have to try - # others. - if any(cmd.lower().endswith(ext.lower()) for ext in pathext): - files = [cmd] - else: - files = [cmd + ext for ext in pathext] - else: - # On other platforms you don't have things like PATHEXT to tell you - # what file suffixes are executable, so just pass on cmd as-is. - files = [cmd] - - seen = set() - for dir in path: - normdir = os.path.normcase(dir) - if normdir not in seen: - seen.add(normdir) - for thefile in files: - name = os.path.join(dir, thefile) - if _access_check(name, mode): - return name - return None - - def _get_cpp_file_patterns() -> typing.List[str]: return ["*.h", "*.hh", "*.hpp", "*.hxx", "*.cpp", "*.c", "*.cc"] @@ -124,7 +31,7 @@ def _find_doxygen() -> str: Returns: The full path to the doxygen executable. """ - exec_path = which("doxygen") + exec_path = shutil.which("doxygen") if exec_path is not None: return exec_path raise Exception( @@ -141,7 +48,7 @@ def _find_breathe_apidoc() -> str: Returns: The full path to the black executable. """ - exec_path = which("breathe-apidoc") + exec_path = shutil.which("breathe-apidoc") if exec_path is not None: return exec_path raise Exception( @@ -159,7 +66,7 @@ def _find_sphinx_apidoc() -> str: Returns: The full path to the black executable. """ - exec_path = which("sphinx-apidoc") + exec_path = shutil.which("sphinx-apidoc") if exec_path is not None: return exec_path raise Exception( @@ -177,7 +84,7 @@ def _find_sphinx_build() -> str: Returns: The full path to the black executable. """ - exec_path = which("sphinx-build") + exec_path = shutil.which("sphinx-build") if exec_path is not None: return exec_path From 914f241da393aae330b877aea9a0e8347595a4b9 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 15:25:49 +0200 Subject: [PATCH 03/16] Cleanup --- mpiis_doc_build/build.py | 43 ++++++---------------------------------- 1 file changed, 6 insertions(+), 37 deletions(-) diff --git a/mpiis_doc_build/build.py b/mpiis_doc_build/build.py index 13d589e..feb42cd 100644 --- a/mpiis_doc_build/build.py +++ b/mpiis_doc_build/build.py @@ -1,6 +1,4 @@ -"""documentation_builder.py - -Build the documentation based on sphinx and the read_the_doc layout. +"""Build the documentation based on sphinx and doxygen. License BSD-3-Clause Copyright (c) 2021, New York University and Max Planck Gesellschaft. @@ -94,22 +92,17 @@ def _find_sphinx_build() -> str: ) -def _resource_path(project_source_dir: Path) -> Path: +def _resource_path() -> Path: """ - Fetch the resources path. This will contains all the configuration files + Fetch the resources path. It contains all the configuration files for the different executables: Doxyfile, conf.py, etc. - Args: - project_source_dir (str): Path to the source file of the project. - Raises: - Exception: if the resources folder is not found. + AssertionError: if the resources folder is not found. Returns: pathlib.Path: Path to the configuration files. """ - # TODO project_source_dir is unused - this_dir = Path(__file__).parent resource_path = this_dir / "resources" @@ -117,30 +110,6 @@ def _resource_path(project_source_dir: Path) -> Path: return resource_path - # assert project_source_dir.is_dir() - - # # Find the resources from the package. - # project_name = project_source_dir.name - # # TODO: can this special case be avoided? - # if project_name == "mpi_cmake_modules": - # resource_path = project_source_dir / "resources" - # if not resource_path.is_dir(): - # raise Exception( - # "failed to find the resource directory in " - # + str(mpi_cmake_modules.__path__) - # ) - # return resource_path - - # # Find the resources from the installation of this package. - # for module_path in mpi_cmake_modules.__path__: - # resource_path = Path(module_path) / "resources" - # if resource_path.is_dir(): - # return resource_path - - # raise Exception( - # "failed to find the resource directory in " + str(mpi_cmake_modules.__path__) - # ) - def _build_doxygen_xml(doc_build_dir: Path, project_source_dir: Path): """ @@ -158,7 +127,7 @@ def _build_doxygen_xml(doc_build_dir: Path, project_source_dir: Path): doxygen = _find_doxygen() # Get the resources path. - resource_path = _resource_path(project_source_dir) + resource_path = _resource_path() # get the Doxyfile.in file doxyfile_in = resource_path / "sphinx" / "doxygen" / "Doxyfile.in" @@ -491,7 +460,7 @@ def build_documentation( doc_build_dir.mkdir(parents=True, exist_ok=True) # Get the path to resource files. - resource_dir = Path(_resource_path(project_source_dir)) + resource_dir = Path(_resource_path()) # # Parametrize the final doc layout depending we have CMake/Python/C++ api. From 0f928c857a3787ccd343da434f47f2e8b5d1b6bc Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 15:41:52 +0200 Subject: [PATCH 04/16] Add __version__ and --version --- mpiis_doc_build/__init__.py | 3 +++ mpiis_doc_build/__main__.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/mpiis_doc_build/__init__.py b/mpiis_doc_build/__init__.py index e69de29..c1d20e7 100644 --- a/mpiis_doc_build/__init__.py +++ b/mpiis_doc_build/__init__.py @@ -0,0 +1,3 @@ +import importlib.metadata + +__version__ = importlib.metadata.version(__package__) diff --git a/mpiis_doc_build/__main__.py b/mpiis_doc_build/__main__.py index 74f3487..7e3b501 100644 --- a/mpiis_doc_build/__main__.py +++ b/mpiis_doc_build/__main__.py @@ -1,6 +1,7 @@ import argparse import pathlib +from . import __version__ from .build import build_documentation @@ -9,6 +10,12 @@ def AbsolutePath(path): return pathlib.Path(path).absolute() parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--version", + action="version", + help="Show version of mpiis_doc_build.", + version=f"mpiis_doc_build version {__version__}", + ) parser.add_argument( "--output-dir", required=True, From 0ab944c9c899c25040b359d3d8363bfdad096519 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 15:55:57 +0200 Subject: [PATCH 05/16] Remove unused resources files --- mpiis_doc_build/resources/Doxyfile.in | 69 ------------------- .../resources/__init__.py.empty.in | 4 -- mpiis_doc_build/resources/py_filter.sh | 2 - .../resources/sphinx/sphinx/conf.py | 1 - 4 files changed, 76 deletions(-) delete mode 100644 mpiis_doc_build/resources/Doxyfile.in delete mode 100644 mpiis_doc_build/resources/__init__.py.empty.in delete mode 100755 mpiis_doc_build/resources/py_filter.sh delete mode 120000 mpiis_doc_build/resources/sphinx/sphinx/conf.py diff --git a/mpiis_doc_build/resources/Doxyfile.in b/mpiis_doc_build/resources/Doxyfile.in deleted file mode 100644 index ffc39f4..0000000 --- a/mpiis_doc_build/resources/Doxyfile.in +++ /dev/null @@ -1,69 +0,0 @@ -# -# @file Doxyfile.in -# @author Maximilien Naveau (maximilien.naveau@gmail.com) -# @copyright Copyright (c) 2019, New York University and Max Planck Gesellschaft. -# @license License BSD-3 clause -# @date 2019-05-06 -# -# @brief This file is the parameter file for the Doxygen ouput. All the variables -# surrounded by "@" are CMake variable. See doxygen.cmake for details. -# - -PROJECT_NAME = "@PROJECT_NAME@" -OUTPUT_DIRECTORY = . -JAVADOC_AUTOBRIEF = YES -TAB_SIZE = 4 -EXTRACT_ALL = NO -EXTRACT_PRIVATE = YES -EXTRACT_STATIC = YES - -# Add examples from this folder. -EXAMPLE_PATH = @PROJECT_SOURCE_DIR@/demos - -# Look for any files in this folder ...(see RECURSIVE) -INPUT = @PROJECT_SOURCE_DIR@ -# (see INPUT)... and recursively. -RECURSIVE = YES - -# Set the root path for the images. -IMAGE_PATH = @PROJECT_SOURCE_DIR@ - -# Filter the python docstring as Doxygen ones. -FILTER_PATTERNS = *.py=@MPI_CMAKE_MODULES_RESOURCES_DIR@/py_filter - -# Remove the absolute path to the package from the file path. -FULL_PATH_NAMES = YES -STRIP_FROM_PATH = @PROJECT_SOURCE_DIR@ - -# Get all the intersting files by looking for them recursively and according to -# their extension -FILE_PATTERNS = *.h *.hpp *.hh *.cpp *.c *.cc *.hxx *.dox *.md *.py - -# Side bar with the tree. -GENERATE_TREEVIEW = YES - -# remove the useless folders from the documentation. -EXCLUDE_PATTERNS = */*deprecated*/* -EXCLUDE_PATTERNS += */*build*/* -EXCLUDE_PATTERNS += */tests/* -EXCLUDE_PATTERNS += */internal/* - -# removing joss paper -EXCLUDE_PATTERNS += */paper.md - -GENERATE_TAGFILE = @PROJECT_NAME@.tag -GENERATE_HTML = YES -GENERATE_XML = YES - -# Include and resize and image. -ALIASES += imageSize{3}="\htmlonly \endhtmlonly \image html \1 \"\3\"" -# add a `@license` tag -ALIASES += "license=\xrefitem license \"License\" \"License\" " - -# Allow searching from teh client side. -SERVER_BASED_SEARCH = NO -SEARCHENGINE = YES - -# TODO manage this tag file with the cmake macro: -# e.g. "@CMAKE_INSTALL_PREFIX@/share/doc/@DEPENDENCY_PROJECT@.doxytag = @CMAKE_INSTALL_PREFIX@/share/doc/@DEPENDENCY_PROJECT_NAME" -# TAGFILES = @@PROJECT_NAME@_TAGFILES@ diff --git a/mpiis_doc_build/resources/__init__.py.empty.in b/mpiis_doc_build/resources/__init__.py.empty.in deleted file mode 100644 index ac01ec0..0000000 --- a/mpiis_doc_build/resources/__init__.py.empty.in +++ /dev/null @@ -1,4 +0,0 @@ -""" -License BSD-3-Clause -Copyright (c) 2019, New York University and Max Planck Gesellschaft. -""" \ No newline at end of file diff --git a/mpiis_doc_build/resources/py_filter.sh b/mpiis_doc_build/resources/py_filter.sh deleted file mode 100755 index da843b7..0000000 --- a/mpiis_doc_build/resources/py_filter.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -doxypypy -a -c $1 \ No newline at end of file diff --git a/mpiis_doc_build/resources/sphinx/sphinx/conf.py b/mpiis_doc_build/resources/sphinx/sphinx/conf.py deleted file mode 120000 index b3dd357..0000000 --- a/mpiis_doc_build/resources/sphinx/sphinx/conf.py +++ /dev/null @@ -1 +0,0 @@ -conf.py.in \ No newline at end of file From 428a748f50b51cf49b51474fb8f9ae7ac821eec5 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 16:00:02 +0200 Subject: [PATCH 06/16] Move resources files one level up --- mpiis_doc_build/build.py | 14 +++++++------- .../resources/{sphinx => }/doxygen/Doxyfile.in | 0 .../resources/sphinx/{sphinx => }/cmake_doc.rst.in | 0 .../resources/sphinx/{sphinx => }/conf.py.in | 0 .../sphinx/{sphinx => }/doxygen_index.rst.in | 0 .../{sphinx => }/doxygen_index_one_page.rst.in | 0 .../{sphinx => }/general_documentation.rst.in | 0 .../resources/sphinx/{sphinx => }/index.rst.in | 0 pyproject.toml | 2 +- 9 files changed, 8 insertions(+), 8 deletions(-) rename mpiis_doc_build/resources/{sphinx => }/doxygen/Doxyfile.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/cmake_doc.rst.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/conf.py.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/doxygen_index.rst.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/doxygen_index_one_page.rst.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/general_documentation.rst.in (100%) rename mpiis_doc_build/resources/sphinx/{sphinx => }/index.rst.in (100%) diff --git a/mpiis_doc_build/build.py b/mpiis_doc_build/build.py index feb42cd..ce3658e 100644 --- a/mpiis_doc_build/build.py +++ b/mpiis_doc_build/build.py @@ -130,7 +130,7 @@ def _build_doxygen_xml(doc_build_dir: Path, project_source_dir: Path): resource_path = _resource_path() # get the Doxyfile.in file - doxyfile_in = resource_path / "sphinx" / "doxygen" / "Doxyfile.in" + doxyfile_in = resource_path / "doxygen" / "Doxyfile.in" assert doxyfile_in.is_file() # Which files are going to be parsed. @@ -289,11 +289,11 @@ def _search_for_cpp_api( ) # Copy the index of the C++ API. shutil.copy( - resource_dir / "sphinx" / "sphinx" / "doxygen_index_one_page.rst.in", + resource_dir / "sphinx" / "doxygen_index_one_page.rst.in", doc_build_dir / "doxygen_index_one_page.rst", ) shutil.copy( - resource_dir / "sphinx" / "sphinx" / "doxygen_index.rst.in", + resource_dir / "sphinx" / "doxygen_index.rst.in", doc_build_dir / "doxygen_index.rst", ) @@ -385,7 +385,7 @@ def _search_for_cmake_api( doc_cmake_module += cmake_file.stem + "\n" doc_cmake_module += len(cmake_file.stem) * "-" + "\n\n" doc_cmake_module += ".. cmake-module:: cmake/" + cmake_file.name + "\n\n" - with open(resource_dir / "sphinx" / "sphinx" / "cmake_doc.rst.in", "rt") as f: + with open(resource_dir / "sphinx" / "cmake_doc.rst.in", "rt") as f: out_text = f.read().replace("@DOC_CMAKE_MODULE@", doc_cmake_module) with open(str(doc_build_dir / "cmake_doc.rst"), "wt") as f: f.write(out_text) @@ -426,7 +426,7 @@ def _search_for_general_documentation( """ ) shutil.copy( - resource_dir / "sphinx" / "sphinx" / "general_documentation.rst.in", + resource_dir / "sphinx" / "general_documentation.rst.in", doc_build_dir / "general_documentation.rst", ) shutil.copytree( @@ -487,7 +487,7 @@ def build_documentation( # configure the index.rst.in. header = "Welcome to " + project_name + "'s documentation!" header += "\n" + len(header) * "=" + "\n" - with open(resource_dir / "sphinx" / "sphinx" / "index.rst.in", "rt") as f: + with open(resource_dir / "sphinx" / "index.rst.in", "rt") as f: out_text = ( f.read() .replace("@HEADER@", header) @@ -500,7 +500,7 @@ def build_documentation( f.write(out_text) # configure the config.py.in. - with open(resource_dir / "sphinx" / "sphinx" / "conf.py.in", "rt") as f: + with open(resource_dir / "sphinx" / "conf.py.in", "rt") as f: out_text = ( f.read() .replace("@PROJECT_SOURCE_DIR@", os.fspath(project_source_dir)) diff --git a/mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in b/mpiis_doc_build/resources/doxygen/Doxyfile.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/doxygen/Doxyfile.in rename to mpiis_doc_build/resources/doxygen/Doxyfile.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in b/mpiis_doc_build/resources/sphinx/cmake_doc.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/cmake_doc.rst.in rename to mpiis_doc_build/resources/sphinx/cmake_doc.rst.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/conf.py.in b/mpiis_doc_build/resources/sphinx/conf.py.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/conf.py.in rename to mpiis_doc_build/resources/sphinx/conf.py.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in b/mpiis_doc_build/resources/sphinx/doxygen_index.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/doxygen_index.rst.in rename to mpiis_doc_build/resources/sphinx/doxygen_index.rst.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in b/mpiis_doc_build/resources/sphinx/doxygen_index_one_page.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/doxygen_index_one_page.rst.in rename to mpiis_doc_build/resources/sphinx/doxygen_index_one_page.rst.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in b/mpiis_doc_build/resources/sphinx/general_documentation.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/general_documentation.rst.in rename to mpiis_doc_build/resources/sphinx/general_documentation.rst.in diff --git a/mpiis_doc_build/resources/sphinx/sphinx/index.rst.in b/mpiis_doc_build/resources/sphinx/index.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/sphinx/index.rst.in rename to mpiis_doc_build/resources/sphinx/index.rst.in diff --git a/pyproject.toml b/pyproject.toml index 29a93c7..825a90a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,4 +30,4 @@ dependencies = [ [tool.setuptools.package-data] -mpiis_doc_build = ["resources/*.sh", "resources/**/*.in"] +mpiis_doc_build = ["resources/**/*.in"] From e0acdae48f5b07e153c24ea9f6afc5228ab96c94 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 17:16:10 +0200 Subject: [PATCH 07/16] Remove unused dependency --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 825a90a..5b3bd84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,7 +19,6 @@ license = {file = "LICENSE"} dependencies = [ "breathe", - "doxypypy", "m2r", "mistune<2.0.0", "recommonmark", From aade1b37dfa25aeef3cc8264a35550f2746e149a Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Thu, 8 Sep 2022 17:16:45 +0200 Subject: [PATCH 08/16] Try to auto-detect version if --project-version is not set Make `--project-version` optional and, if not set, try to auto-detect the version of the package. Currently it tries to parse the version from a package.xml or (if that fails) from a CMakeLists.txt. More functions can be added easily to support more package types. --- mpiis_doc_build/__main__.py | 31 +++++++++++--- mpiis_doc_build/find_version.py | 74 +++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 6 deletions(-) create mode 100644 mpiis_doc_build/find_version.py diff --git a/mpiis_doc_build/__main__.py b/mpiis_doc_build/__main__.py index 7e3b501..7a93e9e 100644 --- a/mpiis_doc_build/__main__.py +++ b/mpiis_doc_build/__main__.py @@ -1,7 +1,9 @@ import argparse +import logging import pathlib +import sys -from . import __version__ +from . import __version__, find_version from .build import build_documentation @@ -35,17 +37,22 @@ def AbsolutePath(path): auto-detected inside the package directory """, ) - parser.add_argument( - "--project-version", required=True, type=str, help="Package version" - ) + parser.add_argument("--project-version", type=str, help="Package version") parser.add_argument( "--force", "-f", action="store_true", help="Do not ask before deleting files.", ) + parser.add_argument("--verbose", action="store_true", help="Enable debug output.") args = parser.parse_args() + if args.verbose: + logger_level = logging.DEBUG + else: + logger_level = logging.INFO + logging.basicConfig(level=logger_level) + if not args.force and args.output_dir.exists(): print( "Output directory {} already exists." @@ -55,7 +62,17 @@ def AbsolutePath(path): if c not in ["y", "Y", "yes"]: print("Abort.") - return + return 1 + + if not args.project_version: + try: + args.project_version = find_version.find_version(args.package_dir) + except find_version.VersionNotFound: + print( + "ERROR: Package version could not be determined." + " Please specify it using --package-version." + ) + return 1 build_documentation( args.output_dir, @@ -64,6 +81,8 @@ def AbsolutePath(path): python_pkg_path=args.python_dir, ) + return 0 + if __name__ == "__main__": - main() + sys.exit(main()) diff --git a/mpiis_doc_build/find_version.py b/mpiis_doc_build/find_version.py new file mode 100644 index 0000000..d55a1ce --- /dev/null +++ b/mpiis_doc_build/find_version.py @@ -0,0 +1,74 @@ +"""Functions for auto-detecting the version of the package.""" +import logging +import pathlib +import re +import typing +import xml.etree.ElementTree + + +_logger = logging.getLogger("mpiis_doc_build.find_version") + + +class VersionNotFound(Exception): + pass + + +def _check_package_xml(package_dir: pathlib.Path) -> typing.Optional[str]: + """Try to get version from package.xml file.""" + file = package_dir / "package.xml" + _logger.debug("Check %s", file) + if file.exists(): + _logger.debug("%s exists", file) + + tree = xml.etree.ElementTree.parse(file) + root = tree.getroot() + version = root.find("version") + if version is not None: + _logger.info("Found package version %s", version.text) + return version.text + + return None + + +def _check_cmakelists(package_dir: pathlib.Path) -> typing.Optional[str]: + """Try to get project version from CMakeLists.txt.""" + file = package_dir / "CMakeLists.txt" + _logger.debug("Check %s", file) + if file.exists(): + _logger.debug("%s exists", file) + + pattern = re.compile(r"\bproject\(.* VERSION (\S+)\b.*\)", re.IGNORECASE) + with open(file) as f: + for line in f: + m = re.search(pattern, line) + if m: + version = m.group(1) + _logger.info("Found package version %s", version) + return version + + return None + + +def find_version(package_dir: pathlib.Path) -> str: + """Try to automatically detect version of the package. + + Args: + package_dir: Root directory of the package. + + Returns: + The package version as string. + + Raises: + VersionNotFound: If no version can be determined for the package. + """ + # Can be extended by adding more functions to the list. The functions need to + # follow the interface `func(package_dir: Path) -> Optional[str]`, returning either + # a Version string or None, if no version could be found. + # The first function that returns a version wins, so the order matters! + for func in [_check_package_xml, _check_cmakelists]: + version = func(package_dir) + if version: + print(f"Found package version: {version}") + return version + + raise VersionNotFound() From dd648e31b1d0526f44530068e5babadbac21c6c1 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 10:45:14 +0200 Subject: [PATCH 09/16] Install entry point `mpiis-doc-build` This will execute the same as `python3 -m mpiis_doc_build` but looks a bit nicer. --- pyproject.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 5b3bd84..bd74693 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,5 +28,8 @@ dependencies = [ ] +[project.scripts] +mpiis-doc-build = "mpiis_doc_build.__main__:main" + [tool.setuptools.package-data] mpiis_doc_build = ["resources/**/*.in"] From 672899322a6b825ec9e346e93d4bbfbd86ae7d99 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 10:51:50 +0200 Subject: [PATCH 10/16] Rename --project-version to --package-version ...and extend the help text. --- mpiis_doc_build/__main__.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/mpiis_doc_build/__main__.py b/mpiis_doc_build/__main__.py index 7a93e9e..5f53e3a 100644 --- a/mpiis_doc_build/__main__.py +++ b/mpiis_doc_build/__main__.py @@ -37,7 +37,14 @@ def AbsolutePath(path): auto-detected inside the package directory """, ) - parser.add_argument("--project-version", type=str, help="Package version") + parser.add_argument( + "--package-version", + type=str, + help="""Package version that is shown in the documentation (something like + '1.42.0'). If not set, mpiis-doc-build tries to auto-detect it by looking + for files like package.xml in the package directory. + """, + ) parser.add_argument( "--force", "-f", @@ -64,9 +71,9 @@ def AbsolutePath(path): print("Abort.") return 1 - if not args.project_version: + if not args.package_version: try: - args.project_version = find_version.find_version(args.package_dir) + args.package_version = find_version.find_version(args.package_dir) except find_version.VersionNotFound: print( "ERROR: Package version could not be determined." @@ -77,7 +84,7 @@ def AbsolutePath(path): build_documentation( args.output_dir, args.package_dir, - args.project_version, + args.package_version, python_pkg_path=args.python_dir, ) From add3e8c952ed59f247303f98d3d136f718b92e3d Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 10:53:29 +0200 Subject: [PATCH 11/16] Add short alternatives for main arguments --- mpiis_doc_build/__main__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mpiis_doc_build/__main__.py b/mpiis_doc_build/__main__.py index 5f53e3a..61895f0 100644 --- a/mpiis_doc_build/__main__.py +++ b/mpiis_doc_build/__main__.py @@ -20,12 +20,14 @@ def AbsolutePath(path): ) parser.add_argument( "--output-dir", + "-o", required=True, type=AbsolutePath, help="Build directory", ) parser.add_argument( "--package-dir", + "-p", required=True, type=AbsolutePath, help="Package directory", From bb4b0cae3a7094fce676d8f08f0c073713443ce3 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 13:08:52 +0200 Subject: [PATCH 12/16] Apply workaround for floating property fields The RTD theme has an incompatibility issue with autodoc as both use a "property" CSS class for different purposes. Add a custom CSS file with a workaround to fix this issue. See https://github.com/readthedocs/sphinx_rtd_theme/issues/1247 for more information. --- mpiis_doc_build/build.py | 8 ++++++++ mpiis_doc_build/resources/sphinx/conf.py.in | 7 ++++++- mpiis_doc_build/resources/sphinx/custom.css.in | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 mpiis_doc_build/resources/sphinx/custom.css.in diff --git a/mpiis_doc_build/build.py b/mpiis_doc_build/build.py index ce3658e..12933ee 100644 --- a/mpiis_doc_build/build.py +++ b/mpiis_doc_build/build.py @@ -511,6 +511,14 @@ def build_documentation( with open(doc_build_dir / "conf.py", "wt") as f: f.write(out_text) + # copy the custom.css to _static + static_dir = doc_build_dir / "_static" + static_dir.mkdir(exist_ok=True) + shutil.copy( + resource_dir / "sphinx" / "custom.css.in", + static_dir / "custom.css", + ) + # # Copy the license and readme file. # diff --git a/mpiis_doc_build/resources/sphinx/conf.py.in b/mpiis_doc_build/resources/sphinx/conf.py.in index 84675d9..d3ab74d 100644 --- a/mpiis_doc_build/resources/sphinx/conf.py.in +++ b/mpiis_doc_build/resources/sphinx/conf.py.in @@ -121,7 +121,12 @@ if not on_rtd: # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +html_static_path = ['_static'] + +# path is relative to html_static_path +html_css_files = [ + 'custom.css', +] # Custom sidebar templates, must be a dictionary that maps document names # to template names. diff --git a/mpiis_doc_build/resources/sphinx/custom.css.in b/mpiis_doc_build/resources/sphinx/custom.css.in new file mode 100644 index 0000000..337401c --- /dev/null +++ b/mpiis_doc_build/resources/sphinx/custom.css.in @@ -0,0 +1,4 @@ +/* Workaround for https://github.com/readthedocs/sphinx_rtd_theme/issues/1247 */ +dl.py.property { + display: block !important; +} From 06f0d5e972c94f1193ceedf9a713edf946cea8c6 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 13:50:05 +0200 Subject: [PATCH 13/16] Add README --- README.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/README.md b/README.md index e69de29..98b29d9 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,68 @@ +Documentation Builder for C++ and Python packages +================================================= + +mpiis_doc_build is a tool for building documentation that is used for some of the +software packages developed at the Max Planck Institute for Intelligent Systems (MPI-IS) +and the New York University. + +It is basically a wrapper around Doxygen, Sphinx and Breathe and runs those tools to +generate a Sphinx-based documentation, automatically including API documentation for +C++, Python and CMake code found in the package. + +It is tailored to work with the structure of our packages but we are doing nothing +extraordinary there, so it will likely work for others as well (see below for the +assumptions we make regarding the package structure). + + +Installation +------------ + +Simply clone this repository and install it with + +``` +cd path/to/mpiis_doc_build +pip install . +``` + +Note that for building C++ API documentation Doxygen is used, which needs to be +installed separately (e.g. with `sudo apt install doxygen` on Ubuntu). + + +Usage +----- + +In the most simple case you can run it like this: + +``` +mpiis-doc-build --package-dir path/to/package --output-dir path/to/output +``` + +If no package version is specified, `mpiis-doc-build` tries to find it by checking a +number of files in the package directory. If no version is found this way, it fails +with an error. In this case, you can explicitly specify the version using +`--package-version`. + +`mpiis-doc-build` tries to automatically detect if the package contains Python code and, +if yes, adds a Python API section to the documentation. However, if your package +contains Python modules that are only generated at build-time (e.g. Python bindings for +C++ code) you can use `--python-dir` to specify the directory where the Python modules +are installed to. This way, the generated modules will be included in the documentation +as well. + +For a complete list of options see `mpiis-doc-build --help`. + + + + +Assumptions Regarding Package Structure +--------------------------------------- + +TODO + + +Copyright & License +------------------- + +Copyright (c) 2022, New York University and Max Planck Gesellschaft. + +License: BSD 3-clause (see LICENSE). From b62dd6298945ca35f2ecf6f3ebed6c94984d745b Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Fri, 9 Sep 2022 13:59:37 +0200 Subject: [PATCH 14/16] Add CHANGELOG.md --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c90671d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] +### Added +- Try to auto-detect package version if not explicitly specified. +- Install executable `mpiis-doc-build`. +- Short arguments `-p`/`-o` for `--package-dir`/`--output-dir`. + +### Changed +- Renamed `--project-version` to `--package-version`. + +### Fixed +- Workaround for an incompatibility issue between the RTD theme and autodoc, which + caused class properties to be floating (see + https://github.com/readthedocs/sphinx_rtd_theme/issues/1247) + +## [0.1.0] - 2022-09-08 +Extracted the documentation build code from +[mpi_cmake_modules](https://github.com/machines-in-motion/mpi_cmake_modules) with only +some minor changes. From 9a14a012d580436d013b83553e3800a4bdabaf37 Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Tue, 27 Sep 2022 17:30:28 +0200 Subject: [PATCH 15/16] Rename project to "breathing_cat" --- README.md | 4 ++-- {mpiis_doc_build => breathing_cat}/__init__.py | 0 {mpiis_doc_build => breathing_cat}/__main__.py | 4 ++-- {mpiis_doc_build => breathing_cat}/build.py | 0 {mpiis_doc_build => breathing_cat}/find_version.py | 2 +- .../resources/doxygen/Doxyfile.in | 0 .../resources/sphinx/cmake_doc.rst.in | 0 .../resources/sphinx/conf.py.in | 0 .../resources/sphinx/custom.css.in | 0 .../resources/sphinx/doxygen_index.rst.in | 0 .../resources/sphinx/doxygen_index_one_page.rst.in | 0 .../resources/sphinx/general_documentation.rst.in | 0 .../resources/sphinx/index.rst.in | 0 pyproject.toml | 6 +++--- 14 files changed, 8 insertions(+), 8 deletions(-) rename {mpiis_doc_build => breathing_cat}/__init__.py (100%) rename {mpiis_doc_build => breathing_cat}/__main__.py (95%) rename {mpiis_doc_build => breathing_cat}/build.py (100%) rename {mpiis_doc_build => breathing_cat}/find_version.py (97%) rename {mpiis_doc_build => breathing_cat}/resources/doxygen/Doxyfile.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/cmake_doc.rst.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/conf.py.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/custom.css.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/doxygen_index.rst.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/doxygen_index_one_page.rst.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/general_documentation.rst.in (100%) rename {mpiis_doc_build => breathing_cat}/resources/sphinx/index.rst.in (100%) diff --git a/README.md b/README.md index 98b29d9..806267c 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ Documentation Builder for C++ and Python packages ================================================= -mpiis_doc_build is a tool for building documentation that is used for some of the +breathing_cat is a tool for building documentation that is used for some of the software packages developed at the Max Planck Institute for Intelligent Systems (MPI-IS) and the New York University. @@ -20,7 +20,7 @@ Installation Simply clone this repository and install it with ``` -cd path/to/mpiis_doc_build +cd path/to/breathing-cat pip install . ``` diff --git a/mpiis_doc_build/__init__.py b/breathing_cat/__init__.py similarity index 100% rename from mpiis_doc_build/__init__.py rename to breathing_cat/__init__.py diff --git a/mpiis_doc_build/__main__.py b/breathing_cat/__main__.py similarity index 95% rename from mpiis_doc_build/__main__.py rename to breathing_cat/__main__.py index 61895f0..6110c4e 100644 --- a/mpiis_doc_build/__main__.py +++ b/breathing_cat/__main__.py @@ -15,8 +15,8 @@ def AbsolutePath(path): parser.add_argument( "--version", action="version", - help="Show version of mpiis_doc_build.", - version=f"mpiis_doc_build version {__version__}", + help="Show version of breathing_cat.", + version=f"breathing_cat version {__version__}", ) parser.add_argument( "--output-dir", diff --git a/mpiis_doc_build/build.py b/breathing_cat/build.py similarity index 100% rename from mpiis_doc_build/build.py rename to breathing_cat/build.py diff --git a/mpiis_doc_build/find_version.py b/breathing_cat/find_version.py similarity index 97% rename from mpiis_doc_build/find_version.py rename to breathing_cat/find_version.py index d55a1ce..61bf493 100644 --- a/mpiis_doc_build/find_version.py +++ b/breathing_cat/find_version.py @@ -6,7 +6,7 @@ import xml.etree.ElementTree -_logger = logging.getLogger("mpiis_doc_build.find_version") +_logger = logging.getLogger("breathing_cat.find_version") class VersionNotFound(Exception): diff --git a/mpiis_doc_build/resources/doxygen/Doxyfile.in b/breathing_cat/resources/doxygen/Doxyfile.in similarity index 100% rename from mpiis_doc_build/resources/doxygen/Doxyfile.in rename to breathing_cat/resources/doxygen/Doxyfile.in diff --git a/mpiis_doc_build/resources/sphinx/cmake_doc.rst.in b/breathing_cat/resources/sphinx/cmake_doc.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/cmake_doc.rst.in rename to breathing_cat/resources/sphinx/cmake_doc.rst.in diff --git a/mpiis_doc_build/resources/sphinx/conf.py.in b/breathing_cat/resources/sphinx/conf.py.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/conf.py.in rename to breathing_cat/resources/sphinx/conf.py.in diff --git a/mpiis_doc_build/resources/sphinx/custom.css.in b/breathing_cat/resources/sphinx/custom.css.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/custom.css.in rename to breathing_cat/resources/sphinx/custom.css.in diff --git a/mpiis_doc_build/resources/sphinx/doxygen_index.rst.in b/breathing_cat/resources/sphinx/doxygen_index.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/doxygen_index.rst.in rename to breathing_cat/resources/sphinx/doxygen_index.rst.in diff --git a/mpiis_doc_build/resources/sphinx/doxygen_index_one_page.rst.in b/breathing_cat/resources/sphinx/doxygen_index_one_page.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/doxygen_index_one_page.rst.in rename to breathing_cat/resources/sphinx/doxygen_index_one_page.rst.in diff --git a/mpiis_doc_build/resources/sphinx/general_documentation.rst.in b/breathing_cat/resources/sphinx/general_documentation.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/general_documentation.rst.in rename to breathing_cat/resources/sphinx/general_documentation.rst.in diff --git a/mpiis_doc_build/resources/sphinx/index.rst.in b/breathing_cat/resources/sphinx/index.rst.in similarity index 100% rename from mpiis_doc_build/resources/sphinx/index.rst.in rename to breathing_cat/resources/sphinx/index.rst.in diff --git a/pyproject.toml b/pyproject.toml index bd74693..3bf135e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] -name = "mpiis_doc_build" +name = "breathing_cat" version = "0.1.0" description = "Tool to build documentation for C++/Python-Packages used at MPI-IS" authors = [ @@ -29,7 +29,7 @@ dependencies = [ [project.scripts] -mpiis-doc-build = "mpiis_doc_build.__main__:main" +mpiis-doc-build = "breathing_cat.__main__:main" [tool.setuptools.package-data] -mpiis_doc_build = ["resources/**/*.in"] +breathing_cat = ["resources/**/*.in"] From deb8d5850d6eb3ab079635e2dba313e0859228ec Mon Sep 17 00:00:00 2001 From: Felix Widmaier Date: Tue, 27 Sep 2022 17:41:21 +0200 Subject: [PATCH 16/16] Rename executable to `bcat` --- CHANGELOG.md | 2 +- README.md | 9 +++++---- breathing_cat/__main__.py | 6 +++--- pyproject.toml | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c90671d..37d3639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - Try to auto-detect package version if not explicitly specified. -- Install executable `mpiis-doc-build`. +- Install executable `bcat`. - Short arguments `-p`/`-o` for `--package-dir`/`--output-dir`. ### Changed diff --git a/README.md b/README.md index 806267c..3948b24 100644 --- a/README.md +++ b/README.md @@ -34,23 +34,24 @@ Usage In the most simple case you can run it like this: ``` -mpiis-doc-build --package-dir path/to/package --output-dir path/to/output +bcat --package-dir path/to/package --output-dir path/to/output ``` -If no package version is specified, `mpiis-doc-build` tries to find it by checking a +If no package version is specified, `bcat` tries to find it by checking a number of files in the package directory. If no version is found this way, it fails with an error. In this case, you can explicitly specify the version using `--package-version`. -`mpiis-doc-build` tries to automatically detect if the package contains Python code and, +`bcat` tries to automatically detect if the package contains Python code and, if yes, adds a Python API section to the documentation. However, if your package contains Python modules that are only generated at build-time (e.g. Python bindings for C++ code) you can use `--python-dir` to specify the directory where the Python modules are installed to. This way, the generated modules will be included in the documentation as well. -For a complete list of options see `mpiis-doc-build --help`. +For a complete list of options see `bcat --help`. +Instead of the `bcat` executable, you can also use `python -m breathing_cat`. diff --git a/breathing_cat/__main__.py b/breathing_cat/__main__.py index 6110c4e..a1118fc 100644 --- a/breathing_cat/__main__.py +++ b/breathing_cat/__main__.py @@ -15,8 +15,8 @@ def AbsolutePath(path): parser.add_argument( "--version", action="version", - help="Show version of breathing_cat.", - version=f"breathing_cat version {__version__}", + help="Show version of breathing-cat.", + version=f"breathing-cat version {__version__}", ) parser.add_argument( "--output-dir", @@ -43,7 +43,7 @@ def AbsolutePath(path): "--package-version", type=str, help="""Package version that is shown in the documentation (something like - '1.42.0'). If not set, mpiis-doc-build tries to auto-detect it by looking + '1.42.0'). If not set, breathing-cat tries to auto-detect it by looking for files like package.xml in the package directory. """, ) diff --git a/pyproject.toml b/pyproject.toml index 3bf135e..669b985 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ dependencies = [ [project.scripts] -mpiis-doc-build = "breathing_cat.__main__:main" +bcat = "breathing_cat.__main__:main" [tool.setuptools.package-data] breathing_cat = ["resources/**/*.in"]