From f7032fd88dd7f900c4ee0d7ebeac92baccc7e50e Mon Sep 17 00:00:00 2001 From: "R. Kent James" Date: Sat, 30 Mar 2024 15:30:07 -0700 Subject: [PATCH 1/4] Clarify directory naming --- .../verbs/build/builders/sphinx_builder.py | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/rosdoc2/verbs/build/builders/sphinx_builder.py b/rosdoc2/verbs/build/builders/sphinx_builder.py index d0990d2..30e91a9 100644 --- a/rosdoc2/verbs/build/builders/sphinx_builder.py +++ b/rosdoc2/verbs/build/builders/sphinx_builder.py @@ -161,7 +161,7 @@ def ensure_global(name, default): from exhale import utils exhale_args.update({{ # These arguments are required. - "containmentFolder": "{user_sourcedir}/generated", + "containmentFolder": "{sphinx_project_directory}/generated", "rootFileName": "index.rst", "rootFileTitle": "C++ API", "doxygenStripFromPath": "..", @@ -220,7 +220,7 @@ def ensure_global(name, default): # import os import sys -sys.path.insert(0, os.path.abspath(os.path.join('{package_src_directory}', '..'))) +sys.path.insert(0, os.path.abspath(os.path.join('{python_src_directory}', '..'))) # -- Project information ----------------------------------------------------- @@ -421,9 +421,9 @@ def build(self, *, doc_build_folder, output_staging_directory): f"'{self.doxygen_xml_directory}' does not exist.") package_xml_directory = os.path.dirname(self.build_context.package.filename) - # If 'python_source' is specified, construct 'package_src_directory' from it + # If 'python_source' is specified, construct 'python_src_directory' from it if self.build_context.python_source is not None: - package_src_directory = \ + python_src_directory = \ os.path.abspath( os.path.join( package_xml_directory, @@ -432,20 +432,21 @@ def build(self, *, doc_build_folder, output_staging_directory): else: package_list = setuptools.find_packages(where=package_xml_directory) if self.build_context.package.name in package_list: - package_src_directory = \ + python_src_directory = \ os.path.abspath( os.path.join( package_xml_directory, self.build_context.package.name)) else: - package_src_directory = None + python_src_directory = None - # Check if the user provided a sourcedir. - sourcedir = self.sphinx_sourcedir - if sourcedir is not None: + # Check if the user provided a sphinx directory. + sphinx_project_directory = self.sphinx_sourcedir + if sphinx_project_directory is not None: # We do not need to check if this directory exists, as that was done in __init__. logger.info( - f"Note: the user provided sourcedir for Sphinx '{sourcedir}' will be used.") + 'Note: the user provided sourcedir for Sphinx ' + f"'{sphinx_project_directory}' will be used.") else: # If the user does not supply a Sphinx sourcedir, check the standard locations. standard_sphinx_sourcedir = self.locate_sphinx_sourcedir_from_standard_locations() @@ -453,7 +454,7 @@ def build(self, *, doc_build_folder, output_staging_directory): logger.info( 'Note: no sourcedir provided, but a Sphinx sourcedir located in the ' f"standard location '{standard_sphinx_sourcedir}' and that will be used.") - sourcedir = standard_sphinx_sourcedir + sphinx_project_directory = standard_sphinx_sourcedir else: # If the user does not supply a Sphinx sourcedir, and there is no Sphinx project # in the conventional location, i.e. '/doc', create a temporary @@ -461,18 +462,18 @@ def build(self, *, doc_build_folder, output_staging_directory): logger.info( 'Note: no sourcedir provided by the user and no Sphinx sourcedir was found ' 'in the standard locations, therefore using a default Sphinx configuration.') - sourcedir = os.path.join(doc_build_folder, 'default_sphinx_project') + sphinx_project_directory = os.path.join(doc_build_folder, 'default_sphinx_project') # Generate rst documents for interfaces interface_counts = generate_interface_docs( package_xml_directory, self.build_context.package.name, - os.path.join(sourcedir, 'generated') + os.path.join(sphinx_project_directory, 'generated') ) logger.info(f'interface_counts: {interface_counts}') self.generate_default_project_into_directory( - sourcedir, package_src_directory, interface_counts) + sphinx_project_directory, python_src_directory, interface_counts) # Collect intersphinx mapping extensions from discovered inventory files. inventory_files = \ @@ -487,11 +488,12 @@ def build(self, *, doc_build_folder, output_staging_directory): if package_name != self.build_context.package.name ] - # Setup rosdoc2 Sphinx file which will include and extend the one in `sourcedir`. + # Setup rosdoc2 Sphinx file which will include and extend the one in + # `sphinx_project_directory`. self.generate_wrapping_rosdoc2_sphinx_project_into_directory( doc_build_folder, - sourcedir, - package_src_directory, + sphinx_project_directory, + python_src_directory, intersphinx_mapping_extensions) # If the package has python code, then invoke `sphinx-apidoc` before building @@ -499,16 +501,16 @@ def build(self, *, doc_build_folder, output_staging_directory): self.build_context.always_run_sphinx_apidoc or \ self.build_context.ament_cmake_python if has_python: - if not package_src_directory or not os.path.isdir(package_src_directory): + if not python_src_directory or not os.path.isdir(python_src_directory): raise RuntimeError( 'Could not locate source directory to invoke sphinx-apidoc in. ' 'If this is package does not have a standard Python package layout, ' "please specify the Python source in 'rosdoc2.yaml'.") cmd = [ 'sphinx-apidoc', - '-o', os.path.relpath(sourcedir, start=doc_build_folder), + '-o', os.path.relpath(sphinx_project_directory, start=doc_build_folder), '-e', # Document each module in its own page. - package_src_directory, + python_src_directory, ] logger.info( f"Running sphinx-apidoc: '{' '.join(cmd)}' in '{doc_build_folder}'" @@ -526,7 +528,7 @@ def build(self, *, doc_build_folder, output_staging_directory): cmd = [ 'sphinx-build', '-c', os.path.relpath(doc_build_folder, start=working_directory), - os.path.relpath(sourcedir, start=working_directory), + os.path.relpath(sphinx_project_directory, start=working_directory), sphinx_output_dir, ] logger.info( @@ -590,14 +592,14 @@ def locate_sphinx_sourcedir_from_standard_locations(self): return None def generate_default_project_into_directory( - self, directory, package_src_directory, interface_counts): + self, directory, python_src_directory, interface_counts): """Generate the default project configuration files.""" os.makedirs(directory, exist_ok=True) package = self.build_context.package template_variables = { 'package': package, - 'package_src_directory': esc_backslash(package_src_directory), + 'python_src_directory': esc_backslash(python_src_directory), 'package_version_short': '.'.join(package.version.split('.')[0:2]), 'package_licenses': ', '.join(package.licenses), 'package_authors': ', '.join(set( @@ -622,36 +624,36 @@ def generate_default_project_into_directory( def generate_wrapping_rosdoc2_sphinx_project_into_directory( self, - directory, - user_sourcedir, - package_src_directory, + doc_build_folder, + sphinx_project_directory, + python_src_directory, intersphinx_mapping_extensions, ): """Generate the rosdoc2 sphinx project configuration files.""" # Copy all user content, like images or documentation files, and # source files to the wrapping directory - if user_sourcedir: + if sphinx_project_directory: try: shutil.copytree( - os.path.abspath(user_sourcedir), - os.path.abspath(directory), + os.path.abspath(sphinx_project_directory), + os.path.abspath(doc_build_folder), dirs_exist_ok=True) if self.build_context.build_type == 'ament_python': # shutil.copy tree will recursively copy an entire # directory rooted at the provided src directory. - # If we supply package_src_directory as src, - # it will copy the contents within package_src_directory. - # However, we want to copy the package_src_directory itself + # If we supply python_src_directory as src, + # it will copy the contents within python_src_directory. + # However, we want to copy the python_src_directory itself # such that the python modules will reside within this folder # at the destination directory. shutil.copytree( - os.path.abspath(os.path.join(package_src_directory, '..')), - os.path.abspath(directory), + os.path.abspath(os.path.join(python_src_directory, '..')), + os.path.abspath(doc_build_folder), dirs_exist_ok=True) except OSError as e: print(f'Failed to copy user content: {e}') - os.makedirs(directory, exist_ok=True) + os.makedirs(doc_build_folder, exist_ok=True) package = self.build_context.package breathe_projects = [] @@ -661,15 +663,15 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory( f'"{esc_backslash(self.doxygen_xml_directory)}"') template_variables = { 'package_name': package.name, - 'package_src_directory': package_src_directory, + 'python_src_directory': python_src_directory, 'exec_depends': [exec_depend.name for exec_depend in package.exec_depends] + [doc_depend.name for doc_depend in package.doc_depends], 'build_type': self.build_context.build_type, 'always_run_doxygen': self.build_context.always_run_doxygen, 'did_run_doxygen': self.doxygen_xml_directory is not None, - 'user_sourcedir': esc_backslash(os.path.abspath(user_sourcedir)), + 'sphinx_project_directory': esc_backslash(os.path.abspath(sphinx_project_directory)), 'user_conf_py_filename': esc_backslash( - os.path.abspath(os.path.join(user_sourcedir, 'conf.py'))), + os.path.abspath(os.path.join(sphinx_project_directory, 'conf.py'))), 'breathe_projects': ',\n'.join(breathe_projects) + '\n ', 'intersphinx_mapping_extensions': ',\n '.join(intersphinx_mapping_extensions), 'package': package, @@ -679,6 +681,6 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory( 'package_version_short': '.'.join(package.version.split('.')[0:2]), } - print(os.path.abspath(os.path.join(directory, 'conf.py'))) - with open(os.path.join(directory, 'conf.py'), 'w+') as f: + print(os.path.abspath(os.path.join(doc_build_folder, 'conf.py'))) + with open(os.path.join(doc_build_folder, 'conf.py'), 'w+') as f: f.write(rosdoc2_wrapping_conf_py_template.format_map(template_variables)) From ca20f833ff35a3befab311057bbcdd106ee149b7 Mon Sep 17 00:00:00 2001 From: "R. Kent James" Date: Sat, 30 Mar 2024 19:29:00 -0700 Subject: [PATCH 2/4] Refactor to write directly into wrapped directory --- .../verbs/build/builders/sphinx_builder.py | 120 +++++++++--------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/rosdoc2/verbs/build/builders/sphinx_builder.py b/rosdoc2/verbs/build/builders/sphinx_builder.py index 30e91a9..e1b270d 100644 --- a/rosdoc2/verbs/build/builders/sphinx_builder.py +++ b/rosdoc2/verbs/build/builders/sphinx_builder.py @@ -161,7 +161,7 @@ def ensure_global(name, default): from exhale import utils exhale_args.update({{ # These arguments are required. - "containmentFolder": "{sphinx_project_directory}/generated", + "containmentFolder": "{wrapped_sphinx_directory}/generated", "rootFileName": "index.rst", "rootFileTitle": "C++ API", "doxygenStripFromPath": "..", @@ -428,7 +428,7 @@ def build(self, *, doc_build_folder, output_staging_directory): os.path.join( package_xml_directory, self.build_context.python_source)) - # If not provided, try to find the package source directory + # If not provided, try to find the python source directory else: package_list = setuptools.find_packages(where=package_xml_directory) if self.build_context.package.name in package_list: @@ -440,6 +440,19 @@ def build(self, *, doc_build_folder, output_staging_directory): else: python_src_directory = None + # We will ultimately run the sphinx project from a wrapped directory. Create it now, + # so that we can put generated items there. + wrapped_sphinx_directory = os.path.join(doc_build_folder, 'wrapped_sphinx_directory') + os.makedirs(wrapped_sphinx_directory, exist_ok=True) + + # Generate rst documents for interfaces + interface_counts = generate_interface_docs( + package_xml_directory, + self.build_context.package.name, + os.path.join(wrapped_sphinx_directory, 'generated') + ) + logger.info(f'interface_counts: {interface_counts}') + # Check if the user provided a sphinx directory. sphinx_project_directory = self.sphinx_sourcedir if sphinx_project_directory is not None: @@ -464,16 +477,8 @@ def build(self, *, doc_build_folder, output_staging_directory): 'in the standard locations, therefore using a default Sphinx configuration.') sphinx_project_directory = os.path.join(doc_build_folder, 'default_sphinx_project') - # Generate rst documents for interfaces - interface_counts = generate_interface_docs( - package_xml_directory, - self.build_context.package.name, - os.path.join(sphinx_project_directory, 'generated') - ) - logger.info(f'interface_counts: {interface_counts}') - self.generate_default_project_into_directory( - sphinx_project_directory, python_src_directory, interface_counts) + sphinx_project_directory, python_src_directory) # Collect intersphinx mapping extensions from discovered inventory files. inventory_files = \ @@ -491,10 +496,11 @@ def build(self, *, doc_build_folder, output_staging_directory): # Setup rosdoc2 Sphinx file which will include and extend the one in # `sphinx_project_directory`. self.generate_wrapping_rosdoc2_sphinx_project_into_directory( - doc_build_folder, + wrapped_sphinx_directory, sphinx_project_directory, python_src_directory, - intersphinx_mapping_extensions) + intersphinx_mapping_extensions, + interface_counts) # If the package has python code, then invoke `sphinx-apidoc` before building has_python = self.build_context.build_type == 'ament_python' or \ @@ -508,14 +514,14 @@ def build(self, *, doc_build_folder, output_staging_directory): "please specify the Python source in 'rosdoc2.yaml'.") cmd = [ 'sphinx-apidoc', - '-o', os.path.relpath(sphinx_project_directory, start=doc_build_folder), + '-o', wrapped_sphinx_directory, '-e', # Document each module in its own page. python_src_directory, ] logger.info( - f"Running sphinx-apidoc: '{' '.join(cmd)}' in '{doc_build_folder}'" + f"Running sphinx-apidoc: '{' '.join(cmd)}' in '{wrapped_sphinx_directory}'" ) - completed_process = subprocess.run(cmd, cwd=doc_build_folder) + completed_process = subprocess.run(cmd, cwd=wrapped_sphinx_directory) msg = f"sphinx-apidoc exited with return code '{completed_process.returncode}'" if completed_process.returncode == 0: logger.debug(msg) @@ -523,18 +529,17 @@ def build(self, *, doc_build_folder, output_staging_directory): raise RuntimeError(msg) # Invoke Sphinx-build. - working_directory = doc_build_folder - sphinx_output_dir = os.path.abspath(os.path.join(doc_build_folder, 'sphinx_output')) + sphinx_output_dir = os.path.abspath( + os.path.join(wrapped_sphinx_directory, 'sphinx_output')) cmd = [ 'sphinx-build', - '-c', os.path.relpath(doc_build_folder, start=working_directory), - os.path.relpath(sphinx_project_directory, start=working_directory), + wrapped_sphinx_directory, sphinx_output_dir, ] logger.info( - f"Running Sphinx-build: '{' '.join(cmd)}' in '{working_directory}'" + f"Running Sphinx-build: '{' '.join(cmd)}' in '{wrapped_sphinx_directory}'" ) - completed_process = subprocess.run(cmd, cwd=working_directory) + completed_process = subprocess.run(cmd, cwd=wrapped_sphinx_directory) msg = f"Sphinx-build exited with return code '{completed_process.returncode}'" if completed_process.returncode == 0: logger.info(msg) @@ -592,12 +597,12 @@ def locate_sphinx_sourcedir_from_standard_locations(self): return None def generate_default_project_into_directory( - self, directory, python_src_directory, interface_counts): + self, sphinx_project_directory, python_src_directory): """Generate the default project configuration files.""" - os.makedirs(directory, exist_ok=True) + os.makedirs(sphinx_project_directory, exist_ok=True) package = self.build_context.package - template_variables = { + self.template_variables.update({ 'package': package, 'python_src_directory': esc_backslash(python_src_directory), 'package_version_short': '.'.join(package.version.split('.')[0:2]), @@ -605,13 +610,24 @@ def generate_default_project_into_directory( 'package_authors': ', '.join(set( [a.name for a in package.authors] + [m.name for m in package.maintainers] )), - } + }) - with open(os.path.join(directory, 'conf.py'), 'w+') as f: - f.write(default_conf_py_template.format_map(template_variables)) + with open(os.path.join(sphinx_project_directory, 'conf.py'), 'w+') as f: + f.write(default_conf_py_template.format_map(self.template_variables)) + def generate_wrapping_rosdoc2_sphinx_project_into_directory( + self, + wrapped_sphinx_directory, + sphinx_project_directory, + python_src_directory, + intersphinx_mapping_extensions, + interface_counts, + ): + """Generate the rosdoc2 sphinx project configuration files.""" + # Generate a default index.rst + package = self.build_context.package root_title = f'Welcome to the documentation for {package.name}' - template_variables.update({ + self.template_variables.update({ 'root_title': root_title, 'root_title_underline': '=' * len(root_title), 'package_toc_entry': generate_package_toc_entry( @@ -619,57 +635,39 @@ def generate_default_project_into_directory( interface_counts=interface_counts) }) - with open(os.path.join(directory, 'index.rst'), 'w+') as f: - f.write(index_rst_template.format_map(template_variables)) + with open(os.path.join(wrapped_sphinx_directory, 'index.rst'), 'w+') as f: + f.write(index_rst_template.format_map(self.template_variables)) - def generate_wrapping_rosdoc2_sphinx_project_into_directory( - self, - doc_build_folder, - sphinx_project_directory, - python_src_directory, - intersphinx_mapping_extensions, - ): - """Generate the rosdoc2 sphinx project configuration files.""" # Copy all user content, like images or documentation files, and # source files to the wrapping directory + # + # If the user created an index.rst, it will overwrite our default here. Later we will + # overwrite any user's conf.py with a wrapped version, that also includes any user's + # conf.py variables. if sphinx_project_directory: try: shutil.copytree( os.path.abspath(sphinx_project_directory), - os.path.abspath(doc_build_folder), + os.path.abspath(wrapped_sphinx_directory), dirs_exist_ok=True) - if self.build_context.build_type == 'ament_python': - # shutil.copy tree will recursively copy an entire - # directory rooted at the provided src directory. - # If we supply python_src_directory as src, - # it will copy the contents within python_src_directory. - # However, we want to copy the python_src_directory itself - # such that the python modules will reside within this folder - # at the destination directory. - shutil.copytree( - os.path.abspath(os.path.join(python_src_directory, '..')), - os.path.abspath(doc_build_folder), - dirs_exist_ok=True) + except OSError as e: print(f'Failed to copy user content: {e}') - os.makedirs(doc_build_folder, exist_ok=True) - package = self.build_context.package breathe_projects = [] if self.doxygen_xml_directory is not None: breathe_projects.append( f' "{package.name} Doxygen Project": ' f'"{esc_backslash(self.doxygen_xml_directory)}"') - template_variables = { - 'package_name': package.name, + self.template_variables.update({ 'python_src_directory': python_src_directory, 'exec_depends': [exec_depend.name for exec_depend in package.exec_depends] + [doc_depend.name for doc_depend in package.doc_depends], 'build_type': self.build_context.build_type, 'always_run_doxygen': self.build_context.always_run_doxygen, 'did_run_doxygen': self.doxygen_xml_directory is not None, - 'sphinx_project_directory': esc_backslash(os.path.abspath(sphinx_project_directory)), + 'wrapped_sphinx_directory': esc_backslash(os.path.abspath(wrapped_sphinx_directory)), 'user_conf_py_filename': esc_backslash( os.path.abspath(os.path.join(sphinx_project_directory, 'conf.py'))), 'breathe_projects': ',\n'.join(breathe_projects) + '\n ', @@ -679,8 +677,8 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory( [a.name for a in package.authors] + [m.name for m in package.maintainers] ))), 'package_version_short': '.'.join(package.version.split('.')[0:2]), - } + }) - print(os.path.abspath(os.path.join(doc_build_folder, 'conf.py'))) - with open(os.path.join(doc_build_folder, 'conf.py'), 'w+') as f: - f.write(rosdoc2_wrapping_conf_py_template.format_map(template_variables)) + print(os.path.abspath(os.path.join(wrapped_sphinx_directory, 'conf.py'))) + with open(os.path.join(wrapped_sphinx_directory, 'conf.py'), 'w+') as f: + f.write(rosdoc2_wrapping_conf_py_template.format_map(self.template_variables)) From 95e3d87b679c1d69ecfdce6c30c3bc8615f3c5bb Mon Sep 17 00:00:00 2001 From: "R. Kent James" Date: Sat, 30 Mar 2024 21:20:59 -0700 Subject: [PATCH 3/4] Add basic c++ test with custom conf.py --- .../verbs/build/builders/sphinx_builder.py | 1 - test/packages/basic_cpp/CMakeLists.txt | 35 +++++ test/packages/basic_cpp/doc/conf.py | 128 ++++++++++++++++++ test/packages/basic_cpp/doc/index.rst | 16 +++ test/packages/basic_cpp/doc/overview.markdown | 3 + .../basic_cpp/include/basic_cpp/test_node.hpp | 27 ++++ test/packages/basic_cpp/package.xml | 18 +++ test/packages/basic_cpp/rosdoc2.yaml | 68 ++++++++++ test/packages/basic_cpp/src/test_node.cpp | 10 ++ test/test_builder.py | 20 +++ 10 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 test/packages/basic_cpp/CMakeLists.txt create mode 100644 test/packages/basic_cpp/doc/conf.py create mode 100644 test/packages/basic_cpp/doc/index.rst create mode 100644 test/packages/basic_cpp/doc/overview.markdown create mode 100644 test/packages/basic_cpp/include/basic_cpp/test_node.hpp create mode 100644 test/packages/basic_cpp/package.xml create mode 100644 test/packages/basic_cpp/rosdoc2.yaml create mode 100644 test/packages/basic_cpp/src/test_node.cpp diff --git a/rosdoc2/verbs/build/builders/sphinx_builder.py b/rosdoc2/verbs/build/builders/sphinx_builder.py index e1b270d..e9f9535 100644 --- a/rosdoc2/verbs/build/builders/sphinx_builder.py +++ b/rosdoc2/verbs/build/builders/sphinx_builder.py @@ -679,6 +679,5 @@ def generate_wrapping_rosdoc2_sphinx_project_into_directory( 'package_version_short': '.'.join(package.version.split('.')[0:2]), }) - print(os.path.abspath(os.path.join(wrapped_sphinx_directory, 'conf.py'))) with open(os.path.join(wrapped_sphinx_directory, 'conf.py'), 'w+') as f: f.write(rosdoc2_wrapping_conf_py_template.format_map(self.template_variables)) diff --git a/test/packages/basic_cpp/CMakeLists.txt b/test/packages/basic_cpp/CMakeLists.txt new file mode 100644 index 0000000..fce2d47 --- /dev/null +++ b/test/packages/basic_cpp/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.8) +project(basic_cpp) + +if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wall -Wextra -Wpedantic) +endif() + +# find dependencies +find_package(ament_cmake REQUIRED) +# uncomment the following section in order to fill in +# further dependencies manually. +# find_package( REQUIRED) + +add_executable(test_node src/test_node.cpp) +target_include_directories(test_node PUBLIC + $ + $) +target_compile_features(test_node PUBLIC c_std_99 cxx_std_17) # Require C99 and C++17 + +install(TARGETS test_node + DESTINATION lib/${PROJECT_NAME}) + +if(BUILD_TESTING) + find_package(ament_lint_auto REQUIRED) + # the following line skips the linter which checks for copyrights + # comment the line when a copyright and license is added to all source files + set(ament_cmake_copyright_FOUND TRUE) + # the following line skips cpplint (only works in a git repo) + # comment the line when this package is in a git repo and when + # a copyright and license is added to all source files + set(ament_cmake_cpplint_FOUND TRUE) + ament_lint_auto_find_test_dependencies() +endif() + +ament_package() diff --git a/test/packages/basic_cpp/doc/conf.py b/test/packages/basic_cpp/doc/conf.py new file mode 100644 index 0000000..a6c7e47 --- /dev/null +++ b/test/packages/basic_cpp/doc/conf.py @@ -0,0 +1,128 @@ +# Configuration file for the Sphinx documentation builder. +# This was copied from a generated conf.py, and flake8 does not like it. +# flake8: noqa +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- 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 os +import sys +sys.path.insert(0, os.path.abspath(os.path.join('/home/kent/github/rkent/rosdoc2/test/packages/basic_cpp/basic_cpp', '..'))) + + +# -- Project information ----------------------------------------------------- + +project = 'basic_cpp_and_more' +copyright = '2024, Dummy User' +author = 'Custom User' +print('[basic cpp] config.py') + +# The full version, including alpha/beta/rc tags +release = '0.0.0' + +version = '0.0' + + +# -- 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. +## rosdoc2 will extend the extensions to enable Breathe and Exhale if you +## do not add them here, as well as others, perhaps. +## If you add them manually rosdoc2 may still try to configure them. +## See the rosdoc2_settings below for some options on avoiding that. +extensions = [ + 'sphinx_rtd_theme', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# 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 = [] + +master_doc = 'index' + +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', + '.markdown': 'markdown', +} + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +## rosdoc2 will override the theme, but you may set one here for running Sphinx +## without the rosdoc2 tool. +html_theme = 'sphinx_rtd_theme' + +html_theme_options = { + # Toc options + 'collapse_navigation': False, + 'sticky_navigation': True, + 'navigation_depth': -1, + 'includehidden': True, + 'titles_only': False, +} + +# 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". +## rosdoc2 comments this out by default because we're not creating it. +# html_static_path = ['_static'] + +# -- Options for rosdoc2 ----------------------------------------------------- + +## These settings are specific to rosdoc2, and if Sphinx is run without rosdoc2 +## they will be safely ignored. +## None are required by default, so the lines below show the default values, +## therefore you will need to uncomment the lines and change their value +## if you want change the behavior of rosdoc2. +rosdoc2_settings = { + ## This setting, if True, will ensure breathe is part of the 'extensions', + ## and will set all of the breathe configurations, if not set, and override + ## settings as needed if they are set by this configuration. + # 'enable_breathe': True, + + ## This setting, if True, will ensure exhale is part of the 'extensions', + ## and will set all of the exhale configurations, if not set, and override + ## settings as needed if they are set by this configuration. + # 'enable_exhale': True, + + ## This setting, if provided, allows option specification for breathe + ## directives through exhale. If not set, exhale defaults will be used. + ## If an empty dictionary is provided, breathe defaults will be used. + # 'exhale_specs_mapping': {}, + + ## This setting, if True, will ensure autodoc is part of the 'extensions'. + # 'enable_autodoc': True, + + ## This setting, if True, will ensure intersphinx is part of the 'extensions'. + # 'enable_intersphinx': True, + + ## This setting, if True, will have the 'html_theme' overridden to provide + ## a consistent style across all of the ROS documentation. + # 'override_theme': True, + + ## This setting, if True, will automatically extend the intersphinx mapping + ## using inventory files found in the cross-reference directory. + ## If false, the `found_intersphinx_mappings` variable will be in the global + ## scope when run with rosdoc2, and could be conditionally used in your own + ## Sphinx conf.py file. + # 'automatically_extend_intersphinx_mapping': True, + + ## Support markdown + # 'support_markdown': True, +} diff --git a/test/packages/basic_cpp/doc/index.rst b/test/packages/basic_cpp/doc/index.rst new file mode 100644 index 0000000..b394f19 --- /dev/null +++ b/test/packages/basic_cpp/doc/index.rst @@ -0,0 +1,16 @@ +Welcome to the documentation for basic_cpp +========================================== + +.. toctree:: + :maxdepth: 2 + + C++ API + A Different Title + + +Indices and Search +================== + +* :ref:`genindex` +* :ref:`search` + diff --git a/test/packages/basic_cpp/doc/overview.markdown b/test/packages/basic_cpp/doc/overview.markdown new file mode 100644 index 0000000..1ab7317 --- /dev/null +++ b/test/packages/basic_cpp/doc/overview.markdown @@ -0,0 +1,3 @@ +# Overview + +This documentation demos a custom conf.py diff --git a/test/packages/basic_cpp/include/basic_cpp/test_node.hpp b/test/packages/basic_cpp/include/basic_cpp/test_node.hpp new file mode 100644 index 0000000..34cec56 --- /dev/null +++ b/test/packages/basic_cpp/include/basic_cpp/test_node.hpp @@ -0,0 +1,27 @@ +// +// Copyright 2024 R. Kent James +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef BASIC_CPP__TEST_NODE_HPP_ +#define BASIC_CPP__TEST_NODE_HPP_ + +/// @file +/// @brief This is an include file to go with the C++ file generated by `ros2 pkg create` + +/** + This demo program just prints hello world. + */ +int main(int argc, char ** argv) + +#endif // BASIC_CPP__TEST_NODE_HPP_ diff --git a/test/packages/basic_cpp/package.xml b/test/packages/basic_cpp/package.xml new file mode 100644 index 0000000..3c8165c --- /dev/null +++ b/test/packages/basic_cpp/package.xml @@ -0,0 +1,18 @@ + + + + basic_cpp + 0.0.0 + Basic c++ package using ros2 pkg create + ros2 user + Apache 2.0 + + ament_cmake + + ament_lint_auto + ament_lint_common + + + ament_cmake + + diff --git a/test/packages/basic_cpp/rosdoc2.yaml b/test/packages/basic_cpp/rosdoc2.yaml new file mode 100644 index 0000000..27248e8 --- /dev/null +++ b/test/packages/basic_cpp/rosdoc2.yaml @@ -0,0 +1,68 @@ +## Default configuration, generated by rosdoc2. + +## This 'attic section' self-documents this file's type and version. +type: 'rosdoc2 config' +version: 1 + +--- + +settings: + ## If this is true, a standard index page is generated in the output directory. + ## It uses the package information from the 'package.xml' to show details + ## about the package, creates a table of contents for the various builders + ## that were run, and may contain links to things like build farm jobs for + ## this package or links to other versions of this package. + + ## If false, you can still include content that would have been in the index + ## into one of your '.rst' files from your Sphinx project, using the + ## '.. include::' directive in Sphinx. + ## For example, you could include it in a custom 'index.rst' so you can have + ## the standard information followed by custom content. + + ## TODO(wjwwood): provide a concrete example of this (relative path?) + + ## If this is not specified explicitly, it defaults to 'true'. + generate_package_index: true + + ## This setting is relevant mostly if the standard Python package layout cannot + ## be assumed for 'sphinx-apidoc' invocation. The user can provide the path + ## (relative to the 'package.xml' file) where the Python modules defined by this + ## package are located. + python_source: 'basic_cpp' + + ## This setting, if true, attempts to run `doxygen` and the `breathe`/`exhale` + ## extensions to `sphinx` regardless of build type. This is most useful if the + ## user would like to generate C/C++ API documentation for a package that is not + ## of the `ament_cmake/cmake` build type. + always_run_doxygen: false + + ## This setting, if true, attempts to run `sphinx-apidoc` regardless of build + ## type. This is most useful if the user would like to generate Python API + ## documentation for a package that is not of the `ament_python` build type. + always_run_sphinx_apidoc: false + + # This setting, if provided, will override the build_type of this package + # for documentation purposes only. If not provided, documentation will be + # generated assuming the build_type in package.xml. + # override_build_type: 'ament_python' +builders: + ## Each stanza represents a separate build step, performed by a specific 'builder'. + ## The key of each stanza is the builder to use; this must be one of the + ## available builders. + ## The value of each stanza is a dictionary of settings for the builder that + ## outputs to that directory. + ## Required keys in the settings dictionary are: + ## * 'output_dir' - determines output subdirectory for builder instance + ## relative to --output-directory + ## * 'name' - used when referencing the built docs from the index. + + - doxygen: { + name: 'basic_cpp Public C/C++ API', + output_dir: 'generated/doxygen' + } + - sphinx: { + name: 'basic_cpp', + ## This path is relative to output staging. + doxygen_xml_directory: 'generated/doxygen/xml', + output_dir: '' + } diff --git a/test/packages/basic_cpp/src/test_node.cpp b/test/packages/basic_cpp/src/test_node.cpp new file mode 100644 index 0000000..fce5b94 --- /dev/null +++ b/test/packages/basic_cpp/src/test_node.cpp @@ -0,0 +1,10 @@ +#include + +int main(int argc, char ** argv) +{ + (void) argc; + (void) argv; + + printf("hello world basic_cpp package\n"); + return 0; +} diff --git a/test/test_builder.py b/test/test_builder.py index a7891ae..ab33ec4 100644 --- a/test/test_builder.py +++ b/test/test_builder.py @@ -243,3 +243,23 @@ def test_only_messages(session_dir): links_exist = ['generated/msg/NumPwrResult.html'] do_test_package(PKG_NAME, session_dir, includes=includes, links_exist=links_exist) + + +def test_basic_cpp(session_dir): + """Test a basic C++ package.""" + PKG_NAME = 'basic_cpp' + + do_build_package(DATAPATH / PKG_NAME, session_dir) + + includes = [ + 'a different title', # changed in custom index.rst + 'basic_cpp_and_more', # changed in custom config.py + ] + do_test_package(PKG_NAME, session_dir, includes=includes) + + # Previously, running rosdoc2 would create a 'generated' folder in the doc + # subdirectory of the package. Directory refactoring should have eliminated + # this. + generated = pathlib.Path(DATAPATH / PKG_NAME / 'doc' / 'generated') + assert not generated.exists(), \ + 'Building should not create a "generated" directory in package/doc' From 9810ed11e285eab1e0d057e37eb02a54313e358b Mon Sep 17 00:00:00 2001 From: "R. Kent James" Date: Tue, 2 Apr 2024 14:07:07 -0700 Subject: [PATCH 4/4] Create wrapped_sphinx_directory with an absolute path --- rosdoc2/verbs/build/builders/sphinx_builder.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rosdoc2/verbs/build/builders/sphinx_builder.py b/rosdoc2/verbs/build/builders/sphinx_builder.py index e9f9535..dec3695 100644 --- a/rosdoc2/verbs/build/builders/sphinx_builder.py +++ b/rosdoc2/verbs/build/builders/sphinx_builder.py @@ -442,7 +442,8 @@ def build(self, *, doc_build_folder, output_staging_directory): # We will ultimately run the sphinx project from a wrapped directory. Create it now, # so that we can put generated items there. - wrapped_sphinx_directory = os.path.join(doc_build_folder, 'wrapped_sphinx_directory') + wrapped_sphinx_directory = os.path.abspath( + os.path.join(doc_build_folder, 'wrapped_sphinx_directory')) os.makedirs(wrapped_sphinx_directory, exist_ok=True) # Generate rst documents for interfaces