diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 892f11884..73a3f6728 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,6 +6,9 @@ benchmarks/ @rapidsai/rmm-cpp-codeowners #python code owners python/ @rapidsai/rmm-python-codeowners +#docs code owners +fern/ @rapidsai/rmm-cpp-codeowners @rapidsai/rmm-python-codeowners + #cmake code owners **/CMakeLists.txt @rapidsai/rmm-cmake-codeowners **/cmake/ @rapidsai/rmm-cmake-codeowners diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 9dbdc1f44..c0907413a 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -136,6 +136,7 @@ jobs: - '!ci/test_wheel.sh' - '!ci/validate_wheel.sh' - '!docs/**' + - '!fern/**' - '!img/**' - '!python/**' test_python_conda: @@ -165,6 +166,7 @@ jobs: - '!cpp/.clang-tidy' - '!cpp/doxygen/**' - '!docs/**' + - '!fern/**' - '!img/**' test_python_wheels: - '**' @@ -197,6 +199,7 @@ jobs: - '!cpp/.clang-tidy' - '!cpp/doxygen/**' - '!docs/**' + - '!fern/**' - '!img/**' checks: permissions: diff --git a/.gitignore b/.gitignore index baf30c999..5f215cffc 100644 --- a/.gitignore +++ b/.gitignore @@ -98,6 +98,8 @@ cpp/doxygen/html cpp/doxygen/latex cpp/doxygen/xml docs/_build +fern/pages/api_reference +fern/sphinx/build #Java target @@ -134,6 +136,7 @@ celerybeat-schedule # dotenv .env +.venv-docs/ # virtualenv .venv diff --git a/build.sh b/build.sh index 3a5f919e5..46d1f4f59 100755 --- a/build.sh +++ b/build.sh @@ -19,11 +19,12 @@ ARGS=$* # script, and that this script resides in the repo dir! REPODIR=$(cd "$(dirname "$0")"; pwd) -VALIDARGS="clean librmm rmm -v -g -n --ptds -h tests benchmarks" -HELP="$0 [clean] [librmm] [rmm] [-v] [-g] [-n] [--ptds] [--cmake-args=\"\"] [-h] +VALIDARGS="clean librmm rmm docs -v -g -n --ptds -h tests benchmarks" +HELP="$0 [clean] [librmm] [rmm] [docs] [-v] [-g] [-n] [--ptds] [--cmake-args=\"\"] [-h] clean - remove all existing build artifacts and configuration (start over) librmm - build and install the librmm C++ code rmm - build and install the rmm Python package + docs - validate the Fern documentation benchmarks - build benchmarks tests - build tests -v - verbose build mode @@ -183,3 +184,8 @@ if (( NUMARGS == 0 )) || hasArg rmm; then "${PYTHON_ARGS_FOR_INSTALL[@]}" \ "${REPODIR}"/python/rmm fi + +# Validate Fern docs +if hasArg docs; then + "${REPODIR}/fern/build_docs.sh" "${FERN_DOCS_MODE:-check}" +fi diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 5ee3b835e..86b6b273e 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -4,26 +4,17 @@ set -euo pipefail -rapids-logger "Downloading artifacts from previous jobs" -CPP_CHANNEL=$(rapids-download-conda-from-github cpp) -PYTHON_CHANNEL=$(rapids-download-from-github "$(rapids-package-name "conda_python" rmm --stable --cuda "$RAPIDS_CUDA_VERSION")") - -rapids-logger "Create test conda environment" +rapids-logger "Create docs conda environment" . /opt/conda/etc/profile.d/conda.sh rapids-logger "Configuring conda strict channel priority" conda config --set channel_priority strict -RAPIDS_VERSION_MAJOR_MINOR="$(rapids-version-major-minor)" -export RAPIDS_VERSION_MAJOR_MINOR - rapids-dependency-file-generator \ --output conda \ --file-key docs \ --matrix "cuda=${RAPIDS_CUDA_VERSION%.*};arch=$(arch);py=${RAPIDS_PY_VERSION}" \ - --prepend-channel "${CPP_CHANNEL}" \ - --prepend-channel "${PYTHON_CHANNEL}" \ | tee env.yaml rapids-mamba-retry env create --yes -f env.yaml -n docs @@ -31,22 +22,5 @@ conda activate docs rapids-print-env -RAPIDS_DOCS_DIR="$(mktemp -d)" -export RAPIDS_DOCS_DIR - - -rapids-logger "Build CPP docs" -pushd cpp/doxygen -doxygen Doxyfile -mkdir -p "${RAPIDS_DOCS_DIR}/librmm/html" -mv html/* "${RAPIDS_DOCS_DIR}/librmm/html" -popd - -rapids-logger "Build Python docs" -pushd docs -make dirhtml -mkdir -p "${RAPIDS_DOCS_DIR}/rmm/html" -mv _build/dirhtml/* "${RAPIDS_DOCS_DIR}/rmm/html" -popd - -RAPIDS_VERSION_NUMBER="${RAPIDS_VERSION_MAJOR_MINOR}" rapids-upload-docs +rapids-logger "Validate Fern docs" +fern/build_docs.sh "${FERN_DOCS_MODE:-check}" diff --git a/conda/environments/all_cuda-129_arch-aarch64.yaml b/conda/environments/all_cuda-129_arch-aarch64.yaml index 1d470f54e..b8a499787 100644 --- a/conda/environments/all_cuda-129_arch-aarch64.yaml +++ b/conda/environments/all_cuda-129_arch-aarch64.yaml @@ -20,11 +20,9 @@ dependencies: - gcovr>=5.0 - graphviz - identify>=2.5.20 -- ipython -- make - myst-parser -- nbsphinx - ninja +- nodejs>=18 - numba-cuda>=0.22.1,<0.29.0 - numba>=0.60.0,<0.65.0 - numpy>=1.23,<3.0 @@ -39,8 +37,7 @@ dependencies: - rapids-logger==0.2.*,>=0.0.0a0 - scikit-build-core>=0.11.0 - sphinx -- sphinx-copybutton +- sphinx-markdown-builder - sphinx-markdown-tables -- sphinxcontrib-jquery - sysroot_linux-aarch64==2.28 name: all_cuda-129_arch-aarch64 diff --git a/conda/environments/all_cuda-129_arch-x86_64.yaml b/conda/environments/all_cuda-129_arch-x86_64.yaml index e53d15d89..3d1d40464 100644 --- a/conda/environments/all_cuda-129_arch-x86_64.yaml +++ b/conda/environments/all_cuda-129_arch-x86_64.yaml @@ -20,11 +20,9 @@ dependencies: - gcovr>=5.0 - graphviz - identify>=2.5.20 -- ipython -- make - myst-parser -- nbsphinx - ninja +- nodejs>=18 - numba-cuda>=0.22.1,<0.29.0 - numba>=0.60.0,<0.65.0 - numpy>=1.23,<3.0 @@ -39,8 +37,7 @@ dependencies: - rapids-logger==0.2.*,>=0.0.0a0 - scikit-build-core>=0.11.0 - sphinx -- sphinx-copybutton +- sphinx-markdown-builder - sphinx-markdown-tables -- sphinxcontrib-jquery - sysroot_linux-64==2.28 name: all_cuda-129_arch-x86_64 diff --git a/conda/environments/all_cuda-132_arch-aarch64.yaml b/conda/environments/all_cuda-132_arch-aarch64.yaml index 7b9d5dd8b..101b7caa8 100644 --- a/conda/environments/all_cuda-132_arch-aarch64.yaml +++ b/conda/environments/all_cuda-132_arch-aarch64.yaml @@ -20,11 +20,9 @@ dependencies: - gcovr>=5.0 - graphviz - identify>=2.5.20 -- ipython -- make - myst-parser -- nbsphinx - ninja +- nodejs>=18 - numba-cuda>=0.22.1,<0.29.0 - numba>=0.60.0,<0.65.0 - numpy>=1.23,<3.0 @@ -39,8 +37,7 @@ dependencies: - rapids-logger==0.2.*,>=0.0.0a0 - scikit-build-core>=0.11.0 - sphinx -- sphinx-copybutton +- sphinx-markdown-builder - sphinx-markdown-tables -- sphinxcontrib-jquery - sysroot_linux-aarch64==2.28 name: all_cuda-132_arch-aarch64 diff --git a/conda/environments/all_cuda-132_arch-x86_64.yaml b/conda/environments/all_cuda-132_arch-x86_64.yaml index ca08f786d..e329831ec 100644 --- a/conda/environments/all_cuda-132_arch-x86_64.yaml +++ b/conda/environments/all_cuda-132_arch-x86_64.yaml @@ -20,11 +20,9 @@ dependencies: - gcovr>=5.0 - graphviz - identify>=2.5.20 -- ipython -- make - myst-parser -- nbsphinx - ninja +- nodejs>=18 - numba-cuda>=0.22.1,<0.29.0 - numba>=0.60.0,<0.65.0 - numpy>=1.23,<3.0 @@ -39,8 +37,7 @@ dependencies: - rapids-logger==0.2.*,>=0.0.0a0 - scikit-build-core>=0.11.0 - sphinx -- sphinx-copybutton +- sphinx-markdown-builder - sphinx-markdown-tables -- sphinxcontrib-jquery - sysroot_linux-64==2.28 name: all_cuda-132_arch-x86_64 diff --git a/dependencies.yaml b/dependencies.yaml index 54bf774c8..3ba74cc33 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -68,9 +68,9 @@ files: includes: - cuda_version - docs - - py_version - - depends_on_rmm - depends_on_librmm + - depends_on_rmm + - py_version py_cpp_build: output: pyproject pyproject_dir: python/librmm @@ -336,18 +336,16 @@ dependencies: - breathe>=4.35.0 - *doxygen - graphviz - - ipython - - make - myst-parser - - nbsphinx - - &numba numba>=0.60.0,<0.65.0 - - &numba_cuda numba-cuda>=0.22.1,<0.29.0 + - nodejs>=18 + - numba>=0.60.0,<0.65.0 + - numba-cuda>=0.22.1,<0.29.0 - numpydoc + - packaging - pydata-sphinx-theme>=0.15.4 - sphinx - - sphinx-copybutton + - sphinx-markdown-builder - sphinx-markdown-tables - - sphinxcontrib-jquery py_version: specific: - output_types: conda @@ -395,6 +393,8 @@ dependencies: common: - output_types: [conda, requirements, pyproject] packages: + - &numba numba>=0.60.0,<0.65.0 + - &numba_cuda numba-cuda>=0.22.1,<0.29.0 - packaging - pytest - pytest-cov diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 4c6a7f0e4..000000000 --- a/docs/Makefile +++ /dev/null @@ -1,23 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION. -# SPDX-License-Identifier: Apache-2.0 - -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line, and also -# from the environment for the first two. -SPHINXOPTS = -n -v -W --keep-going -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/_static/RAPIDS-logo-purple.png b/docs/_static/RAPIDS-logo-purple.png deleted file mode 100644 index d884e0137..000000000 Binary files a/docs/_static/RAPIDS-logo-purple.png and /dev/null differ diff --git a/fern/build_docs.sh b/fern/build_docs.sh new file mode 100755 index 000000000..4e2a9b4c6 --- /dev/null +++ b/fern/build_docs.sh @@ -0,0 +1,105 @@ +#!/bin/bash +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. +# SPDX-License-Identifier: Apache-2.0 + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_DIR=$(cd "${SCRIPT_DIR}/.." && pwd) +MODE="${1:-check}" + +if [[ $# -gt 0 ]]; then + shift +fi + +usage() { + cat <<'EOF' +Usage: fern/build_docs.sh [check|preview|publish|dev] [fern arguments...] + +Modes: + check Validate Fern configuration, links, and Markdown syntax. + preview Build and publish a Fern preview deployment. + publish Build and publish the production Fern docs site. + dev Start Fern's local docs preview server. +EOF +} + +require_node_18() { + if ! command -v node >/dev/null 2>&1; then + echo "Fern docs require Node.js 18 or newer, but node was not found on PATH." >&2 + exit 1 + fi + + local node_version + local node_major + node_version=$(node -p 'process.versions.node' 2>/dev/null || true) + node_major="${node_version%%.*}" + + if [[ ! "${node_major}" =~ ^[0-9]+$ || "${node_major}" -lt 18 ]]; then + echo "Fern docs require Node.js 18 or newer, but found Node.js ${node_version:-unknown}." >&2 + exit 1 + fi +} + +require_node_18 + +if [[ -n "${FERN_CLI:-}" ]]; then + FERN_CMD=("${FERN_CLI}") +elif command -v fern >/dev/null 2>&1; then + FERN_CMD=("fern") +else + FERN_CMD=("npx" "--yes" "fern-api@5.30.4") +fi + +run_fern() { + "${FERN_CMD[@]}" "$@" +} + +generate_api_reference() { + pushd "${REPO_DIR}" >/dev/null + python3 fern/scripts/generate_api_reference.py + popd >/dev/null +} + +run_checks() { + pushd "${REPO_DIR}" >/dev/null + run_fern check --warnings + run_fern docs md check + popd >/dev/null +} + +case "${MODE}" in + check) + generate_api_reference + run_checks + ;; + preview) + generate_api_reference + run_checks + pushd "${REPO_DIR}" >/dev/null + run_fern generate --docs --preview "$@" + popd >/dev/null + ;; + publish) + generate_api_reference + run_checks + pushd "${REPO_DIR}" >/dev/null + run_fern generate --docs "$@" + popd >/dev/null + ;; + dev) + generate_api_reference + pushd "${REPO_DIR}" >/dev/null + run_fern docs dev "$@" + popd >/dev/null + ;; + -h|--help|help) + usage + ;; + *) + echo "Unknown mode: ${MODE}" >&2 + echo >&2 + usage >&2 + exit 2 + ;; +esac diff --git a/fern/docs.yml b/fern/docs.yml new file mode 100644 index 000000000..b4c7464d9 --- /dev/null +++ b/fern/docs.yml @@ -0,0 +1,84 @@ +# yaml-language-server: $schema=https://schema.buildwithfern.dev/docs-yml.json + +title: "RMM" +instances: + - url: "nvidia-rmm.docs.buildwithfern.com/rmm" + custom-domain: docs.nvidia.com/rmm +colors: + accentPrimary: + light: "#2F4B00" + dark: "#76B900" + background: + light: "#FFFFFF" + dark: "#000000" +check: + rules: + broken-links: "error" +layout: + searchbar-placement: "header" + page-width: "1376px" + sidebar-width: "248px" + content-width: "812px" + tabs-placement: "header" + hide-feedback: true +theme: + page-actions: "toolbar" + footer-nav: "minimal" +experimental: + basepath-aware: true +navbar-links: + - type: "secondary" + text: "GitHub" + url: "https://github.com/rapidsai/rmm" + - type: "secondary" + text: "CUDA-X" + url: "https://www.nvidia.com/en-us/technologies/cuda-x/" +navigation: + - page: "Home" + path: "./pages/index.md" + - page: "User Guide" + path: "./pages/user_guide/guide.md" + - section: "API Reference" + contents: + - section: "C++ API" + contents: + - page: "Overview" + path: "./pages/api_reference/cpp/index.md" + - section: "Memory Resources" + contents: + - page: "Overview" + path: "./pages/api_reference/cpp/memory_resources/index.md" + - page: "Memory Resources" + path: "./pages/api_reference/cpp/memory_resources/memory_resources.md" + - page: "Memory Resource Adaptors" + path: "./pages/api_reference/cpp/memory_resources/memory_resource_adaptors.md" + - page: "Data Containers" + path: "./pages/api_reference/cpp/data_containers.md" + - page: "Thrust Integrations" + path: "./pages/api_reference/cpp/thrust_integrations.md" + - page: "CUDA Device Management" + path: "./pages/api_reference/cpp/cuda_device_management.md" + - page: "CUDA Streams" + path: "./pages/api_reference/cpp/cuda_streams.md" + - page: "Errors" + path: "./pages/api_reference/cpp/errors.md" + - page: "Utilities" + path: "./pages/api_reference/cpp/utilities.md" + - page: "RMM Namespace" + path: "./pages/api_reference/cpp/rmm_namespace.md" + - section: "Python API" + contents: + - page: "Overview" + path: "./pages/api_reference/python/index.md" + - page: "rmm" + path: "./pages/api_reference/python/rmm.md" + - page: "Memory Resources" + path: "./pages/api_reference/python/mr.md" + - page: "Allocators" + path: "./pages/api_reference/python/allocators.md" + - page: "Statistics" + path: "./pages/api_reference/python/statistics.md" + - page: "pylibrmm" + path: "./pages/api_reference/python/pylibrmm.md" + - page: "librmm" + path: "./pages/api_reference/python/librmm.md" diff --git a/fern/fern.config.json b/fern/fern.config.json new file mode 100644 index 000000000..08c703bf5 --- /dev/null +++ b/fern/fern.config.json @@ -0,0 +1,4 @@ +{ + "organization": "nvidia", + "version": "*" +} diff --git a/docs/index.md b/fern/pages/index.md similarity index 59% rename from docs/index.md rename to fern/pages/index.md index d95428e72..d5f89f235 100644 --- a/docs/index.md +++ b/fern/pages/index.md @@ -2,11 +2,6 @@ RMM (RAPIDS Memory Manager) is a library for allocating and managing GPU memory in C++ and Python. -```{toctree} -:maxdepth: 2 -:caption: Contents +## Documentation -user_guide/guide -cpp/index -python/index -``` +- [User Guide](./user_guide/guide.md) diff --git a/docs/user_guide/guide.md b/fern/pages/user_guide/guide.md similarity index 100% rename from docs/user_guide/guide.md rename to fern/pages/user_guide/guide.md diff --git a/fern/scripts/generate_api_reference.py b/fern/scripts/generate_api_reference.py new file mode 100644 index 000000000..ddc4510fd --- /dev/null +++ b/fern/scripts/generate_api_reference.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. +# SPDX-License-Identifier: Apache-2.0 + +"""Generate Fern API reference pages from Sphinx-rendered Markdown.""" + +from __future__ import annotations + +import os +import shutil +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[2] +FERN_ROOT = REPO_ROOT / "fern" +SPHINX_ROOT = FERN_ROOT / "sphinx" +SPHINX_SOURCE_DIR = SPHINX_ROOT / "source" +SPHINX_BUILD_DIR = SPHINX_ROOT / "build" +MARKDOWN_BUILD_DIR = SPHINX_BUILD_DIR / "markdown" +API_OUTPUT_DIR = FERN_ROOT / "pages" / "api_reference" +DOXYGEN_DIR = REPO_ROOT / "cpp" / "doxygen" +DOXYGEN_XML_INDEX = DOXYGEN_DIR / "xml" / "index.xml" + +GENERATED_NOTICE = ( + "\n\n" +) +API_SOURCE_DIRS = ("cpp", "python") + + +def relative_to_repo(path: Path) -> str: + """Return a repo-relative path for concise command output.""" + return str(path.relative_to(REPO_ROOT)) + + +def require_command(command: str) -> None: + if find_command(command) is None: + raise SystemExit( + f"{command!r} is required to generate Fern API reference pages. " + "Install the docs environment from dependencies.yaml." + ) + + +def find_command(command: str) -> str | None: + sibling = Path(sys.executable).with_name(command) + if sibling.exists(): + return str(sibling) + return shutil.which(command) + + +def docs_environment() -> dict[str, str]: + env = os.environ.copy() + version = (REPO_ROOT / "VERSION").read_text(encoding="utf-8").strip() + major_minor = ".".join(version.split(".")[:2]) + env.setdefault("RAPIDS_VERSION", version) + env.setdefault("RAPIDS_VERSION_MAJOR_MINOR", major_minor) + return env + + +def run_command(args: list[str], *, cwd: Path, env: dict[str, str]) -> None: + display = " ".join(args) + print(f"+ ({relative_to_repo(cwd)}) {display}", file=sys.stderr) + subprocess.run(args, cwd=cwd, env=env, check=True) + + +def build_doxygen_xml(env: dict[str, str]) -> None: + require_command("doxygen") + doxygen = find_command("doxygen") + if doxygen is None: + raise SystemExit("'doxygen' is required to generate C++ API XML.") + run_command([doxygen, "Doxyfile"], cwd=DOXYGEN_DIR, env=env) + if not DOXYGEN_XML_INDEX.exists(): + raise SystemExit( + "Doxygen completed without producing cpp/doxygen/xml/index.xml" + ) + + +def build_sphinx_markdown(env: dict[str, str]) -> None: + shutil.rmtree(MARKDOWN_BUILD_DIR, ignore_errors=True) + run_command( + [ + sys.executable, + "-m", + "sphinx.cmd.build", + "-M", + "markdown", + ".", + "../build", + ], + cwd=SPHINX_SOURCE_DIR, + env=env, + ) + if not MARKDOWN_BUILD_DIR.exists(): + raise SystemExit( + "Sphinx completed without producing fern/sphinx/build/markdown" + ) + + +def normalize_markdown(text: str) -> str: + text = text.replace("\r\n", "\n") + text = "\n".join(line.rstrip() for line in text.splitlines()).strip() + return f"{GENERATED_NOTICE}{text}\n" + + +def copy_generated_markdown_pages(markdown_dir: Path, output_dir: Path) -> int: + shutil.rmtree(output_dir, ignore_errors=True) + copied = 0 + for source_dir_name in API_SOURCE_DIRS: + source_dir = markdown_dir / source_dir_name + if not source_dir.exists(): + continue + for source_path in sorted(source_dir.rglob("*.md")): + target_path = output_dir / source_path.relative_to(markdown_dir) + target_path.parent.mkdir(parents=True, exist_ok=True) + target_path.write_text( + normalize_markdown(source_path.read_text(encoding="utf-8")), + encoding="utf-8", + ) + copied += 1 + return copied + + +def main() -> int: + env = docs_environment() + build_doxygen_xml(env) + build_sphinx_markdown(env) + copied = copy_generated_markdown_pages(MARKDOWN_BUILD_DIR, API_OUTPUT_DIR) + if copied == 0: + raise SystemExit("No API reference Markdown pages were generated") + print(f"Generated {copied} Fern API reference pages.", file=sys.stderr) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/fern/scripts/tests/test_build_docs.py b/fern/scripts/tests/test_build_docs.py new file mode 100644 index 000000000..6d3a0b252 --- /dev/null +++ b/fern/scripts/tests/test_build_docs.py @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. +# SPDX-License-Identifier: Apache-2.0 + +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] +FERN_ROOT = REPO_ROOT / "fern" + + +def test_build_docs_pins_npx_fern_api_fallback(): + script = REPO_ROOT / "fern" / "build_docs.sh" + text = script.read_text(encoding="utf-8") + + assert '"fern-api@5.30.4"' in text + assert 'FERN_CMD=("npx" "--yes" "fern-api")' not in text + + +def test_build_docs_runs_sphinx_api_generator(): + script = REPO_ROOT / "fern" / "build_docs.sh" + text = script.read_text(encoding="utf-8") + + assert "generate_api_reference.py" in text + assert "run_fern check --warnings" in text + + +def test_fern_docs_do_not_link_to_legacy_api_reference(): + docs_yml = FERN_ROOT / "docs.yml" + page_text = "\n".join( + path.read_text(encoding="utf-8") + for path in sorted((FERN_ROOT / "pages").rglob("*.md")) + ) + + assert "docs.rapids.ai/api/rmm" not in page_text + assert "api_reference" in docs_yml.read_text(encoding="utf-8") diff --git a/fern/scripts/tests/test_generate_api_reference.py b/fern/scripts/tests/test_generate_api_reference.py new file mode 100644 index 000000000..43a5f7467 --- /dev/null +++ b/fern/scripts/tests/test_generate_api_reference.py @@ -0,0 +1,94 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. +# SPDX-License-Identifier: Apache-2.0 + +import importlib.util +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[3] +SCRIPT_PATH = REPO_ROOT / "fern" / "scripts" / "generate_api_reference.py" + + +def load_generator(): + spec = importlib.util.spec_from_file_location( + "generate_api_reference", SCRIPT_PATH + ) + module = importlib.util.module_from_spec(spec) + assert spec.loader is not None + spec.loader.exec_module(module) + return module + + +def test_restored_sphinx_sources_keep_api_semantics(): + cpp_source = ( + REPO_ROOT / "fern" / "sphinx" / "source" / "cpp" / "data_containers.md" + ).read_text(encoding="utf-8") + python_source = ( + REPO_ROOT / "fern" / "sphinx" / "source" / "python" / "mr.md" + ).read_text(encoding="utf-8") + conf = (REPO_ROOT / "fern" / "sphinx" / "source" / "conf.py").read_text( + encoding="utf-8" + ) + + assert "{doxygengroup} data_containers" in cpp_source + assert ".. automodule:: rmm.mr" in python_source + assert "sphinx_markdown_builder" in conf + assert "../../../cpp/doxygen/xml" in conf + + +def test_copy_generated_markdown_pages_preserves_rendered_content(tmp_path): + generator = load_generator() + markdown_dir = tmp_path / "sphinx" / "build" / "markdown" + output_dir = tmp_path / "fern" / "pages" / "api_reference" + (markdown_dir / "cpp").mkdir(parents=True) + (markdown_dir / "python").mkdir(parents=True) + (markdown_dir / "cpp" / "data_containers.md").write_text( + "# Data Containers\n\n" + "RAII construct for device memory allocation.\n\n" + "This class allocates untyped and uninitialized device memory.\n", + encoding="utf-8", + ) + (markdown_dir / "python" / "mr.md").write_text( + "# rmm.mr (Memory Resources)\n\n" + "Python memory resource docstrings are rendered here.\n", + encoding="utf-8", + ) + + copied = generator.copy_generated_markdown_pages(markdown_dir, output_dir) + + cpp_output = (output_dir / "cpp" / "data_containers.md").read_text( + encoding="utf-8" + ) + python_output = (output_dir / "python" / "mr.md").read_text( + encoding="utf-8" + ) + assert copied == 2 + assert "Generated from the Sphinx API extraction build." in cpp_output + assert ( + "This class allocates untyped and uninitialized device memory." + in cpp_output + ) + assert ( + "Python memory resource docstrings are rendered here." in python_output + ) + assert "docs.rapids.ai/api/rmm" not in cpp_output + python_output + + +def test_find_command_prefers_current_python_environment( + tmp_path, monkeypatch +): + generator = load_generator() + bin_dir = tmp_path / "bin" + bin_dir.mkdir() + command = bin_dir / "doxygen" + command.write_text("#!/bin/sh\n", encoding="utf-8") + monkeypatch.setattr(generator.sys, "executable", str(bin_dir / "python")) + + assert generator.find_command("doxygen") == str(command) + + +def test_docs_yml_includes_native_api_navigation(): + docs_yml = (REPO_ROOT / "fern" / "docs.yml").read_text(encoding="utf-8") + + assert 'section: "API Reference"' in docs_yml + assert "./pages/api_reference/cpp/data_containers.md" in docs_yml + assert "./pages/api_reference/python/mr.md" in docs_yml diff --git a/docs/conf.py b/fern/sphinx/source/conf.py similarity index 96% rename from docs/conf.py rename to fern/sphinx/source/conf.py index db45dff09..3ce419b21 100644 --- a/docs/conf.py +++ b/fern/sphinx/source/conf.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2020-2025, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2020-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 # Configuration file for the Sphinx documentation builder. @@ -46,28 +46,20 @@ # ones. extensions = [ - "IPython.sphinxext.ipython_console_highlighting", - "IPython.sphinxext.ipython_directive", "breathe", "myst_parser", - "nbsphinx", "numpydoc", + "sphinx_markdown_builder", "sphinx.ext.autodoc", "sphinx.ext.autosummary", "sphinx.ext.intersphinx", - "sphinx_copybutton", "sphinx_markdown_tables", - "sphinxcontrib.jquery", ] # Breathe Configuration -breathe_projects = {"librmm": "../cpp/doxygen/xml"} +breathe_projects = {"librmm": "../../../cpp/doxygen/xml"} breathe_default_project = "librmm" -copybutton_prompt_text = ">>> " - -ipython_mplbackend = "str" - # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -110,6 +102,11 @@ # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False +markdown_flavor = "github" +markdown_uri_doc_suffix = ".md" +markdown_anchor_sections = True +markdown_anchor_signatures = True + # -- Options for HTML output ---------------------------------------------- diff --git a/docs/cpp/cuda_device_management.md b/fern/sphinx/source/cpp/cuda_device_management.md similarity index 100% rename from docs/cpp/cuda_device_management.md rename to fern/sphinx/source/cpp/cuda_device_management.md diff --git a/docs/cpp/cuda_streams.md b/fern/sphinx/source/cpp/cuda_streams.md similarity index 100% rename from docs/cpp/cuda_streams.md rename to fern/sphinx/source/cpp/cuda_streams.md diff --git a/docs/cpp/data_containers.md b/fern/sphinx/source/cpp/data_containers.md similarity index 100% rename from docs/cpp/data_containers.md rename to fern/sphinx/source/cpp/data_containers.md diff --git a/docs/cpp/errors.md b/fern/sphinx/source/cpp/errors.md similarity index 100% rename from docs/cpp/errors.md rename to fern/sphinx/source/cpp/errors.md diff --git a/docs/cpp/index.md b/fern/sphinx/source/cpp/index.md similarity index 80% rename from docs/cpp/index.md rename to fern/sphinx/source/cpp/index.md index 4ef0edf41..6b4c69302 100644 --- a/docs/cpp/index.md +++ b/fern/sphinx/source/cpp/index.md @@ -14,8 +14,3 @@ errors utilities rmm_namespace ``` - -## Indices and tables - -- {ref}`genindex` -- {ref}`search` diff --git a/docs/cpp/memory_resources/index.md b/fern/sphinx/source/cpp/memory_resources/index.md similarity index 100% rename from docs/cpp/memory_resources/index.md rename to fern/sphinx/source/cpp/memory_resources/index.md diff --git a/docs/cpp/memory_resources/memory_resource_adaptors.md b/fern/sphinx/source/cpp/memory_resources/memory_resource_adaptors.md similarity index 100% rename from docs/cpp/memory_resources/memory_resource_adaptors.md rename to fern/sphinx/source/cpp/memory_resources/memory_resource_adaptors.md diff --git a/docs/cpp/memory_resources/memory_resources.md b/fern/sphinx/source/cpp/memory_resources/memory_resources.md similarity index 100% rename from docs/cpp/memory_resources/memory_resources.md rename to fern/sphinx/source/cpp/memory_resources/memory_resources.md diff --git a/docs/cpp/rmm_namespace.md b/fern/sphinx/source/cpp/rmm_namespace.md similarity index 100% rename from docs/cpp/rmm_namespace.md rename to fern/sphinx/source/cpp/rmm_namespace.md diff --git a/docs/cpp/thrust_integrations.md b/fern/sphinx/source/cpp/thrust_integrations.md similarity index 100% rename from docs/cpp/thrust_integrations.md rename to fern/sphinx/source/cpp/thrust_integrations.md diff --git a/docs/cpp/utilities.md b/fern/sphinx/source/cpp/utilities.md similarity index 100% rename from docs/cpp/utilities.md rename to fern/sphinx/source/cpp/utilities.md diff --git a/fern/sphinx/source/index.md b/fern/sphinx/source/index.md new file mode 100644 index 000000000..46fc09cd3 --- /dev/null +++ b/fern/sphinx/source/index.md @@ -0,0 +1,8 @@ +# RMM API Reference + +```{toctree} +:maxdepth: 2 + +cpp/index +python/index +``` diff --git a/docs/python/allocators.md b/fern/sphinx/source/python/allocators.md similarity index 100% rename from docs/python/allocators.md rename to fern/sphinx/source/python/allocators.md diff --git a/docs/python/index.md b/fern/sphinx/source/python/index.md similarity index 100% rename from docs/python/index.md rename to fern/sphinx/source/python/index.md diff --git a/docs/python/librmm.md b/fern/sphinx/source/python/librmm.md similarity index 100% rename from docs/python/librmm.md rename to fern/sphinx/source/python/librmm.md diff --git a/docs/python/mr.md b/fern/sphinx/source/python/mr.md similarity index 100% rename from docs/python/mr.md rename to fern/sphinx/source/python/mr.md diff --git a/docs/python/pylibrmm.md b/fern/sphinx/source/python/pylibrmm.md similarity index 100% rename from docs/python/pylibrmm.md rename to fern/sphinx/source/python/pylibrmm.md diff --git a/docs/python/rmm.md b/fern/sphinx/source/python/rmm.md similarity index 100% rename from docs/python/rmm.md rename to fern/sphinx/source/python/rmm.md diff --git a/docs/python/statistics.md b/fern/sphinx/source/python/statistics.md similarity index 100% rename from docs/python/statistics.md rename to fern/sphinx/source/python/statistics.md diff --git a/python/rmm/rmm/statistics.py b/python/rmm/rmm/statistics.py index 059c323e7..add8ecf87 100644 --- a/python/rmm/rmm/statistics.py +++ b/python/rmm/rmm/statistics.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION. # SPDX-License-Identifier: Apache-2.0 import inspect @@ -129,7 +129,7 @@ def statistics() -> Generator[None, None, None]: If statistics have been enabled already (the current memory resource is an instance of StatisticsResourceAdaptor), new counters are pushed on the current allocation statistics stack when entering the context and popped - again when exiting using `push_statistics()` and `push_statistics()`. + again when exiting using `push_statistics()` and `pop_statistics()`. If statistics have not been enabled, a new StatisticsResourceAdaptor is set as the current RMM memory resource when entering the context and removed