diff --git a/.gitignore b/.gitignore index d334546546..b3631c8302 100644 --- a/.gitignore +++ b/.gitignore @@ -119,6 +119,7 @@ test/.tmp/* test/.sync_disk_db subprojects/.wraplock subprojects/capstone-*/ +subprojects/doxygen-awesome-css-*/ subprojects/pcre2*/ subprojects/libzip-*/ subprojects/lz4-*/ diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000000..cfd9d1d653 --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,25 @@ +if get_option('enable_doc') + doxygen_exe = find_program('doxygen', required: true) + + r = run_command(py3_exe, check_meson_subproject_py, 'doxygen-awesome-css', check: false) + if r.returncode() == 1 and get_option('subprojects_check') + error(subproject_clean_error_msg) + endif + doxygen_awesome_css = subproject('doxygen-awesome-css') + + doxygen_args = [ + py3_exe, files('../sys/doxygen.py'), + '--doxygen', doxygen_exe.full_path(), + '--source-dir', meson.project_source_root(), + '--output-dir', meson.project_build_root() / 'doc', + '--theme-dir', doxygen_awesome_css.get_variable('doxygen_awesome_css_dir'), + '--project-number', meson.project_version(), + ] + if get_option('werror') + doxygen_args += '--warnings-as-errors' + endif + + # Not built by default: generating the docs takes far longer than the library + # itself and needs no compiler output, so it stays an explicit `ninja doc`. + run_target('doc', command: doxygen_args) +endif diff --git a/meson.build b/meson.build index a4418c608f..ec26d2c02e 100644 --- a/meson.build +++ b/meson.build @@ -941,6 +941,7 @@ endif subdir('test') subdir('examples') +subdir('doc') install_data( diff --git a/meson_options.txt b/meson_options.txt index 508740d0f4..d890ce11d4 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -49,3 +49,4 @@ option('enable_benchmarks', type: 'boolean', value: false, description: 'Build m option('enable_rz_test', type: 'boolean', value: true, description: 'Build rz-test executable for regression testing') option('regenerate_cmds', type: 'feature', value: 'auto', description: 'Regenerate the cmd_descs.[ch] files (requires PyYAML)') option('enable_examples', type: 'boolean', value: false, description: 'Build examples') +option('enable_doc', type: 'boolean', value: false, description: 'Enable the `doc` target building the Doxygen API documentation') diff --git a/subprojects/doxygen-awesome-css.wrap b/subprojects/doxygen-awesome-css.wrap new file mode 100644 index 0000000000..d1f57cb7f9 --- /dev/null +++ b/subprojects/doxygen-awesome-css.wrap @@ -0,0 +1,7 @@ +[wrap-file] +directory = doxygen-awesome-css-2.4.2 +source_url = https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.2.tar.gz +source_fallback_url = https://github.com/rizinorg/fallback-repo/raw/main/doxygen-awesome-css-2.4.2.tar.gz +source_filename = doxygen-awesome-css-2.4.2.tar.gz +source_hash = 6f527e5733723bfd7243c28f24588dca4714cf977fea6147fdb4ae2050244409 +patch_directory = doxygen-awesome-css-2.4.2 diff --git a/subprojects/packagefiles/doxygen-awesome-css-2.4.2/meson.build b/subprojects/packagefiles/doxygen-awesome-css-2.4.2/meson.build new file mode 100644 index 0000000000..c86d1bfe70 --- /dev/null +++ b/subprojects/packagefiles/doxygen-awesome-css-2.4.2/meson.build @@ -0,0 +1,4 @@ +project('doxygen-awesome-css', version: '2.4.2', license: 'MIT') + +# The theme is plain CSS consumed by doxygen, so only its location is exported. +doxygen_awesome_css_dir = meson.current_source_dir() diff --git a/sys/doxygen.py b/sys/doxygen.py new file mode 100755 index 0000000000..7f629b9e9a --- /dev/null +++ b/sys/doxygen.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# +# SPDX-FileCopyrightText: 2026 RizinOrg +# SPDX-License-Identifier: LGPL-3.0-only + +"""Build the Rizin API documentation with the Doxygen Awesome theme. + +The checked-in Doxyfile holds every setting that does not depend on a path. +Everything that only the build system knows (where the theme was unpacked, +where the output goes, which version is being built) is appended here, so that +the Doxyfile stays free of build directory references and keeps working when +`doxygen` is invoked directly from the repository root. +""" + +import argparse +import os +import subprocess +import sys + +# Relative to the theme directory. The sidebar-only layout needs both. +THEME_STYLESHEETS = [ + "doxygen-awesome.css", + "doxygen-awesome-sidebar-only.css", +] + + +def doxyfile_overrides(args): + """Build the config lines that override the checked-in Doxyfile.""" + lines = [] + if args.output_dir: + lines.append("OUTPUT_DIRECTORY = %s" % args.output_dir) + if args.project_number: + lines.append("PROJECT_NUMBER = %s" % args.project_number) + if args.warnings_as_errors: + lines.append("WARN_AS_ERROR = FAIL_ON_WARNINGS") + if args.theme_dir: + sheets = [os.path.join(args.theme_dir, name) for name in THEME_STYLESHEETS] + missing = [sheet for sheet in sheets if not os.path.isfile(sheet)] + if missing: + raise SystemExit("missing theme file(s): %s" % ", ".join(missing)) + lines.append("HTML_EXTRA_STYLESHEET = %s" % " \\\n ".join(sheets)) + return lines + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--doxygen", default="doxygen", help="doxygen executable") + parser.add_argument("--source-dir", default=".", help="repository root") + parser.add_argument("--output-dir", help="where the generated docs are written") + parser.add_argument("--theme-dir", help="doxygen-awesome-css checkout") + parser.add_argument("--project-number", help="version shown in the docs") + parser.add_argument( + "--warnings-as-errors", + action="store_true", + help="fail on any Doxygen diagnostic (needs a pinned Doxygen version)", + ) + args = parser.parse_args() + + doxyfile = os.path.join(args.source_dir, "Doxyfile") + if not os.path.isfile(doxyfile): + raise SystemExit("no Doxyfile in %s" % args.source_dir) + + with open(doxyfile, encoding="utf-8") as fd: + config = fd.read() + config += "\n" + "\n".join(doxyfile_overrides(args)) + "\n" + + # Doxygen resolves the relative paths of the Doxyfile against the current + # directory, so it has to run from the repository root. + proc = subprocess.run( + [args.doxygen, "-"], + cwd=args.source_dir, + input=config, + encoding="utf-8", + check=False, + ) + return proc.returncode + + +if __name__ == "__main__": + sys.exit(main())