From 0b56d47543d604a9280e1d609633065f60950df9 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Mon, 6 Jul 2026 18:21:43 -0500 Subject: [PATCH 01/22] Port RAPIDS docs from Jekyll to Sphinx Signed-off-by: Bradley Dice --- .devcontainer/devcontainer.json | 24 - .github/workflows/deploy-cudf-java-docs.yaml | 18 +- .github/workflows/deploy.yaml | 38 +- .github/workflows/pr.yaml | 16 + .github/zizmor.yml | 3 +- .gitignore | 19 +- .pre-commit-config.yaml | 18 +- .python-version | 1 + .ruby-version | 1 - CONTRIBUTING.md | 143 +- Gemfile | 5 - Gemfile.lock | 292 - Makefile | 34 + README.md | 62 +- _config.yml | 57 - _drafts/maintainers/artifacts.md | 33 - _drafts/maintainers/permissions.md | 30 - _drafts/maintainers/projectboards.md | 30 - _drafts/maintainers/readthedocs.md | 21 - _drafts/maintainers/structure.md | 31 - _includes/api-docs.html | 25 - _includes/gpu-labels-table-row.html | 12 - _includes/gpu-labels-table.html | 19 - _includes/head.html | 99 - _includes/nav.html | 59 - _layouts/default.html | 77 - _layouts/notice-index.html | 97 - _layouts/notice.html | 35 - api.md | 35 - assets/images/seaborn-logo.svg | 5216 ------ ci/check_style.sh | 2 +- ci/customization/customize_doc.py | 34 +- ci/customization/customize_docs_in_folder.sh | 2 +- ci/customization/lib_map.sh | 2 +- ci/download_from_s3.py | 83 + ci/download_from_s3.sh | 157 +- ci/generate-projects-to-versions.py | 18 +- ci/post-process.sh | 4 +- ci/update_symlinks.sh | 2 +- ci/upload_cudf_java_docs.sh | 2 +- extensions/__init__.py | 4 + extensions/rapids_docs.py | 499 + favicon.ico | Bin 358 -> 0 bytes index.md | 46 - maintainers/index.md | 47 - notices/feed.xml | 32 - notices/index.md | 20 - notices/rdn/index.md | 15 - notices/rgn/index.md | 15 - notices/rsn/index.md | 15 - platform-support/index.md | 60 - pyproject.toml | 44 + release_checklist.md | 7 - releases/index.md | 12 - releases/schedule.md | 66 - resources/index.md | 13 - scripts/compare_routes.py | 157 + scripts/migrate_from_jekyll.py | 137 + scripts/validate_site.py | 118 + 404.md => source/404.md | 9 +- source/SECURITY.md | 44 + {_data => source/_data}/docs.yml | 2 +- {_data => source/_data}/platform_support.yml | 0 .../_data}/previous_releases.json | 0 {_data => source/_data}/releases.json | 0 {_includes => source/_includes}/bokeh.html | 2 +- .../_includes}/datashader.html | 2 +- .../_includes}/holoviews.html | 2 +- {_includes => source/_includes}/hvplot.html | 2 +- {_includes => source/_includes}/plotly.html | 2 +- {_includes => source/_includes}/seaborn.html | 2 +- {_includes => source/_includes}/selector.html | 175 +- .../_includes}/viz-cdn-js-css.html | 46 + _redirects => source/_redirects | 0 source/_static/css/custom.css | 65 + source/_static/js/portal-analytics.js | 43 + source/_templates/layout.html | 9 + source/api.md | 26 + {assets => source/assets}/css/custom.css | 0 .../assets}/css/custom_nvidia.css | 0 .../assets}/css/just-the-docs.css | 24 +- .../bar_cudf_0.json | 0 .../bar_cudf_1.json | 0 .../bar_cudf_2.json | 0 .../bar_pandas_0.json | 0 .../bar_pandas_1.json | 0 .../bar_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../bar_cudf_0.json | 0 .../bar_cudf_1.json | 0 .../bar_cudf_2.json | 0 .../bar_pandas_0.json | 0 .../bar_pandas_1.json | 0 .../bar_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../bar_cudf_0.json | 0 .../bar_cudf_1.json | 0 .../bar_cudf_2.json | 0 .../bar_pandas_0.json | 0 .../bar_pandas_1.json | 0 .../bar_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../bar_cudf_0.json | 0 .../bar_cudf_1.json | 0 .../bar_cudf_2.json | 0 .../bar_pandas_0.json | 0 .../bar_pandas_1.json | 0 .../bar_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../bar_cudf_0.json | 0 .../bar_cudf_1.json | 0 .../bar_cudf_2.json | 0 .../bar_pandas_0.json | 0 .../bar_pandas_1.json | 0 .../bar_pandas_2.json | 0 .../line_cudf_0.json | 0 .../line_cudf_1.json | 0 .../line_cudf_2.json | 0 .../line_pandas_0.json | 0 .../line_pandas_1.json | 0 .../line_pandas_2.json | 0 .../points_cudf_0.json | 0 .../points_cudf_1.json | 0 .../points_cudf_2.json | 0 .../points_pandas_0.json | 0 .../points_pandas_1.json | 0 .../points_pandas_2.json | 0 .../assets}/images/bokeh-logo.svg | 0 .../assets}/images/clifford_interact.png | Bin .../assets}/images/cuxfilter-demo.gif | Bin .../images/datashader-census-rapids.png | Bin .../assets}/images/datashader-logo.png | Bin .../assets}/images/downloads-github.png | Bin .../assets}/images/downloads.png | Bin .../assets}/images/gapminders.png | Bin {assets => source/assets}/images/heatmap.png | Bin .../assets}/images/hexagon-layer.jpg | Bin .../assets}/images/hexbin_marginals.png | Bin .../assets}/images/holoviews-logo.png | Bin .../assets}/images/hvplot-logo.png | Bin .../assets}/images/label-checker/correct.png | Bin .../images/label-checker/do_not_merge.png | Bin .../images/label-checker/many_breaking.png | Bin .../assets}/images/label-checker/many_cat.png | Bin .../images/label-checker/missing_breaking.png | Bin .../images/label-checker/missing_cat.png | Bin .../label-checker/missing_cat_breaking.png | Bin .../images/latex_blackbody_radiation.png | Bin .../assets}/images/nightly_pipeline.png | Bin .../assets}/images/nodeRAPIDS-streaming.png | Bin .../assets}/images/nytaxi_hover.gif | Bin .../assets}/images/panel-logo.png | Bin .../assets}/images/plotly-dash.png | Bin .../assets}/images/plotly-logo.png | Bin .../assets}/images/pyDeck-logo.svg | 0 .../assets}/images/rapids_logo.png | Bin .../images/reproducing-ci/container.png | Bin .../assets}/images/reproducing-ci/prompts.png | Bin source/assets/images/seaborn-logo.svg | 5216 ++++++ .../images/telemetry/calculate_field.png | Bin .../field_type_and_value_options.png | Bin .../images/telemetry/filter_by_values.png | Bin .../telemetry/filter_by_values_with_var.png | Bin .../telemetry/grafana_variable_definition.png | Bin .../images/telemetry/mermaid-workflow.md | 0 .../images/telemetry/mermaid-workflow.png | Bin .../assets}/images/telemetry/panel_query.png | Bin .../assets}/images/workflow-ui.png | Bin {assets => source/assets}/js/custom.js | 0 {assets => source/assets}/js/just-the-docs.js | 0 {assets => source/assets}/js/search-data.json | 0 .../assets}/js/vendor/lunr.min.js | 0 source/conf.py | 81 + {contributing => source/contributing}/code.md | 27 +- .../contributing}/index.md | 23 +- .../contributing}/issues.md | 19 +- {contributing => source/contributing}/prs.md | 18 +- source/index.md | 90 + {install => source/install}/index.md | 125 +- {licenses => source/licenses}/CubinLinker.txt | 20 +- .../licenses}/cugraph-ops-EULA.txt | 762 +- .../maintainers}/datasets.md | 16 +- .../maintainers}/forward-merger.md | 30 +- source/maintainers/index.md | 23 + source/notices/index.md | 25 + source/notices/rdn/index.md | 6 + {_notices => source/notices}/rdn0001.md | 0 {_notices => source/notices}/rdn0002.md | 0 {_notices => source/notices}/rdn0003.md | 0 source/notices/rgn/index.md | 6 + {_notices => source/notices}/rgn0001.md | 0 {_notices => source/notices}/rgn0002.md | 0 {_notices => source/notices}/rgn0003.md | 2 +- {_notices => source/notices}/rgn0004.md | 0 {_notices => source/notices}/rgn0005.md | 0 {_notices => source/notices}/rgn0006.md | 2 +- {_notices => source/notices}/rgn0007.md | 0 {_notices => source/notices}/rgn0008.md | 2 +- {_notices => source/notices}/rgn0009.md | 2 +- {_notices => source/notices}/rgn0010.md | 0 {_notices => source/notices}/rgn0011.md | 0 {_notices => source/notices}/rgn0012.md | 0 {_notices => source/notices}/rgn0013.md | 0 {_notices => source/notices}/rgn0014.md | 0 {_notices => source/notices}/rgn0015.md | 0 {_notices => source/notices}/rgn0016.md | 78 +- {_notices => source/notices}/rgn0017.md | 74 +- {_notices => source/notices}/rgn0018.md | 74 +- {_notices => source/notices}/rgn0019.md | 0 {_notices => source/notices}/rgn0020.md | 74 +- {_notices => source/notices}/rgn0021.md | 74 +- {_notices => source/notices}/rgn0022.md | 0 {_notices => source/notices}/rgn0023.md | 0 {_notices => source/notices}/rgn0024.md | 0 {_notices => source/notices}/rgn0025.md | 0 {_notices => source/notices}/rgn0026.md | 0 {_notices => source/notices}/rgn0027.md | 0 {_notices => source/notices}/rgn0028.md | 0 {_notices => source/notices}/rgn0029.md | 0 {_notices => source/notices}/rgn0030.md | 0 source/notices/rsn/index.md | 6 + {_notices => source/notices}/rsn0001.md | 0 {_notices => source/notices}/rsn0002.md | 0 {_notices => source/notices}/rsn0003.md | 0 {_notices => source/notices}/rsn0004.md | 0 {_notices => source/notices}/rsn0005.md | 0 {_notices => source/notices}/rsn0006.md | 0 {_notices => source/notices}/rsn0007.md | 0 {_notices => source/notices}/rsn0008.md | 0 {_notices => source/notices}/rsn0009.md | 0 {_notices => source/notices}/rsn0010.md | 0 {_notices => source/notices}/rsn0011.md | 66 +- {_notices => source/notices}/rsn0012.md | 66 +- {_notices => source/notices}/rsn0013.md | 76 +- {_notices => source/notices}/rsn0014.md | 104 +- {_notices => source/notices}/rsn0015.md | 0 {_notices => source/notices}/rsn0016.md | 0 {_notices => source/notices}/rsn0017.md | 0 {_notices => source/notices}/rsn0018.md | 0 {_notices => source/notices}/rsn0019.md | 0 {_notices => source/notices}/rsn0020.md | 0 {_notices => source/notices}/rsn0021.md | 0 {_notices => source/notices}/rsn0022.md | 68 +- {_notices => source/notices}/rsn0023.md | 0 {_notices => source/notices}/rsn0024.md | 0 {_notices => source/notices}/rsn0025.md | 0 {_notices => source/notices}/rsn0026.md | 0 {_notices => source/notices}/rsn0027.md | 0 {_notices => source/notices}/rsn0028.md | 0 {_notices => source/notices}/rsn0029.md | 68 +- {_notices => source/notices}/rsn0030.md | 74 +- {_notices => source/notices}/rsn0031.md | 72 +- {_notices => source/notices}/rsn0032.md | 0 {_notices => source/notices}/rsn0033.md | 0 {_notices => source/notices}/rsn0034.md | 0 {_notices => source/notices}/rsn0035.md | 0 {_notices => source/notices}/rsn0036.md | 72 +- {_notices => source/notices}/rsn0037.md | 0 {_notices => source/notices}/rsn0038.md | 0 {_notices => source/notices}/rsn0039.md | 0 {_notices => source/notices}/rsn0040.md | 70 +- {_notices => source/notices}/rsn0041.md | 70 +- {_notices => source/notices}/rsn0042.md | 0 {_notices => source/notices}/rsn0043.md | 0 {_notices => source/notices}/rsn0044.md | 120 +- {_notices => source/notices}/rsn0045.md | 70 +- {_notices => source/notices}/rsn0046.md | 70 +- {_notices => source/notices}/rsn0047.md | 162 +- {_notices => source/notices}/rsn0048.md | 0 {_notices => source/notices}/rsn0049.md | 0 {_notices => source/notices}/rsn0050.md | 114 +- {_notices => source/notices}/rsn0051.md | 0 {_notices => source/notices}/rsn0052.md | 0 {_notices => source/notices}/rsn0053.md | 0 {_notices => source/notices}/rsn0054.md | 0 {_notices => source/notices}/rsn0055.md | 0 {_notices => source/notices}/rsn0056.md | 0 {_notices => source/notices}/rsn0057.md | 0 {_notices => source/notices}/rsn0058.md | 68 +- {_notices => source/notices}/rsn0059.md | 68 +- {_notices => source/notices}/rsn0060.md | 182 +- {_notices => source/notices}/rsn0061.md | 0 source/platform-support/index.md | 16 + {releases => source/releases}/hotfix.md | 32 +- source/releases/index.md | 11 + {releases => source/releases}/process.md | 40 +- source/releases/schedule.md | 20 + .../resources}/auto-merger.md | 16 +- .../resources}/burn-down-guide.md | 13 +- {resources => source/resources}/changelog.md | 17 +- {resources => source/resources}/conduct.md | 17 +- .../resources}/github-actions.md | 25 +- source/resources/index.md | 19 + .../resources}/label-checker.md | 20 +- .../resources}/merge-barriers.md | 20 +- .../resources}/recently-updated.md | 20 +- .../resources}/reproducing-ci.md | 25 +- {resources => source/resources}/telemetry.md | 50 +- {resources => source/resources}/versions.md | 17 +- source/user-guide/index.md | 46 + .../visualization}/index.md | 76 +- tests/fixtures/jekyll_manifest.json | 14123 ++++++++++++++++ tests/test_customization.py | 39 + tests/test_rendering.py | 59 + user-guide/index.md | 67 - uv.lock | 873 + 362 files changed, 23914 insertions(+), 8900 deletions(-) delete mode 100644 .devcontainer/devcontainer.json create mode 100644 .python-version delete mode 100644 .ruby-version delete mode 100644 Gemfile delete mode 100644 Gemfile.lock create mode 100644 Makefile delete mode 100644 _config.yml delete mode 100644 _drafts/maintainers/artifacts.md delete mode 100644 _drafts/maintainers/permissions.md delete mode 100644 _drafts/maintainers/projectboards.md delete mode 100644 _drafts/maintainers/readthedocs.md delete mode 100644 _drafts/maintainers/structure.md delete mode 100644 _includes/api-docs.html delete mode 100644 _includes/gpu-labels-table-row.html delete mode 100644 _includes/gpu-labels-table.html delete mode 100644 _includes/head.html delete mode 100644 _includes/nav.html delete mode 100644 _layouts/default.html delete mode 100644 _layouts/notice-index.html delete mode 100644 _layouts/notice.html delete mode 100644 api.md delete mode 100644 assets/images/seaborn-logo.svg create mode 100644 ci/download_from_s3.py mode change 100755 => 100644 ci/download_from_s3.sh create mode 100644 extensions/__init__.py create mode 100644 extensions/rapids_docs.py delete mode 100644 favicon.ico delete mode 100644 index.md delete mode 100644 maintainers/index.md delete mode 100644 notices/feed.xml delete mode 100644 notices/index.md delete mode 100644 notices/rdn/index.md delete mode 100644 notices/rgn/index.md delete mode 100644 notices/rsn/index.md delete mode 100644 platform-support/index.md create mode 100644 pyproject.toml delete mode 100644 release_checklist.md delete mode 100644 releases/index.md delete mode 100644 releases/schedule.md delete mode 100644 resources/index.md create mode 100644 scripts/compare_routes.py create mode 100644 scripts/migrate_from_jekyll.py create mode 100644 scripts/validate_site.py rename 404.md => source/404.md (67%) create mode 100644 source/SECURITY.md rename {_data => source/_data}/docs.yml (99%) rename {_data => source/_data}/platform_support.yml (100%) rename {_data => source/_data}/previous_releases.json (100%) rename {_data => source/_data}/releases.json (100%) rename {_includes => source/_includes}/bokeh.html (99%) rename {_includes => source/_includes}/datashader.html (99%) rename {_includes => source/_includes}/holoviews.html (99%) rename {_includes => source/_includes}/hvplot.html (99%) rename {_includes => source/_includes}/plotly.html (99%) rename {_includes => source/_includes}/seaborn.html (99%) rename {_includes => source/_includes}/selector.html (90%) rename {_includes => source/_includes}/viz-cdn-js-css.html (69%) rename _redirects => source/_redirects (100%) create mode 100644 source/_static/css/custom.css create mode 100644 source/_static/js/portal-analytics.js create mode 100644 source/_templates/layout.html create mode 100644 source/api.md rename {assets => source/assets}/css/custom.css (100%) rename {assets => source/assets}/css/custom_nvidia.css (100%) rename {assets => source/assets}/css/just-the-docs.css (99%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_cudf_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_cudf_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_cudf_2.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_pandas_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_pandas_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/bar_pandas_2.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_cudf_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_cudf_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_cudf_2.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_pandas_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_pandas_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/line_pandas_2.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_cudf_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_cudf_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_cudf_2.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_pandas_0.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_pandas_1.json (100%) rename {assets => source/assets}/data/bokeh-JSON_362df7191b614a1bb145178166a0e20e/points_pandas_2.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_cudf_0.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_cudf_1.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_cudf_2.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_pandas_0.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_pandas_1.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/line_pandas_2.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_cudf_0.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_cudf_1.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_cudf_2.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_pandas_0.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_pandas_1.json (100%) rename {assets => source/assets}/data/datashader-JSON_3e5f10d466b14980b1d56e10c837e89b/points_pandas_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_cudf_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_cudf_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_cudf_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_pandas_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_pandas_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/bar_pandas_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_cudf_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_cudf_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_cudf_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_pandas_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_pandas_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/line_pandas_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_cudf_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_cudf_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_cudf_2.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_pandas_0.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_pandas_1.json (100%) rename {assets => source/assets}/data/holoviews-JSON_e4571a178971432dab4dcadfe8920f69/points_pandas_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_cudf_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_cudf_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_cudf_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_pandas_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_pandas_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/bar_pandas_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_cudf_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_cudf_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_cudf_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_pandas_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_pandas_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/line_pandas_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_cudf_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_cudf_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_cudf_2.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_pandas_0.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_pandas_1.json (100%) rename {assets => source/assets}/data/hvplot-JSON_9066d597776646c6af4c1870d7c0c2cd/points_pandas_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_cudf_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_cudf_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_cudf_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_pandas_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_pandas_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/bar_pandas_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_cudf_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_cudf_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_cudf_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_pandas_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_pandas_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/line_pandas_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_cudf_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_cudf_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_cudf_2.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_pandas_0.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_pandas_1.json (100%) rename {assets => source/assets}/data/plotly-JSON_541c64c871f546008e0a79ea590adc9f/points_pandas_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_cudf_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_cudf_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_cudf_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_pandas_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_pandas_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/bar_pandas_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_cudf_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_cudf_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_cudf_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_pandas_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_pandas_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/line_pandas_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_cudf_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_cudf_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_cudf_2.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_pandas_0.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_pandas_1.json (100%) rename {assets => source/assets}/data/seaborn-JSON_b94cd7e206d34ec7949514b993d2d19b/points_pandas_2.json (100%) rename {assets => source/assets}/images/bokeh-logo.svg (100%) rename {assets => source/assets}/images/clifford_interact.png (100%) rename {assets => source/assets}/images/cuxfilter-demo.gif (100%) rename {assets => source/assets}/images/datashader-census-rapids.png (100%) rename {assets => source/assets}/images/datashader-logo.png (100%) rename {assets => source/assets}/images/downloads-github.png (100%) rename {assets => source/assets}/images/downloads.png (100%) rename {assets => source/assets}/images/gapminders.png (100%) rename {assets => source/assets}/images/heatmap.png (100%) rename {assets => source/assets}/images/hexagon-layer.jpg (100%) rename {assets => source/assets}/images/hexbin_marginals.png (100%) rename {assets => source/assets}/images/holoviews-logo.png (100%) rename {assets => source/assets}/images/hvplot-logo.png (100%) rename {assets => source/assets}/images/label-checker/correct.png (100%) rename {assets => source/assets}/images/label-checker/do_not_merge.png (100%) rename {assets => source/assets}/images/label-checker/many_breaking.png (100%) rename {assets => source/assets}/images/label-checker/many_cat.png (100%) rename {assets => source/assets}/images/label-checker/missing_breaking.png (100%) rename {assets => source/assets}/images/label-checker/missing_cat.png (100%) rename {assets => source/assets}/images/label-checker/missing_cat_breaking.png (100%) rename {assets => source/assets}/images/latex_blackbody_radiation.png (100%) rename {assets => source/assets}/images/nightly_pipeline.png (100%) rename {assets => source/assets}/images/nodeRAPIDS-streaming.png (100%) rename {assets => source/assets}/images/nytaxi_hover.gif (100%) rename {assets => source/assets}/images/panel-logo.png (100%) rename {assets => source/assets}/images/plotly-dash.png (100%) rename {assets => source/assets}/images/plotly-logo.png (100%) rename {assets => source/assets}/images/pyDeck-logo.svg (100%) rename {assets => source/assets}/images/rapids_logo.png (100%) rename {assets => source/assets}/images/reproducing-ci/container.png (100%) rename {assets => source/assets}/images/reproducing-ci/prompts.png (100%) create mode 100644 source/assets/images/seaborn-logo.svg rename {assets => source/assets}/images/telemetry/calculate_field.png (100%) rename {assets => source/assets}/images/telemetry/field_type_and_value_options.png (100%) rename {assets => source/assets}/images/telemetry/filter_by_values.png (100%) rename {assets => source/assets}/images/telemetry/filter_by_values_with_var.png (100%) rename {assets => source/assets}/images/telemetry/grafana_variable_definition.png (100%) rename {assets => source/assets}/images/telemetry/mermaid-workflow.md (100%) rename {assets => source/assets}/images/telemetry/mermaid-workflow.png (100%) rename {assets => source/assets}/images/telemetry/panel_query.png (100%) rename {assets => source/assets}/images/workflow-ui.png (100%) rename {assets => source/assets}/js/custom.js (100%) rename {assets => source/assets}/js/just-the-docs.js (100%) rename {assets => source/assets}/js/search-data.json (100%) rename {assets => source/assets}/js/vendor/lunr.min.js (100%) create mode 100644 source/conf.py rename {contributing => source/contributing}/code.md (80%) rename {contributing => source/contributing}/index.md (82%) rename {contributing => source/contributing}/issues.md (89%) rename {contributing => source/contributing}/prs.md (89%) create mode 100644 source/index.md rename {install => source/install}/index.md (78%) rename {licenses => source/licenses}/CubinLinker.txt (96%) rename {licenses => source/licenses}/cugraph-ops-EULA.txt (98%) rename {maintainers => source/maintainers}/datasets.md (96%) rename {maintainers => source/maintainers}/forward-merger.md (66%) create mode 100644 source/maintainers/index.md create mode 100644 source/notices/index.md create mode 100644 source/notices/rdn/index.md rename {_notices => source/notices}/rdn0001.md (100%) rename {_notices => source/notices}/rdn0002.md (100%) rename {_notices => source/notices}/rdn0003.md (100%) create mode 100644 source/notices/rgn/index.md rename {_notices => source/notices}/rgn0001.md (100%) rename {_notices => source/notices}/rgn0002.md (100%) rename {_notices => source/notices}/rgn0003.md (94%) rename {_notices => source/notices}/rgn0004.md (100%) rename {_notices => source/notices}/rgn0005.md (100%) rename {_notices => source/notices}/rgn0006.md (92%) rename {_notices => source/notices}/rgn0007.md (100%) rename {_notices => source/notices}/rgn0008.md (93%) rename {_notices => source/notices}/rgn0009.md (92%) rename {_notices => source/notices}/rgn0010.md (100%) rename {_notices => source/notices}/rgn0011.md (100%) rename {_notices => source/notices}/rgn0012.md (100%) rename {_notices => source/notices}/rgn0013.md (100%) rename {_notices => source/notices}/rgn0014.md (100%) rename {_notices => source/notices}/rgn0015.md (100%) rename {_notices => source/notices}/rgn0016.md (97%) rename {_notices => source/notices}/rgn0017.md (97%) rename {_notices => source/notices}/rgn0018.md (96%) rename {_notices => source/notices}/rgn0019.md (100%) rename {_notices => source/notices}/rgn0020.md (96%) rename {_notices => source/notices}/rgn0021.md (96%) rename {_notices => source/notices}/rgn0022.md (100%) rename {_notices => source/notices}/rgn0023.md (100%) rename {_notices => source/notices}/rgn0024.md (100%) rename {_notices => source/notices}/rgn0025.md (100%) rename {_notices => source/notices}/rgn0026.md (100%) rename {_notices => source/notices}/rgn0027.md (100%) rename {_notices => source/notices}/rgn0028.md (100%) rename {_notices => source/notices}/rgn0029.md (100%) rename {_notices => source/notices}/rgn0030.md (100%) create mode 100644 source/notices/rsn/index.md rename {_notices => source/notices}/rsn0001.md (100%) rename {_notices => source/notices}/rsn0002.md (100%) rename {_notices => source/notices}/rsn0003.md (100%) rename {_notices => source/notices}/rsn0004.md (100%) rename {_notices => source/notices}/rsn0005.md (100%) rename {_notices => source/notices}/rsn0006.md (100%) rename {_notices => source/notices}/rsn0007.md (100%) rename {_notices => source/notices}/rsn0008.md (100%) rename {_notices => source/notices}/rsn0009.md (100%) rename {_notices => source/notices}/rsn0010.md (100%) rename {_notices => source/notices}/rsn0011.md (96%) rename {_notices => source/notices}/rsn0012.md (96%) rename {_notices => source/notices}/rsn0013.md (96%) rename {_notices => source/notices}/rsn0014.md (96%) rename {_notices => source/notices}/rsn0015.md (100%) rename {_notices => source/notices}/rsn0016.md (100%) rename {_notices => source/notices}/rsn0017.md (100%) rename {_notices => source/notices}/rsn0018.md (100%) rename {_notices => source/notices}/rsn0019.md (100%) rename {_notices => source/notices}/rsn0020.md (100%) rename {_notices => source/notices}/rsn0021.md (100%) rename {_notices => source/notices}/rsn0022.md (97%) rename {_notices => source/notices}/rsn0023.md (100%) rename {_notices => source/notices}/rsn0024.md (100%) rename {_notices => source/notices}/rsn0025.md (100%) rename {_notices => source/notices}/rsn0026.md (100%) rename {_notices => source/notices}/rsn0027.md (100%) rename {_notices => source/notices}/rsn0028.md (100%) rename {_notices => source/notices}/rsn0029.md (96%) rename {_notices => source/notices}/rsn0030.md (97%) rename {_notices => source/notices}/rsn0031.md (97%) rename {_notices => source/notices}/rsn0032.md (100%) rename {_notices => source/notices}/rsn0033.md (100%) rename {_notices => source/notices}/rsn0034.md (100%) rename {_notices => source/notices}/rsn0035.md (100%) rename {_notices => source/notices}/rsn0036.md (96%) rename {_notices => source/notices}/rsn0037.md (100%) rename {_notices => source/notices}/rsn0038.md (100%) rename {_notices => source/notices}/rsn0039.md (100%) rename {_notices => source/notices}/rsn0040.md (97%) rename {_notices => source/notices}/rsn0041.md (97%) rename {_notices => source/notices}/rsn0042.md (100%) rename {_notices => source/notices}/rsn0043.md (100%) rename {_notices => source/notices}/rsn0044.md (97%) rename {_notices => source/notices}/rsn0045.md (97%) rename {_notices => source/notices}/rsn0046.md (97%) rename {_notices => source/notices}/rsn0047.md (97%) rename {_notices => source/notices}/rsn0048.md (100%) rename {_notices => source/notices}/rsn0049.md (100%) rename {_notices => source/notices}/rsn0050.md (98%) rename {_notices => source/notices}/rsn0051.md (100%) rename {_notices => source/notices}/rsn0052.md (100%) rename {_notices => source/notices}/rsn0053.md (100%) rename {_notices => source/notices}/rsn0054.md (100%) rename {_notices => source/notices}/rsn0055.md (100%) rename {_notices => source/notices}/rsn0056.md (100%) rename {_notices => source/notices}/rsn0057.md (100%) rename {_notices => source/notices}/rsn0058.md (96%) rename {_notices => source/notices}/rsn0059.md (96%) rename {_notices => source/notices}/rsn0060.md (97%) rename {_notices => source/notices}/rsn0061.md (100%) create mode 100644 source/platform-support/index.md rename {releases => source/releases}/hotfix.md (85%) create mode 100644 source/releases/index.md rename {releases => source/releases}/process.md (86%) create mode 100644 source/releases/schedule.md rename {resources => source/resources}/auto-merger.md (88%) rename {resources => source/resources}/burn-down-guide.md (94%) rename {resources => source/resources}/changelog.md (85%) rename {resources => source/resources}/conduct.md (94%) rename {resources => source/resources}/github-actions.md (98%) create mode 100644 source/resources/index.md rename {resources => source/resources}/label-checker.md (90%) rename {resources => source/resources}/merge-barriers.md (83%) rename {resources => source/resources}/recently-updated.md (88%) rename {resources => source/resources}/reproducing-ci.md (95%) rename {resources => source/resources}/telemetry.md (97%) rename {resources => source/resources}/versions.md (93%) create mode 100644 source/user-guide/index.md rename {visualization => source/visualization}/index.md (76%) create mode 100644 tests/fixtures/jekyll_manifest.json create mode 100644 tests/test_customization.py create mode 100644 tests/test_rendering.py delete mode 100644 user-guide/index.md create mode 100644 uv.lock diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index d049037831a..00000000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,24 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/jekyll -{ - "name": "Jekyll", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/jekyll:2-bullseye", - "features": { - } - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Uncomment the next line to run commands after the container is created. - // "postCreateCommand": "jekyll --version" - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/.github/workflows/deploy-cudf-java-docs.yaml b/.github/workflows/deploy-cudf-java-docs.yaml index 287fb0e5453..adaeaa1c5a7 100644 --- a/.github/workflows/deploy-cudf-java-docs.yaml +++ b/.github/workflows/deploy-cudf-java-docs.yaml @@ -1,4 +1,5 @@ name: Deploy cudf-java docs + on: workflow_dispatch: inputs: @@ -9,16 +10,20 @@ on: version: description: "Version being released. Format: YY.MM or YY.MM.P e.g 24.08 or 24.08.1" required: true + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + defaults: run: shell: bash + permissions: id-token: write contents: write pull-requests: write + jobs: deploy-cudf-java-docs: runs-on: ubuntu-latest @@ -27,16 +32,17 @@ jobs: uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 #v5.1.1 + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} aws-region: ${{ vars.AWS_REGION }} - role-duration-seconds: 7200 # 2h + role-duration-seconds: 7200 - name: Upload cudf-java docs to S3 env: VERSION: ${{ inputs.version }} run: ci/upload_cudf_java_docs.sh "${VERSION}" - - name: Update stable value for cudf-java in docs.yml and projects-to-versions.json + - name: Update stable value for cudf-java env: NEW_STABLE_VALUE: ${{ inputs.new_stable_value }} VERSION: ${{ inputs.version }} @@ -46,12 +52,12 @@ jobs: exit 1 fi - sed -i '/cudf-java:/,/stable:/s/stable: .*/stable: '"$NEW_STABLE_VALUE"'/' _data/docs.yml - echo "Updated stable value for cudf-java to $NEW_STABLE_VALUE in _data/docs.yml" + sed -i '/cudf-java:/,/stable:/s/stable: .*/stable: '"$NEW_STABLE_VALUE"'/' source/_data/docs.yml + echo "Updated stable value for cudf-java to $NEW_STABLE_VALUE in source/_data/docs.yml" jq --arg version "$VERSION" '."cudf-java".stable = $version' ci/customization/projects-to-versions.json > tmp.json \ && mv tmp.json ci/customization/projects-to-versions.json - echo "Updated cudf-java stable version to $VERSION in projects-to-versions.json" + echo "Updated stable version for cudf-java to $VERSION" - name: Create Pull Request uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1 with: diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 771d76cc86b..66f2ee9b035 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -1,4 +1,5 @@ name: Deploy site + on: schedule: - cron: "0 9 * * *" @@ -6,46 +7,53 @@ on: push: branches: - main + - "pull-request/[0-9]+" + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true + defaults: run: shell: bash + permissions: id-token: write contents: read + jobs: build: name: Build (and deploy) runs-on: ubuntu-latest steps: - - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false - # this step uses the `.ruby-version` file - - uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0 - - name: Build Jekyll Site - run: | - bundle install - bundle exec jekyll build - - uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 #v5.1.1 + - name: Set up uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + enable-cache: true + - name: Install dependencies + run: uv sync --locked + - name: Build and validate portal + run: make check + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1 with: role-to-assume: ${{ vars.AWS_ROLE_ARN }} aws-region: ${{ vars.AWS_REGION }} - role-duration-seconds: 7200 # 2h - - name: Fetch doc files from S3 - run: ci/download_from_s3.sh - - name: Post-process docs - run: ci/post-process.sh + role-duration-seconds: 7200 + - name: Assemble complete documentation site + run: make assemble - name: Deploy site env: NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_API_TOKEN }} NETLIFY_SITE_ID: ${{ secrets.NETLIFY_DOCS_SITE_ID }} - # TODO: use official netlify-cli pkg after https://github.com/netlify/cli/issues/1809 - # is resolved and deployed. run: | + # TODO: use the official netlify-cli package after + # https://github.com/netlify/cli/issues/1809 is resolved and deployed. npm install --global --force @aschmidt8/netlify-cli ARGS="" diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 6cde6c075a7..105e81bb23f 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -20,3 +20,19 @@ jobs: with: persist-credentials: false - uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1 + + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Set up uv + uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 + with: + enable-cache: true + - name: Install dependencies + run: uv sync --locked + - name: Build and validate portal + run: make check diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 1b6ea1e53ff..e5ec29cf9a0 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -2,8 +2,7 @@ rules: unpinned-uses: config: policies: - # We require SHA-pinning for all workflows and actions _except_ for those from - # rapidsai/shared-workflows and rapidsai/shared-actions + # Require SHA-pinning except for RAPIDS-managed reusable actions and workflows. "rapidsai/shared-workflows/*": any "rapidsai/shared-actions/*": any "*": hash-pin diff --git a/.gitignore b/.gitignore index d2e9d00015e..3ffa7835d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,7 @@ -_site/ -_sass/ -.sass-cache/ --P/ -.vscode -lib_map.json -.DS_Store -files-to-customize.txt -.jekyll-cache -.jekyll-metadata -.netlify +/.ruff_cache/ +/.venv/ +/_site/ +/files-to-customize.txt +/ci/customization/lib_map.json __pycache__/ -node_modules -rapids-docs-env/ +*.pyc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 404742e42c5..a5adf03e5fa 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,4 +1,4 @@ -# Copyright (c) 2024-2025, NVIDIA CORPORATION. +# Copyright (c) 2024-2026, NVIDIA CORPORATION. ci: autofix_commit_msg: "[pre-commit.ci] auto code formatting" @@ -15,14 +15,14 @@ repos: - id: trailing-whitespace exclude: | (?x)^( - assets/.*| - licenses/.* + source/assets/.*| + source/licenses/.* ) - id: end-of-file-fixer exclude: | (?x)^( - assets/.*| - licenses/.* + source/assets/.*| + source/licenses/.* ) - id: mixed-line-ending - id: debug-statements @@ -31,7 +31,7 @@ repos: - id: check-json exclude: | (?x)^( - assets/.*| + source/assets/.*| .devcontainer/devcontainer.json ) - id: check-yaml @@ -50,8 +50,8 @@ repos: - id: fix-smartquotes exclude: | (?x)^( - assets/.*| - licenses/.* + source/assets/.*| + source/licenses/.* ) - repo: https://github.com/codespell-project/codespell rev: v2.4.2 @@ -60,7 +60,7 @@ repos: types: [markdown] exclude: | (?x)^( - resources/conduct\.md + source/resources/conduct\.md )$ - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.15.9 diff --git a/.python-version b/.python-version new file mode 100644 index 00000000000..e4fba218358 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/.ruby-version b/.ruby-version deleted file mode 100644 index 15a27998172..00000000000 --- a/.ruby-version +++ /dev/null @@ -1 +0,0 @@ -3.3.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 424c77625f5..639ca1c7ce3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,148 +2,87 @@ ## Environment setup -- Install Ruby -- Install Bundler -- Checkout repo -- Run `bundle install` in checked out repo - -## Development - -```sh -bundle exec jekyll serve -``` - -### Dev Containers - -If you're using [VSCode](https://code.visualstudio.com/) you can use the jekyll Dev Container. - -- This project contains a devcontainer config: - - If prompted with "Folder contains a Dev Container configuration file." select "Reopen in Container" - - Alternatively select "Dev Containers: Reopen in Container" from the command palette -- Run `bundle exec jekyll serve` - -### Local Docker development - -Alternatively, you can use the [jekyll-docker](https://github.com/envygeeks/jekyll-docker) container to build and serve the `docs` site locally: - -```sh -docker run --rm \ - --volume="$PWD:/srv/jekyll" \ - --publish [::1]:4000:4000 \ - jekyll/jekyll \ - bash -c 'rm -rf ./_site && jekyll serve' -``` - -### Local macOS development - -Upgrade to a new-enough ruby and install `jekyll`, like following https://jekyllrb.com/docs/installation/macos/ +Install [uv](https://docs.astral.sh/uv/), check out the repository, and install +the locked development environment: ```shell -# (one-time) get ruby env-management stuff -brew install chruby ruby-install - -# (one time) update to newer ruby -ruby-install ruby 3.4.1 - -source $(brew --prefix)/opt/chruby/share/chruby/chruby.sh -source $(brew --prefix)/opt/chruby/share/chruby/auto.sh -chruby ruby-3.4.1 -ruby -v - -# (one time) install Bundler -gem install bundler - -# install everything else the project needs -bundle install +uv sync --locked ``` -Build the site (this populates the `_site/` folder) - -```shell -bundle exec jekyll build --verbose -``` - -At this point, the API documentation will not be populated. +## Development -To test those and the post-processing that happens on them, get read-only AWS credentials for the relevant resources -and put them in a profile called `[rapids-docs]` in your AWS CLI configuration. +Build the portal and serve the rendered site at : ```shell -export AWS_DEFAULT_PROFILE="rapids-docs" -ci/download_from_s3.sh +make html +make serve ``` -At this point, the site is now built and the API documentation has been downloaded. -Next, some post-processing needs to be done to point links like `/stable` (including those in drop-down selectors) -to the appropriate documentation files. - -Those steps include a `pip install`, so create and active a Python virtual environment first. +Pass a different port when needed: ```shell -python -m venv rapids-docs-env -source ./rapids-docs-env/bin/activate +make serve PORT=8080 ``` -Then run the post-processing. +Run the complete credential-free validation suite before submitting a change: ```shell -ci/post-process.sh +make check ``` -At this point, you should be able to view the site locally with a pretty similar experience to what's hosted in deployments. +This runs formatting and lint checks, unit tests, a strict Sphinx build, and +route and rendered-content validation. -`jekyll serve` cleans and re-generates the `_site/` folder, but it also does some other bundling and packaging that's needed for links and formatting -to work correctly. - -First, back up the `api/` directory: - -```shell -BACKUP_DIR=$(mktemp -d) -cp -avR ./_site/api "${BACKUP_DIR}" -``` +## Full documentation assembly -Then serve the site. +The portal-only build does not include the versioned API documentation or the +deployment documentation. To test the complete site and its post-processing, +obtain read-only AWS credentials for the documentation bucket and configure an +AWS CLI profile named `rapids-docs`. Then run: ```shell -bundle exec jekyll serve +AWS_PROFILE=rapids-docs make full ``` -Once it's up, copy all the `api/` files back in. +This builds the Sphinx portal into `_site`, downloads the imported +documentation, creates the stable, latest, nightly, and legacy aliases, adds +the RAPIDS library and version selectors, and validates the assembled site. +Serve the result without rebuilding it with: ```shell -cp -avR "${BACKUP_DIR}/api" _site -``` - -`jekyll serve` should automatically pick up the changes. - -In a browser, navigate to the URL shown in the `jekyll serve` output (probably something like `http://127.0.0.1:4000/`) to see the rendered docs. - -If the hot reloading in `jekyll serve` again deletes all the files in `api/`, just copy them in again. - -```shell -cp -avR "${BACKUP_DIR}/api" _site +make serve ``` ## PR submissions -Once you have code changes, submit a PR to the docs site. Netlify will generate -a preview of your changes and the team will review. +Submit changes as a pull request to `rapidsai/docs`. The RAPIDS copy-PR bot +copies the pull request head to a `pull-request/` branch in the upstream +repository. CI validates that branch, assembles the complete documentation +site, and creates a non-production Netlify preview for review. ## Developer Certificate of Origin -All contributions to this project must be accompanied by a sign-off indicating that the contribution is made pursuant to the Developer Certificate of Origin (DCO). This is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. +All contributions to this project must be accompanied by a sign-off indicating +that the contribution is made pursuant to the Developer Certificate of Origin +(DCO). This is a lightweight way for contributors to certify that they wrote +or otherwise have the right to submit the code they are contributing to the +project. -The DCO is a simple statement that you, as a contributor, have the legal right to make the contribution. To certify your adherence to the DCO, you must sign off on your commits. This is done by adding a `Signed-off-by` line to your commit messages: +The DCO is a simple statement that you, as a contributor, have the legal right +to make the contribution. To certify your adherence to the DCO, you must sign +off on your commits. This is done by adding a `Signed-off-by` line to your +commit messages: -``` +```text Signed-off-by: Random J Developer ``` You can do this automatically with `git commit -s`. -Here is the full text of the DCO, which you can also find at : +Here is the full text of the DCO, which you can also find at +: -``` +```text Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 8197051a907..00000000000 --- a/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -source 'https://rubygems.org' -gem 'github-pages', group: :jekyll_plugins -gem "just-the-docs", "= 0.3.3" - -gem "webrick", "~> 1.8" diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index b60165eb87f..00000000000 --- a/Gemfile.lock +++ /dev/null @@ -1,292 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - activesupport (7.2.3.1) - base64 - benchmark (>= 0.3) - bigdecimal - concurrent-ruby (~> 1.0, >= 1.3.1) - connection_pool (>= 2.2.5) - drb - i18n (>= 1.6, < 2) - logger (>= 1.4.2) - minitest (>= 5.1, < 6) - securerandom (>= 0.3) - tzinfo (~> 2.0, >= 2.0.5) - addressable (2.9.0) - public_suffix (>= 2.0.2, < 8.0) - base64 (0.3.0) - benchmark (0.5.0) - bigdecimal (4.0.1) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - colorator (1.1.0) - commonmarker (0.23.10) - concurrent-ruby (1.3.7) - connection_pool (3.0.2) - csv (3.3.0) - dnsruby (1.72.2) - simpleidn (~> 0.2.1) - drb (2.2.3) - em-websocket (0.5.3) - eventmachine (>= 0.12.9) - http_parser.rb (~> 0) - ethon (0.16.0) - ffi (>= 1.15.0) - eventmachine (1.2.7) - execjs (2.9.1) - faraday (2.14.3) - faraday-net_http (>= 2.0, < 3.5) - json - logger - faraday-net_http (3.4.4) - net-http (~> 0.5) - ffi (1.17.0) - ffi (1.17.0-x86_64-linux-gnu) - forwardable-extended (2.6.0) - gemoji (4.1.0) - github-pages (232) - github-pages-health-check (= 1.18.2) - jekyll (= 3.10.0) - jekyll-avatar (= 0.8.0) - jekyll-coffeescript (= 1.2.2) - jekyll-commonmark-ghpages (= 0.5.1) - jekyll-default-layout (= 0.1.5) - jekyll-feed (= 0.17.0) - jekyll-gist (= 1.5.0) - jekyll-github-metadata (= 2.16.1) - jekyll-include-cache (= 0.2.1) - jekyll-mentions (= 1.6.0) - jekyll-optional-front-matter (= 0.3.2) - jekyll-paginate (= 1.1.0) - jekyll-readme-index (= 0.3.0) - jekyll-redirect-from (= 0.16.0) - jekyll-relative-links (= 0.6.1) - jekyll-remote-theme (= 0.4.3) - jekyll-sass-converter (= 1.5.2) - jekyll-seo-tag (= 2.8.0) - jekyll-sitemap (= 1.4.0) - jekyll-swiss (= 1.0.0) - jekyll-theme-architect (= 0.2.0) - jekyll-theme-cayman (= 0.2.0) - jekyll-theme-dinky (= 0.2.0) - jekyll-theme-hacker (= 0.2.0) - jekyll-theme-leap-day (= 0.2.0) - jekyll-theme-merlot (= 0.2.0) - jekyll-theme-midnight (= 0.2.0) - jekyll-theme-minimal (= 0.2.0) - jekyll-theme-modernist (= 0.2.0) - jekyll-theme-primer (= 0.6.0) - jekyll-theme-slate (= 0.2.0) - jekyll-theme-tactile (= 0.2.0) - jekyll-theme-time-machine (= 0.2.0) - jekyll-titles-from-headings (= 0.5.3) - jemoji (= 0.13.0) - kramdown (= 2.4.0) - kramdown-parser-gfm (= 1.1.0) - liquid (= 4.0.4) - mercenary (~> 0.3) - minima (= 2.5.1) - nokogiri (>= 1.16.2, < 2.0) - rouge (= 3.30.0) - terminal-table (~> 1.4) - webrick (~> 1.8) - github-pages-health-check (1.18.2) - addressable (~> 2.3) - dnsruby (~> 1.60) - octokit (>= 4, < 8) - public_suffix (>= 3.0, < 6.0) - typhoeus (~> 1.3) - html-pipeline (2.14.3) - activesupport (>= 2) - nokogiri (>= 1.4) - http_parser.rb (0.8.0) - i18n (1.14.8) - concurrent-ruby (~> 1.0) - jekyll (3.10.0) - addressable (~> 2.4) - colorator (~> 1.0) - csv (~> 3.0) - em-websocket (~> 0.5) - i18n (>= 0.7, < 2) - jekyll-sass-converter (~> 1.0) - jekyll-watch (~> 2.0) - kramdown (>= 1.17, < 3) - liquid (~> 4.0) - mercenary (~> 0.3.3) - pathutil (~> 0.9) - rouge (>= 1.7, < 4) - safe_yaml (~> 1.0) - webrick (>= 1.0) - jekyll-avatar (0.8.0) - jekyll (>= 3.0, < 5.0) - jekyll-coffeescript (1.2.2) - coffee-script (~> 2.2) - coffee-script-source (~> 1.12) - jekyll-commonmark (1.4.0) - commonmarker (~> 0.22) - jekyll-commonmark-ghpages (0.5.1) - commonmarker (>= 0.23.7, < 1.1.0) - jekyll (>= 3.9, < 4.0) - jekyll-commonmark (~> 1.4.0) - rouge (>= 2.0, < 5.0) - jekyll-default-layout (0.1.5) - jekyll (>= 3.0, < 5.0) - jekyll-feed (0.17.0) - jekyll (>= 3.7, < 5.0) - jekyll-gist (1.5.0) - octokit (~> 4.2) - jekyll-github-metadata (2.16.1) - jekyll (>= 3.4, < 5.0) - octokit (>= 4, < 7, != 4.4.0) - jekyll-include-cache (0.2.1) - jekyll (>= 3.7, < 5.0) - jekyll-mentions (1.6.0) - html-pipeline (~> 2.3) - jekyll (>= 3.7, < 5.0) - jekyll-optional-front-matter (0.3.2) - jekyll (>= 3.0, < 5.0) - jekyll-paginate (1.1.0) - jekyll-readme-index (0.3.0) - jekyll (>= 3.0, < 5.0) - jekyll-redirect-from (0.16.0) - jekyll (>= 3.3, < 5.0) - jekyll-relative-links (0.6.1) - jekyll (>= 3.3, < 5.0) - jekyll-remote-theme (0.4.3) - addressable (~> 2.0) - jekyll (>= 3.5, < 5.0) - jekyll-sass-converter (>= 1.0, <= 3.0.0, != 2.0.0) - rubyzip (>= 1.3.0, < 3.0) - jekyll-sass-converter (1.5.2) - sass (~> 3.4) - jekyll-seo-tag (2.8.0) - jekyll (>= 3.8, < 5.0) - jekyll-sitemap (1.4.0) - jekyll (>= 3.7, < 5.0) - jekyll-swiss (1.0.0) - jekyll-theme-architect (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-cayman (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-dinky (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-hacker (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-leap-day (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-merlot (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-midnight (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-minimal (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-modernist (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-primer (0.6.0) - jekyll (> 3.5, < 5.0) - jekyll-github-metadata (~> 2.9) - jekyll-seo-tag (~> 2.0) - jekyll-theme-slate (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-tactile (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-theme-time-machine (0.2.0) - jekyll (> 3.5, < 5.0) - jekyll-seo-tag (~> 2.0) - jekyll-titles-from-headings (0.5.3) - jekyll (>= 3.3, < 5.0) - jekyll-watch (2.2.1) - listen (~> 3.0) - jemoji (0.13.0) - gemoji (>= 3, < 5) - html-pipeline (~> 2.2) - jekyll (>= 3.0, < 5.0) - json (2.20.0) - just-the-docs (0.3.3) - jekyll (>= 3.8.5) - jekyll-seo-tag (~> 2.0) - rake (>= 12.3.1, < 13.1.0) - kramdown (2.4.0) - rexml - kramdown-parser-gfm (1.1.0) - kramdown (~> 2.0) - liquid (4.0.4) - listen (3.9.0) - rb-fsevent (~> 0.10, >= 0.10.3) - rb-inotify (~> 0.9, >= 0.9.10) - logger (1.7.0) - mercenary (0.3.6) - minima (2.5.1) - jekyll (>= 3.5, < 5.0) - jekyll-feed (~> 0.9) - jekyll-seo-tag (~> 2.1) - minitest (5.27.0) - net-http (0.9.1) - uri (>= 0.11.1) - nokogiri (1.19.4-aarch64-linux-gnu) - racc (~> 1.4) - nokogiri (1.19.4-arm64-darwin) - racc (~> 1.4) - nokogiri (1.19.4-x86_64-linux-gnu) - racc (~> 1.4) - octokit (4.25.1) - faraday (>= 1, < 3) - sawyer (~> 0.9) - pathutil (0.16.2) - forwardable-extended (~> 2.6) - public_suffix (5.1.1) - racc (1.8.1) - rake (13.0.6) - rb-fsevent (0.11.2) - rb-inotify (0.11.1) - ffi (~> 1.0) - rexml (3.4.2) - rouge (3.30.0) - rubyzip (2.3.2) - safe_yaml (1.0.5) - sass (3.7.4) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - sawyer (0.9.2) - addressable (>= 2.3.5) - faraday (>= 0.17.3, < 3) - securerandom (0.4.1) - simpleidn (0.2.3) - terminal-table (1.8.0) - unicode-display_width (~> 1.1, >= 1.1.1) - typhoeus (1.4.1) - ethon (>= 0.9.0) - tzinfo (2.0.6) - concurrent-ruby (~> 1.0) - unicode-display_width (1.8.0) - uri (1.1.1) - webrick (1.8.2) - -PLATFORMS - aarch64-linux - arm64-darwin-24 - x86_64-linux - -DEPENDENCIES - github-pages - just-the-docs (= 0.3.3) - webrick (~> 1.8) - -BUNDLED WITH - 2.5.16 diff --git a/Makefile b/Makefile new file mode 100644 index 00000000000..70a02030ad7 --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +UV ?= uv +PORT ?= 8004 +AWS_PROFILE ?= + +.PHONY: assemble check clean full html lint serve test validate + +clean: + rm -rf _site + +html: clean + $(UV) run sphinx-build -E -b dirhtml source _site -W --keep-going -n + +assemble: + AWS_PROFILE=$(AWS_PROFILE) $(UV) run bash ci/download_from_s3.sh + $(UV) run bash ci/post-process.sh + $(UV) run python scripts/validate_site.py _site --full + +full: html assemble + +lint: + $(UV) run ruff check ci extensions scripts source/conf.py tests + $(UV) run ruff format --check ci extensions scripts source/conf.py tests + +test: + $(UV) run pytest + +validate: + $(UV) run python scripts/validate_site.py _site + $(UV) run python scripts/compare_routes.py tests/fixtures/jekyll_manifest.json _site + +check: lint test html validate + +serve: + $(UV) run python -m http.server $(PORT) --bind 0.0.0.0 --directory _site diff --git a/README.md b/README.md index af1a0a8631f..5b486b75bd7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,61 @@ -# RAPIDS Docs +# NVIDIA RAPIDS Documentation -Jekyll site for RAPIDS documentation. +This repository contains the source for the +[NVIDIA RAPIDS documentation portal](https://docs.rapids.ai/). The portal is +built with Sphinx and the NVIDIA Sphinx theme. -https://docs.rapids.ai +## Build the portal + +Install [uv](https://docs.astral.sh/uv/), then run: + +```shell +make html +make serve +``` + +The rendered portal is written to `_site` and served at + by default. + +## Build the complete site + +The complete docs site imports versioned API documentation and the deployment +documentation from the private `rapidsai-docs` S3 bucket. Configure a read-only +AWS profile named `rapids-docs`, then run: + +```shell +AWS_PROFILE=rapids-docs make full +``` + +This preserves the stable, latest, nightly, and legacy aliases and applies the +RAPIDS library/version selectors to the imported documentation. + +## Validation + +```shell +make check +``` + +This runs Python linting, unit tests, a warning-free Sphinx build, and output +validation. + +Pull requests opened against `rapidsai/docs` are copied to a +`pull-request/` branch by the RAPIDS copy-PR bot. That branch runs the +same validation, assembles the complete documentation tree, and creates a +non-production Netlify preview. Merges to `main` continue to deploy the +production site. + +## Repository layout + +- `source/` contains the portal content, Sphinx configuration, static assets, + and data files. +- `extensions/` contains the portal's data-rendering and publication extension. +- `ci/` downloads and post-processes versioned API and deployment documentation. +- `scripts/` and `tests/` validate rendered routes, content, and publication + behavior. + +## Migration history + +The Sphinx portal was initially migrated from the Jekyll site at +[`rapidsai/docs@b6afa0c`](https://github.com/rapidsai/docs/commit/b6afa0cbf4ddfc4c0a21f7c79b18631f214fd759). +The route and content fixture in `tests/fixtures/jekyll_manifest.json` preserves +that publication baseline. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 19896d9c2fc..00000000000 --- a/_config.yml +++ /dev/null @@ -1,57 +0,0 @@ -# theme used -remote_theme: pmarsceill/just-the-docs -#theme: just-the-docs - -title: RAPIDS Docs -description: RAPIDS demo, process, and technical documentation. -baseurl: "/" # the subpath of your site, e.g. /blog -url: "https://docs.rapids.ai" - -permalink: pretty -exclude: - - CONTRIBUTING.md - - README.md - - release_checklist.md - - ci/ - - node_modules/ - - package.json - - package-lock.json - - Gemfile - - Gemfile.lock - - .ruff_cache/ -include: - - _sources - - _static - - _images - - _redirects - - _sphinx_javascript_frameworks_compat.js - -collections: - notices: - output: true - -aux_links: - "View Docs on GitHub": - - "https://github.com/rapidsai/docs" - -nav_external_links: - - title: Deployment Guides - url: /deployment/stable/ - -social: - twitter: - name: Twitter - username: rapidsai - url: https://twitter.com/rapidsai - fa-icon-class: fab fa-twitter - slack: - name: Slack - url: https://rapids.ai/slack-invite - fa-icon-class: fab fa-slack - stack-overflow: - name: Stack Overflow - url: https://stackoverflow.com/tags/rapids - fa-icon-class: fab fa-stack-overflow - -# Enable or disable the site search -search_enabled: true diff --git a/_drafts/maintainers/artifacts.md b/_drafts/maintainers/artifacts.md deleted file mode 100644 index d9267439a46..00000000000 --- a/_drafts/maintainers/artifacts.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -layout: default -nav_order: 4 -parent: RAPIDS Maintainer Docs -title: Project Artifacts ---- - -# Artifacts - -## Overview - -... - -### Intended audience - -Operations -{: .label .label-purple} - -## Conda packages - -... - -## Pip packages - -... - -## Docker containers - -... - -## Continuous integration - -... diff --git a/_drafts/maintainers/permissions.md b/_drafts/maintainers/permissions.md deleted file mode 100644 index be8ed7a65fb..00000000000 --- a/_drafts/maintainers/permissions.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: default -nav_order: 3 -parent: RAPIDS Maintainer Docs -title: Permissions ---- - -# Permissions - - -## Overview - -... - -### Intended audience - -Operations -{: .label .label-purple} - -## Read - -... - -## Write - -... - -## Admin - -... diff --git a/_drafts/maintainers/projectboards.md b/_drafts/maintainers/projectboards.md deleted file mode 100644 index 1ac8a866af7..00000000000 --- a/_drafts/maintainers/projectboards.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -layout: default -nav_order: 2 -parent: RAPIDS Maintainer Docs -title: Project Boards ---- - -# Project Boards - - -## Overview - -... - -### Intended audience - -Operations -{: .label .label-purple} - -## Release Planning - -... - -## Issue Triage - -... - -## Release Delivery - -... diff --git a/_drafts/maintainers/readthedocs.md b/_drafts/maintainers/readthedocs.md deleted file mode 100644 index a8aebe2cd36..00000000000 --- a/_drafts/maintainers/readthedocs.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -layout: default -nav_order: 5 -parent: RAPIDS Maintainer Docs -title: Read the Docs ---- - -# Read the docs - -## Overview - -... - -### Intended audience - -Operations -{: .label .label-purple} - -## Configuration - -... diff --git a/_drafts/maintainers/structure.md b/_drafts/maintainers/structure.md deleted file mode 100644 index fe2b8982994..00000000000 --- a/_drafts/maintainers/structure.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -layout: default -nav_order: 1 -parent: RAPIDS Maintainer Docs -title: Project Structure ---- - -# Projects - -## Overview - -... - -### Intended audience - -Operations -{: .label .label-purple} - -### Files - -### README - -... - -### CONTRIBUTING - -... - -### CHANGELOG - -... diff --git a/_includes/api-docs.html b/_includes/api-docs.html deleted file mode 100644 index bfa66cdae21..00000000000 --- a/_includes/api-docs.html +++ /dev/null @@ -1,25 +0,0 @@ - -{% for lib in include.data %} -{% assign api = lib[1] %} -{% if api.hidden != true %} -{% comment %} HACK: below operation returns an array of stable/nightly/legacy values only if they're enabled in docs.yml. see issue #97 {% endcomment %} -{% assign versions = api.versions | sort | where_exp: "item", "item[1] == 1" | join: "" | split: "1" | reverse %} -### {{ api.name }} -{{ api.desc }} -#### DOCS {% for version_name in versions -%} - {%- if api.version-overrides -%} - **[{{ version_name }} ({{ api.version-overrides[version_name] }})](/api/{{ api.path }}/{{ version_name }})** - {%- elsif api.name == "libucxx" or api.name == "UCXX" -%} - **[{{ version_name }} ({{ site.data.releases[version_name].ucxx_version }})](/api/{{ api.path }}/{{ version_name }})** - {%- else -%} - **[{{ version_name }} ({{ site.data.releases[version_name].version }})](/api/{{ api.path }}/{{ version_name }})** - {%- endif -%} - {%- unless forloop.last %} | {% endunless -%} -{%- endfor %} - -#### LINKS {% if api.cllink %} **[changelog]({{ api.cllink }}){:target="_blank"}** | {% endif %} **[github]({{ api.ghlink }}){:target="_blank"}** -{: .mb-7 } -{% endif %} -{% endfor %} diff --git a/_includes/gpu-labels-table-row.html b/_includes/gpu-labels-table-row.html deleted file mode 100644 index 071ea2d6b9e..00000000000 --- a/_includes/gpu-labels-table-row.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - linux-{{include.arch}}-gpu-{{include.gpu|downcase}}-{{include.version}}-1 - - {{include.gpu}} - {{include.driver}} - 1 - diff --git a/_includes/gpu-labels-table.html b/_includes/gpu-labels-table.html deleted file mode 100644 index 8bf1267dffd..00000000000 --- a/_includes/gpu-labels-table.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - {% include gpu-labels-table-row.html arch="amd64" gpu="V100" driver=earliest_driver_version version="earliest" %} - {% include gpu-labels-table-row.html arch="amd64" gpu="V100" driver=latest_driver_version version="latest" %} - {% include gpu-labels-table-row.html arch="arm64" gpu="A100" driver=latest_driver_version version="latest" %} - {% include gpu-labels-table-row.html arch="amd64" gpu="T4" driver=latest_driver_version version="latest" %} - -
LabelGPUDriver Version# of GPUs
diff --git a/_includes/head.html b/_includes/head.html deleted file mode 100644 index 98b1aaa1ef1..00000000000 --- a/_includes/head.html +++ /dev/null @@ -1,99 +0,0 @@ - - - - - {% if page.description %} - - - - {% endif %} - - - - - - - - - - - - - - - - - - - {{ page.title }} - {{ site.title }} - - - - - - - - - - - - - - - - {% if site.search_enabled != nil %} - - {% endif %} - - - - - - - - diff --git a/_includes/nav.html b/_includes/nav.html deleted file mode 100644 index 5ddf537efdf..00000000000 --- a/_includes/nav.html +++ /dev/null @@ -1,59 +0,0 @@ - - diff --git a/_layouts/default.html b/_layouts/default.html deleted file mode 100644 index 13c31214f5c..00000000000 --- a/_layouts/default.html +++ /dev/null @@ -1,77 +0,0 @@ - - - -{% include head.html %} - -
- -
- -
- {% unless page.url == "/" %} - {% if page.parent %} - - {% endif %} - {% endunless %} -
- {{ content }} - - {% if page.has_children == true and page.has_toc != false %} -
-

Table of contents

- {% assign children_list = site.pages | sort:"nav_order" %} -
    - {% for child in children_list %} - {% if child.parent == page.title and child.title != page.title %} -
  • - {{ child.title }} -
  • - {% endif %} - {% endfor %} -
- {% endif %} -
-
-
-
- - - diff --git a/_layouts/notice-index.html b/_layouts/notice-index.html deleted file mode 100644 index 0639bd4efc5..00000000000 --- a/_layouts/notice-index.html +++ /dev/null @@ -1,97 +0,0 @@ ---- -layout: default ---- -{{ content }} - -
- -{% if page.has_notice_index %} - {% assign notice_list = site.notices | where:"notice_type",page.notice_type | sort:"notice_updated" | reverse %} - {% assign notice_cnt = notice_list | size %} - {% if notice_cnt == 0 %} -

No current Notices

- {% else %} - - - - - - - - - - - - {% for notice in notice_list %} - - - - - - - - {% endfor %} - -
NoticeTitleTopicRAPIDS VersionUpdated
- {{ notice.notice_type | upcase }} {{ notice.notice_id}}

{{ notice.notice_status }}

-
- {{ notice.title }} - - {{ notice.notice_topic }} - - {{ notice.notice_rapids_version }} - - {% if notice.notice_updated == null or notice.notice_created == notice.notice_updated %} - {{ notice.notice_created | date_to_long_string }} - {% else %} - {{ notice.notice_updated | date_to_long_string }} - {% endif %} -
- {% endif %} -{% endif %} - -{% if page.has_notice_pin_index %} -

Recent and Important Notices

- {% assign notice_list = site.notices | where:"notice_pin","true" | sort:"notice_updated" | reverse %} - {% assign notice_cnt = notice_list | size %} - {% if notice_cnt == 0 %} -

No current Notices

- {% else %} - - - - - - - - - - - - {% for notice in notice_list %} - - - - - - - - {% endfor %} - -
NoticeTitleTopicRAPIDS VersionUpdated
- {{ notice.notice_type | upcase }} {{ notice.notice_id}}

{{ notice.notice_status }}

-
- {{ notice.title }} - - {{ notice.notice_topic }} - - {{ notice.notice_rapids_version }} - - {% if notice.notice_updated == null or notice.notice_created == notice.notice_updated %} - {{ notice.notice_created | date_to_long_string }} - {% else %} - {{ notice.notice_updated | date_to_long_string }} - {% endif %} -
- {% endif %} -{% endif %} diff --git a/_layouts/notice.html b/_layouts/notice.html deleted file mode 100644 index ffbe26e7d72..00000000000 --- a/_layouts/notice.html +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: default ---- -

{{ page.notice_type | upcase }} {{ page.notice_id}} - {{ page.title }}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Author{{ page.notice_author }}
Status

{{ page.notice_status }}

Topic{{ page.notice_topic }}
RAPIDS Version{{ page.notice_rapids_version }}
Created{{ page.notice_created | date_to_long_string }}
Updated{% if page.notice_updated != null and page.notice_created != page.notice_updated %}{{ page.notice_updated | date_to_long_string }}{% else %}N/A{% endif %}
- -{{ content }} diff --git a/api.md b/api.md deleted file mode 100644 index 88c8e4988bb..00000000000 --- a/api.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -layout: default -title: API Docs -nav_order: 4 -has_toc: false -has_children: false ---- - -# RAPIDS API Docs -{:.no_toc} - -Access our current docs for the RAPIDS projects below. Docs are available in -both "stable" and "nightly" versions. The description of each is below to help -select the docs that fit your needs. -{: .fs-6 .fw-300 } -
-
STABLE
-
Current release docs; considered to be stable.
-
NIGHTLY
-
Work-in-progress release docs; considered to be unstable and released nightly.
-
LEGACY
-
Previous release docs; available for reference.
-
- -## RAPIDS APIs - -{% include api-docs.html data=site.data.docs.apis %} - -## RAPIDS Libraries - -{% include api-docs.html data=site.data.docs.libs %} - -## Inactive Projects - -{% include api-docs.html data=site.data.docs.inactive-projects %} diff --git a/assets/images/seaborn-logo.svg b/assets/images/seaborn-logo.svg deleted file mode 100644 index 57f1f71345a..00000000000 --- a/assets/images/seaborn-logo.svg +++ /dev/null @@ -1,5216 +0,0 @@ - - - - - - - - - 2020-09-07T14:13:58.676334 - image/svg+xml - - - Matplotlib v3.3.1, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/ci/check_style.sh b/ci/check_style.sh index 4cd72468c14..d635ce8db43 100755 --- a/ci/check_style.sh +++ b/ci/check_style.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 diff --git a/ci/customization/customize_doc.py b/ci/customization/customize_doc.py index d073bef9608..bddbe382e6f 100644 --- a/ci/customization/customize_doc.py +++ b/ci/customization/customize_doc.py @@ -13,7 +13,6 @@ from concurrent.futures import ProcessPoolExecutor import yaml - from bs4 import BeautifulSoup SCRIPT_TAG_ID = "rapids-selector-js" @@ -70,15 +69,18 @@ def get_version_from_fp(*, filepath: str, versions_dict: dict): match = re.search(r"/(\d?\d\.\d\d)/", filepath) version_number_from_filepath = match.group(1) - # given a version number like "25.10", figure out the corresponding version name like "stable", "nightly", or "legacy" + # Given a version number like "25.10", find the corresponding version name, + # such as "stable", "nightly", or "legacy". for version_name, version_number in versions_dict.items(): if version_number == version_number_from_filepath: return {"name": version_name, "number": version_number_from_filepath} # if we get here, the version number wasn't found - raise ValueError( - f"Filepath implies version '{version_number_from_filepath}', no matching entry in versions_dict: {versions_dict}" + message = ( + f"Filepath implies version '{version_number_from_filepath}', " + f"no matching entry in versions_dict: {versions_dict}" ) + raise ValueError(message) def get_lib_from_fp(*, filepath: str, lib_path_dict: dict) -> str: @@ -148,12 +150,10 @@ def create_version_options( options = [] doc_version = get_version_from_fp(filepath=filepath, versions_dict=versions_dict) doc_is_extra_legacy = ( # extra legacy means the doc version is older then current legacy - doc_version["name"] == "legacy" - and versions_dict["legacy"] != doc_version["number"] + doc_version["name"] == "legacy" and versions_dict["legacy"] != doc_version["number"] ) doc_is_extra_nightly = ( # extra nightly means the doc version is newer then current nightly - doc_version["name"] == "nightly" - and versions_dict["nightly"] != doc_version["number"] + doc_version["name"] == "nightly" and versions_dict["nightly"] != doc_version["number"] ) for version_name, version_path in [ (_, path) for _, path in lib_path_dict[project_name].items() if path is not None @@ -169,9 +169,7 @@ def create_version_options( version_text = f"{version_name} ({version_number_str})" if version_name == doc_version["name"]: is_selected = True - options.append( - {"selected": is_selected, "href": option_href, "text": version_text} - ) + options.append({"selected": is_selected, "href": option_href, "text": version_text}) return options @@ -222,9 +220,7 @@ def create_selector(soup, options, *, fallback_selected_text=None): option_classes = ["rapids-selector__menu-item"] if option["selected"]: option_classes.append("rapids-selector__menu-item--selected") - option_el = soup.new_tag( - "a", href=option["href"], attrs={"class": option_classes} - ) + option_el = soup.new_tag("a", href=option["href"], attrs={"class": option_classes}) option_el.string = option["text"] drop_down_menu.append(option_el) @@ -236,9 +232,7 @@ def create_script_tag(soup): """ Creates and returns a script tag that points to custom.js """ - script_tag = soup.new_tag( - "script", defer=None, id=SCRIPT_TAG_ID, src="/assets/js/custom.js" - ) + script_tag = soup.new_tag("script", defer=None, id=SCRIPT_TAG_ID, src="/assets/js/custom.js") return script_tag @@ -341,9 +335,7 @@ def inspect_document(soup, *, filepath: str): if element.name == "link" and (href := element.get("href")): if "nvidia-sphinx-theme" in href: is_nvidia_theme = True - if element_id not in removable_ids and href.endswith( - "/assets/css/custom.css" - ): + if element_id not in removable_ids and href.endswith("/assets/css/custom.css"): rapids_css_links.append(element) for doc_type in ("jtd", "doxygen", "pydata"): @@ -457,7 +449,7 @@ def main( PROJECT_TO_VERSIONS_PATH = sys.argv[2] LIB_MAP_PATH = os.path.join(os.path.dirname(__file__), "lib_map.json") DOCS_YML_PATH = os.path.join( - os.path.dirname(__file__), "..", "..", "_data", "docs.yml" + os.path.dirname(__file__), "..", "..", "source", "_data", "docs.yml" ) # read in config files (doing this here so it only happens once) diff --git a/ci/customization/customize_docs_in_folder.sh b/ci/customization/customize_docs_in_folder.sh index a7456d66957..09c88d5190f 100755 --- a/ci/customization/customize_docs_in_folder.sh +++ b/ci/customization/customize_docs_in_folder.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 diff --git a/ci/customization/lib_map.sh b/ci/customization/lib_map.sh index e0ff6d51dbe..0a6ae9208a1 100755 --- a/ci/customization/lib_map.sh +++ b/ci/customization/lib_map.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 diff --git a/ci/download_from_s3.py b/ci/download_from_s3.py new file mode 100644 index 00000000000..2644e2966b5 --- /dev/null +++ b/ci/download_from_s3.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Download the versioned RAPIDS documentation tree from S3.""" + +from __future__ import annotations + +import json +import os +from concurrent.futures import ThreadPoolExecutor +from pathlib import Path + +import boto3 + +BUCKET = "rapidsai-docs" +ROOT = Path(__file__).resolve().parents[1] +SITE_DIR = ROOT / os.environ.get("SITE_DIR", "_site") +API_DIR = SITE_DIR / "api" +DEPLOYMENT_DIR = SITE_DIR / "deployment" +PROJECT_VERSIONS = ROOT / "ci" / "customization" / "projects-to-versions.json" +WORKERS = int(os.environ.get("S3_DOWNLOAD_WORKERS", "16")) + + +def validate_output() -> None: + if not SITE_DIR.is_dir(): + raise SystemExit(f'"{SITE_DIR}" does not exist. Build the Sphinx portal first.') + api_entries = sorted(path.name for path in API_DIR.iterdir()) + if api_entries != ["index.html"]: + raise SystemExit(f'"{API_DIR}" must contain only index.html before importing API docs.') + if DEPLOYMENT_DIR.exists(): + raise SystemExit(f'"{DEPLOYMENT_DIR}" is populated only during full-site assembly.') + + +def object_keys(client, prefix: str) -> list[str]: + paginator = client.get_paginator("list_objects_v2") + keys = [ + item["Key"] + for page in paginator.paginate(Bucket=BUCKET, Prefix=prefix) + for item in page.get("Contents", []) + if not item["Key"].endswith("/") + ] + if not keys: + raise SystemExit(f"No files found in s3://{BUCKET}/{prefix}") + return keys + + +def download_prefix(client, prefix: str, destination: Path) -> None: + keys = object_keys(client, prefix) + print(f"Copying s3://{BUCKET}/{prefix} to {destination} ({len(keys)} files)") + + def download(key: str) -> None: + relative = key.removeprefix(prefix) + target = destination / relative + target.parent.mkdir(parents=True, exist_ok=True) + client.download_file(BUCKET, key, str(target)) + + with ThreadPoolExecutor(max_workers=WORKERS) as executor: + list(executor.map(download, keys)) + + +def main() -> None: + validate_output() + profile = os.environ.get("AWS_PROFILE") + session = boto3.Session(profile_name=profile) if profile else boto3.Session() + client = session.client("s3") + + projects = json.loads(PROJECT_VERSIONS.read_text()) + for project, versions in projects.items(): + for version_number in versions.values(): + prefix = f"{project}/html/{version_number}/" + download_prefix(client, prefix, API_DIR / project / str(version_number)) + + for version in ("nightly", "stable"): + download_prefix( + client, + f"deployment/html/{version}/", + DEPLOYMENT_DIR / version, + ) + + +if __name__ == "__main__": + main() diff --git a/ci/download_from_s3.sh b/ci/download_from_s3.sh old mode 100755 new mode 100644 index 060b29e2373..ed86e4a70a4 --- a/ci/download_from_s3.sh +++ b/ci/download_from_s3.sh @@ -1,159 +1,8 @@ #!/bin/bash # SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. -# All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# -# Copies the RAPIDS projects' HTML files from S3 into the "_site" directory of -# the Jekyll build. -set -euo pipefail - -export JEKYLL_DIR="_site" - -export GENERATED_DIRS=" -libs: ${JEKYLL_DIR}/api -deployment: ${JEKYLL_DIR}/deployment -" -export DOCS_BUCKET="rapidsai-docs" - -MAX_CONCURRENT_DOWNLOADS=4 -DOWNLOAD_PIDS=() - -# Checks that the "_site" directory exists from a Jekyll build. Also ensures -# that the directories that are pulled from S3 aren't already present in the -# "_site" directory since that could cause problems. -check_dirs() { - local DIR API_DIR - - if [ ! -d "${JEKYLL_DIR}" ]; then - echo "\"${JEKYLL_DIR}\" directory does not exist." - echo "Build Jekyll site first." - exit 1 - fi - - - API_DIR=$(yq -n 'env(GENERATED_DIRS) | .libs') - if [[ $(cd "${API_DIR}"; ls -1d ./*) != "./index.html" ]]; then - echo "The \"${API_DIR}\" directory should only contain a single 'index.html' file." - exit 1 - fi - - for DIR in $(yq -n 'env(GENERATED_DIRS) | del .libs | .[]'); do - if [ -d "${DIR}" ]; then - echo "The \"${DIR}\" directory is populated at deploy time and should not already exist." - echo "Ensure the \"${DIR}\" directory is not generated by Jekyll." - exit 1 - fi - done -} - -# Helper function for the `aws cp` command. Checks to ensure that the source -# directory has contents before attempting the copy. -aws_cp() { - local SRC DST - - SRC=$1 - DST=$2 - - if ! aws s3 ls "${SRC}" > /dev/null; then - echo "No files found in ${SRC}. Exiting." - exit 1 - fi - - echo "Copying ${SRC} to ${DST}" - aws s3 cp \ - --only-show-errors \ - --recursive \ - "${SRC}" \ - "${DST}" -} - -# Starts an S3 copy and waits when the concurrency limit is reached. -start_aws_cp() { - local DST SRC - SRC=$1 - DST=$2 - - aws_cp "${SRC}" "${DST}" & - DOWNLOAD_PIDS+=("$!") - - if [ "${#DOWNLOAD_PIDS[@]}" -ge "${MAX_CONCURRENT_DOWNLOADS}" ]; then - wait_for_aws_cp - fi -} - -# Waits for the oldest running S3 copy and propagates its failure. -wait_for_aws_cp() { - local PID - - PID=${DOWNLOAD_PIDS[0]} - wait "${PID}" - DOWNLOAD_PIDS=("${DOWNLOAD_PIDS[@]:1}") -} - -# Waits for all remaining S3 copies. -wait_for_all_aws_cp() { - while [ "${#DOWNLOAD_PIDS[@]}" -gt 0 ]; do - wait_for_aws_cp - done -} - -# Downloads the RAPIDS libraries' documentation files from S3 and places them -# into the "_site/api" folder. -download_lib_docs() { - local DST PROJECT PROJECTS_TO_VERSIONS_JSON \ - SRC VERSION_NAME VERSION_NUMBER - - echo "--- processing RAPIDS libraries ---" - PROJECTS_TO_VERSIONS_JSON=$(cat "./ci/customization/projects-to-versions.json") - for PROJECT in $(jq -r 'keys | .[]' <<< "${PROJECTS_TO_VERSIONS_JSON}"); do - - # extract the map of versions to download for this project, which will look something like: - # - # {"stable": 25.10, "nightly": 25.12, "legacy": 25.08} - # - # With keys varying based on which types of docs we want to build for this particular project. - VERSIONS_FOR_THIS_PROJECT=$( - jq \ - -r \ - --arg pr "${PROJECT}" \ - '.[$pr]' \ - <<< "${PROJECTS_TO_VERSIONS_JSON}" - ) - - # loop over 'stable', 'nightly', etc. - for VERSION_NAME in $(jq -r 'keys | .[]' <<< "${VERSIONS_FOR_THIS_PROJECT}"); do - VERSION_NUMBER=$( - jq \ - -r \ - --arg version_name "${VERSION_NAME}" \ - '.[$version_name]' \ - <<< "${VERSIONS_FOR_THIS_PROJECT}" - ) - # copy the relevant files from S3 to the local directory - SRC="s3://${DOCS_BUCKET}/${PROJECT}/html/${VERSION_NUMBER}/" - DST="$(yq -n 'env(GENERATED_DIRS)|.libs')/${PROJECT}/${VERSION_NUMBER}/" - start_aws_cp "${SRC}" "${DST}" - done # for VERSION_NAME - - done # for PROJECT -} - -# Downloads the deployment docs from S3 and places them in the -# "_site/deployment" directory. -download_deployment_docs() { - local DST SRC VERSION - - echo "--- processing deployment docs ---" - for VERSION in nightly stable; do - SRC="s3://${DOCS_BUCKET}/deployment/html/${VERSION}/" - DST="$(yq -n 'env(GENERATED_DIRS)|.deployment')/${VERSION}/" - - start_aws_cp "${SRC}" "${DST}" - done -} +set -euo pipefail -check_dirs -download_lib_docs -download_deployment_docs -wait_for_all_aws_cp +CURRENT_DIR=$(dirname "$(realpath "$0")") +python "${CURRENT_DIR}/download_from_s3.py" diff --git a/ci/generate-projects-to-versions.py b/ci/generate-projects-to-versions.py index cb2263c7b43..4078ee48207 100755 --- a/ci/generate-projects-to-versions.py +++ b/ci/generate-projects-to-versions.py @@ -11,12 +11,12 @@ # * what types of docs to host ('legacy', 'nightly', 'stable', etc.) # * what versions to map to those types # -# The libraries that should be copied are read from "_data/docs.yml". +# The libraries that should be copied are read from "source/_data/docs.yml". # # The versions that should be copied are read from a mix of sources: # -# - active projects: "_data/releases.json" -# - inactive projects: 'version-overrides' field in entries in "_data/docs.yml" +# - active projects: "source/_data/releases.json" +# - inactive projects: 'version-overrides' field in entries in "source/_data/docs.yml" # # Produces a JSON mapping of the form: # @@ -28,7 +28,7 @@ # }, # } # -# With keys omitted based on configuration in _data/docs.yml. +# With keys omitted based on configuration in source/_data/docs.yml. # # e.g. if a project has 'stable: 0' in that file, it will not have a '{project}.stable' # key in the mapping produced by this script. @@ -39,10 +39,10 @@ import yaml -with open("_data/docs.yml") as f: +with open("source/_data/docs.yml") as f: DOCS_YML_DICT = yaml.safe_load(f) -with open("_data/releases.json") as f: +with open("source/_data/releases.json") as f: RELEASES_JSON_DICT = json.load(f) # using OrderedDict minimizes churn in the output as projects are added and removed @@ -66,9 +66,9 @@ if version_override: versions_for_this_project[version_name] = version_override else: - versions_for_this_project[version_name] = RELEASES_JSON_DICT[ - version_name - ][version_key] + versions_for_this_project[version_name] = RELEASES_JSON_DICT[version_name][ + version_key + ] else: print(f"Skipping: {project_name} | {version_name}", file=sys.stderr) diff --git a/ci/post-process.sh b/ci/post-process.sh index 5078a86a181..7b31026f7ee 100755 --- a/ci/post-process.sh +++ b/ci/post-process.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 @@ -7,8 +7,6 @@ set -euo pipefail CURRENT_DIR=$(dirname $(realpath $0)) -pip install -r "${CURRENT_DIR}/customization/requirements.txt" - PROJECTS_TO_VERSIONS_PATH="${CURRENT_DIR}"/customization/projects-to-versions.json "${CURRENT_DIR}"/update_symlinks.sh "${PROJECTS_TO_VERSIONS_PATH}" diff --git a/ci/update_symlinks.sh b/ci/update_symlinks.sh index 7940930fcf9..c2d18d225f1 100755 --- a/ci/update_symlinks.sh +++ b/ci/update_symlinks.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 diff --git a/ci/upload_cudf_java_docs.sh b/ci/upload_cudf_java_docs.sh index 5e6e6af7a28..a67cc93e02a 100755 --- a/ci/upload_cudf_java_docs.sh +++ b/ci/upload_cudf_java_docs.sh @@ -1,5 +1,5 @@ #!/bin/bash -# SPDX-FileCopyrightText: Copyright (c) 2024-2025, NVIDIA CORPORATION & AFFILIATES. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. # All rights reserved. # SPDX-License-Identifier: Apache-2.0 diff --git a/extensions/__init__.py b/extensions/__init__.py new file mode 100644 index 00000000000..b8a9a8d1a7f --- /dev/null +++ b/extensions/__init__.py @@ -0,0 +1,4 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Local Sphinx extensions for the NVIDIA RAPIDS documentation portal.""" diff --git a/extensions/rapids_docs.py b/extensions/rapids_docs.py new file mode 100644 index 00000000000..0436e14df11 --- /dev/null +++ b/extensions/rapids_docs.py @@ -0,0 +1,499 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Data-driven rendering and notice support for the RAPIDS documentation portal.""" + +from __future__ import annotations + +import email.utils +import html +import json +import shutil +from datetime import UTC, datetime +from pathlib import Path +from xml.etree import ElementTree + +import frontmatter +import yaml +from bs4 import BeautifulSoup +from dateutil import parser as date_parser +from jinja2 import Environment, FileSystemLoader, StrictUndefined + + +def _source_dir(app) -> Path: + return Path(app.srcdir) + + +def _date(value) -> datetime: + if isinstance(value, datetime): + return value + if hasattr(value, "year") and hasattr(value, "month") and hasattr(value, "day"): + return datetime(value.year, value.month, value.day) + return date_parser.parse(str(value)) + + +def _long_date(value) -> str: + parsed = _date(value) + return f"{parsed.strftime('%B')} {parsed.day}, {parsed.year}" + + +def _short_date(value) -> str: + parsed = _date(value) + return f"{parsed.strftime('%a, %b')} {parsed.day}, {parsed.year}" + + +def _load_data(app) -> dict: + data_dir = _source_dir(app) / "_data" + with (data_dir / "docs.yml").open() as file: + docs = yaml.safe_load(file) + with (data_dir / "platform_support.yml").open() as file: + platform_support = yaml.safe_load(file) + with (data_dir / "releases.json").open() as file: + releases = json.load(file) + with (data_dir / "previous_releases.json").open() as file: + previous_releases = json.load(file) + + notices = [] + for path in sorted((_source_dir(app) / "notices").glob("r[dgs]n[0-9][0-9][0-9][0-9].md")): + post = frontmatter.load(path) + metadata = dict(post.metadata) + metadata["docname"] = f"notices/{path.stem}" + metadata["body"] = post.content + notices.append(metadata) + + return { + "docs": docs, + "notices": notices, + "platform_support": platform_support, + "previous_releases": previous_releases, + "releases": releases, + } + + +def _version_label(project: dict, version_name: str, releases: dict) -> str: + override = project.get("version-overrides", {}).get(version_name) + if override: + return str(override) + version_key = "ucxx_version" if "ucxx" in project["path"].lower() else "version" + return str(releases[version_name][version_key]) + + +def _api_docs(data: dict, section: str) -> str: + blocks = [] + for project in data["docs"][section].values(): + if project.get("hidden", False): + continue + versions = [] + for name in ("stable", "nightly", "legacy"): + if project["versions"].get(name) == 1: + label = _version_label(project, name, data["releases"]) + versions.append(f"**[{name} ({label})](/api/{project['path']}/{name}/)**") + links = [] + if project.get("cllink"): + links.append(f"**[changelog]({project['cllink']})**") + links.append(f"**[github]({project['ghlink']})**") + blocks.append( + "\n".join( + [ + f"### {project['name']}", + "", + project["desc"], + "", + "#### DOCS" + (" " + " | ".join(versions) if versions else ""), + "", + "#### LINKS " + " | ".join(links), + ] + ) + ) + return "\n\n".join(blocks) + + +def _compute_capability(cuda: dict) -> str: + capabilities = [] + for capability in cuda["compute_capability"]: + sms = capability["sm"] + if not isinstance(sms, list): + sms = [sms] + capabilities.append(f"{capability['name']} ({', '.join(map(str, sms))})") + return ", ".join(capabilities) + " or newer" + + +def _platform_support(data: dict) -> str: + releases = data["platform_support"]["releases"] + links = ", ".join( + f"[{release['version']}{' (nightly)' if release.get('nightly') else ''}]" + f"(#rapids-{str(release['version']).replace('.', '')})" + for release in releases + ) + sections = [f"**Releases:** {links}"] + for release in releases: + title = f"## RAPIDS {release['version']}" + if release.get("nightly"): + title += " (nightly)" + cuda_headers = [f"CUDA {cuda['major']}" for cuda in release["cuda"]] + cuda_rows = [ + ( + "Toolkit", + [ + f"{cuda['toolkit_min']}" + + ( + f" - {cuda['toolkit_max']}" + if cuda["toolkit_min"] != cuda["toolkit_max"] + else "" + ) + for cuda in release["cuda"] + ], + ), + ("Driver", [f"{cuda['driver_min']}+" for cuda in release["cuda"]]), + ("Compute Capability", [_compute_capability(cuda) for cuda in release["cuda"]]), + ] + table = [ + "| | " + " | ".join(cuda_headers) + " |", + "|:--|" + "|".join(":--" for _ in cuda_headers) + "|", + ] + table.extend(f"| **{name}** | " + " | ".join(values) + " |" for name, values in cuda_rows) + sections.append( + "\n".join( + [ + "---", + "", + title, + "", + '### Operating Systems', + "", + f'- ' + f"**Linux (glibc {release['glibc_min']}+):** {', '.join(release['cpu_arch'])} " + f"(tested on {', '.join(release['os_support'])})", + '- ' + "**Windows:** Supported via [WSL](/install/#wsl2) with a compatible Linux distribution", + "", + '### Python', + "", + f"**{', '.join(release['python'])}**", + "", + '### CUDA', + "", + *table, + "", + '### Source Builds', + "", + "| Dependency | Version |", + "|:--|:--|", + f"| **GCC** | {release['source_build']['gcc']} |", + f"| **CCCL** | {release['source_build']['cccl']} |", + f"| **nvCOMP** | {release['source_build']['nvcomp']} |", + ] + ) + ) + return "\n\n".join(sections) + + +def _schedule_table(release: dict) -> str: + rows = [ + ("Development", release["dev"]), + ( + "[Burn Down](/releases/process/#burn-down) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", + release["cudf_burndown"], + ), + ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", + release["cudf_codefreeze"], + ), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ), + ("[Release](/releases/process/#releasing)", release["release"]), + ] + output = ["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"] + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + return "\n".join(output) + + +def _current_schedules(data: dict) -> str: + releases = data["releases"] + return "\n\n".join( + [ + f"## Release v{releases['nightly']['version']} Schedule", + "**NOTE:** *Dates are subject to change at any time. Completed release schedules are posted " + "[here](/releases/schedule/).*", + _schedule_table(releases["nightly"]), + f"## *PROPOSED* Release v{releases['next_nightly']['version']} Schedule", + _schedule_table(releases["next_nightly"]), + ] + ) + + +def _old_project_group(version: str) -> str: + group = "cuDF/RMM" + if version >= "23.06": + group += "/rapids-cmake/" + if version <= "24.12": + group += "cugraph-ops/" + group += "raft" + return group + + +def _previous_schedules(data: dict) -> str: + sections = [] + for release in data["previous_releases"]: + output = [f"### Release v{release['version']} Schedule", ""] + if release.get("dev"): + rows = [("Development", release["dev"])] + group_suffix = ( + " (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf)" + if release.get("other_burndown") + else "" + ) + rows.append( + ( + f"[Burn Down](/releases/process/#burn-down){group_suffix}", + release.get("cudf_burndown") or release["burndown"], + ) + ) + if release.get("other_burndown"): + rows.append( + ( + "[Burn Down](/releases/process/#burn-down) (others)", + release["other_burndown"], + ) + ) + if release.get("cudf_codefreeze"): + rows.append( + ( + f"[Code Freeze/Testing](/releases/process/#code-freeze){group_suffix}", + release["cudf_codefreeze"], + ) + ) + rows.append( + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ) + ) + else: + rows.append( + ("[Code Freeze/Testing](/releases/process/#code-freeze)", release["codefreeze"]) + ) + rows.append(("[Release](/releases/process/#releasing)", release["release"])) + output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + elif release.get("date"): + output.extend( + ["| Phase | Date |", "|:--|:--|", f"| Release | {_short_date(release['date'])} |"] + ) + else: + group = _old_project_group(release["version"]) + rows = [ + (f"Development ({group})", release["cudf_dev"]), + ("Development (others)", release["other_dev"]), + (f"[Burn Down](/releases/process/#burn-down) ({group})", release["cudf_burndown"]), + ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), + ( + f"[Code Freeze/Testing](/releases/process/#code-freeze) ({group})", + release["cudf_codefreeze"], + ), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ), + ("[Release](/releases/process/#releasing)", release["release"]), + ] + output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + sections.append("\n".join(output)) + return "\n\n".join(sections) + + +def _notice_date(notice: dict) -> datetime: + return _date(notice.get("notice_updated") or notice["notice_created"]) + + +def _notice_table(data: dict, notice_type: str | None = None, pinned: bool = False) -> str: + notices = data["notices"] + if notice_type: + notices = [notice for notice in notices if notice["notice_type"] == notice_type] + if pinned: + notices = [ + notice for notice in notices if str(notice.get("notice_pin", "")).lower() == "true" + ] + notices = sorted(notices, key=_notice_date, reverse=True) + if not notices: + return "## No current notices" + output = [ + "| Notice | Title | Topic | RAPIDS Version | Updated |", + "|:--|:--|:--|:--|:--|", + ] + for notice in notices: + updated = notice.get("notice_updated") + if not updated or _date(updated).date() == _date(notice["notice_created"]).date(): + updated = notice["notice_created"] + output.append( + f"| **{notice['notice_type'].upper()} {notice['notice_id']}**
**{notice['notice_status']}** " + f"| [{notice['title']}](/notices/{Path(notice['docname']).name}/) " + f"| {notice['notice_topic']} | {notice['notice_rapids_version']} | {_long_date(updated)} |" + ) + return "\n".join(output) + + +def _jinja_environment(app) -> Environment: + return Environment( + loader=FileSystemLoader(app.srcdir), + undefined=StrictUndefined, + autoescape=False, + variable_start_string="<>", + block_start_string="[%", + block_end_string="%]", + comment_start_string="[#%", + comment_end_string="%#]", + keep_trailing_newline=True, + ) + + +def _context(app) -> dict: + data = app.rapids_portal_data + return { + **data, + "api_docs": lambda section: _api_docs(data, section), + "current_schedules": lambda: _current_schedules(data), + "notice_table": lambda notice_type=None, pinned=False: _notice_table( + data, notice_type, pinned + ), + "platform_support_content": lambda: _platform_support(data), + "previous_schedules": lambda: _previous_schedules(data), + } + + +def _builder_inited(app) -> None: + app.rapids_portal_data = _load_data(app) + app.rapids_portal_jinja = _jinja_environment(app) + + +def _notice_header(metadata: dict) -> str: + updated = metadata.get("notice_updated") + if not updated or _date(updated).date() == _date(metadata["notice_created"]).date(): + updated_display = "N/A" + else: + updated_display = _long_date(updated) + return "\n".join( + [ + "---", + "orphan: true", + "---", + f"# {metadata['notice_type'].upper()} {metadata['notice_id']} - {metadata['title']}", + "", + "| | |", + "|:--|:--|", + f"| **Author** | {metadata['notice_author']} |", + f"| **Status** | **{metadata['notice_status']}** |", + f"| **Topic** | {metadata['notice_topic']} |", + f"| **RAPIDS Version** | {metadata['notice_rapids_version']} |", + f"| **Created** | {_long_date(metadata['notice_created'])} |", + f"| **Updated** | {updated_display} |", + "", + ] + ) + + +def _source_read(app, docname: str, source: list[str]) -> None: + raw = source[0] + if docname.startswith("notices/") and Path(docname).name[:3] in {"rdn", "rgn", "rsn"}: + post = frontmatter.loads(raw) + raw = _notice_header(post.metadata) + post.content + template = app.rapids_portal_jinja.from_string(raw) + source[0] = template.render(_context(app)) + + +def _rss_date(value) -> str: + parsed = _date(value) + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=UTC) + return email.utils.format_datetime(parsed) + + +def _build_rss(app, exception) -> None: + if exception is not None or app.builder.name not in {"html", "dirhtml"}: + return + data = app.rapids_portal_data + ElementTree.register_namespace("atom", "http://www.w3.org/2005/Atom") + rss = ElementTree.Element("rss", version="2.0") + channel = ElementTree.SubElement(rss, "channel") + ElementTree.SubElement(channel, "title").text = "NVIDIA RAPIDS Documentation - Notices" + ElementTree.SubElement( + channel, "description" + ).text = "Notices communicate and document changes in RAPIDS for contributors, developers, users, and the community." + ElementTree.SubElement(channel, "link").text = "https://docs.rapids.ai/notices/" + ElementTree.SubElement( + channel, + "{http://www.w3.org/2005/Atom}link", + href="https://docs.rapids.ai/notices/feed.xml", + rel="self", + type="application/rss+xml", + ) + now = email.utils.format_datetime(datetime.now(UTC)) + ElementTree.SubElement(channel, "pubDate").text = now + ElementTree.SubElement(channel, "lastBuildDate").text = now + ElementTree.SubElement(channel, "generator").text = "Sphinx" + + for notice in sorted(data["notices"], key=_notice_date, reverse=True): + item = ElementTree.SubElement(channel, "item") + ElementTree.SubElement(item, "title").text = str(notice["title"]) + output_path = Path(app.outdir) / notice["docname"] / "index.html" + if output_path.exists(): + soup = BeautifulSoup(output_path.read_text(), "html.parser") + article = soup.select_one("article.bd-article") or soup.select_one("main") + description = article.decode_contents() if article else notice["body"] + else: + description = notice["body"] + ElementTree.SubElement(item, "description").text = html.unescape(description) + published = notice.get("notice_updated") or notice["notice_created"] + ElementTree.SubElement(item, "pubDate").text = _rss_date(published) + url = f"https://docs.rapids.ai/notices/{Path(notice['docname']).name}/" + ElementTree.SubElement(item, "link").text = url + ElementTree.SubElement(item, "guid", isPermaLink="true").text = url + for category in [*notice.get("tags", []), *notice.get("categories", [])]: + ElementTree.SubElement(item, "category").text = str(category) + + output = Path(app.outdir) / "notices" / "feed.xml" + output.parent.mkdir(parents=True, exist_ok=True) + ElementTree.ElementTree(rss).write(output, encoding="utf-8", xml_declaration=True) + + +def _copy_portal_files(app, exception) -> None: + """Copy trees whose root paths are part of the published site interface.""" + if exception is not None or app.builder.name not in {"html", "dirhtml"}: + return + source_dir = _source_dir(app) + output_dir = Path(app.outdir) + for directory in ("assets", "licenses"): + shutil.copytree( + source_dir / directory, + output_dir / directory, + dirs_exist_ok=True, + ) + for filename in ("LICENSE", "SECURITY.md"): + shutil.copy2(source_dir.parent / filename, output_dir / filename) + + # ``dirhtml`` correctly creates pretty URLs everywhere except the hosting + # platform's required top-level error document. sphinx-notfound-page has + # already rewritten this page's resource and navigation links as absolute. + shutil.copy2(output_dir / "404" / "index.html", output_dir / "404.html") + + +def setup(app): + app.connect("builder-inited", _builder_inited) + app.connect("source-read", _source_read) + app.connect("build-finished", _build_rss) + app.connect("build-finished", _copy_portal_files) + return {"version": "1.0", "parallel_read_safe": False, "parallel_write_safe": True} diff --git a/favicon.ico b/favicon.ico deleted file mode 100644 index cf20c489d7cdc055dc49d2aee7fe8721d166d35d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 358 zcmV-s0h#`ZP)LE>gpZH_(V!GaRCH7vfg^ARB<@7R9jK@gq68rjC9_^=3k=yRy93IOC~4{+!~bYvTe>k1GB^;iD;Xwaw2L2kr%|(_$0zn&07rHO7e*(=rc(|GfYS6le+D_U)WqqV_ z9UNbByE@DBcQ6UVk)dgfJ|P+{0EmVVv`BdcACSZa0RY1`bYCIX8&Ew9{>OV07*qoM6N<$ Eg4yJc^8f$< diff --git a/index.md b/index.md deleted file mode 100644 index 1a3f9961c4e..00000000000 --- a/index.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -layout: default -title: Home -nav_order: 1 -description: | - A collection of all the documentation for RAPIDS. Whether you're new to RAPIDS, looking to contribute, or are a part of the RAPIDS team, the docs here will help guide you. ---- - -# RAPIDS Documentation and Resources -{: .fs-8 } - -This site serves to unify the documentation for RAPIDS. Whether you're new to RAPIDS, looking to contribute, or are a part of the RAPIDS team, the docs here will help guide you. Visit [RAPIDS.ai](http://rapids.ai){: target="_blank"} for more information on the overall project. -{: .fs-6 .fw-300 } - -## Sections - -[ Installation Guide]({% link install/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -[ Platform Support]({% link platform-support/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -
-[ User Guides]({% link user-guide/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -
-[ API Documentation]({% link api.md %}){: .btn.fs-4 .mb-4 .mb-md-04 .mr-2 } -[ Visualization Guide]({% link visualization/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -
-[ Deployment Guides](/deployment/stable/){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -[ Maintainer Documentation]({% link maintainers/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -
-[ RAPIDS Notices]({% link notices/index.md %}){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } -[ RAPIDS GitHub](https://github.com/rapidsai){: .btn.fs-4 .mb-4 .mb-md-4 .mr-2 } - ---- - -## Stay Connected - - - -## Docs Issues or Feedback - -[File an issue](https://github.com/rapidsai/docs/issues/new) for any unexpected problems encountered, incorrect information, or general feedback for this documentation site. diff --git a/maintainers/index.md b/maintainers/index.md deleted file mode 100644 index 87249a0d111..00000000000 --- a/maintainers/index.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -layout: default -title: Maintainer Docs -nav_order: 6 -has_children: true ---- - -# RAPIDS Maintainers Docs -{:.no_toc} - -RAPIDS projects use an established set of guidelines and procedures for all projects. These are available for the community to review and provide feedback on. -{: .fs-6 .fw-300 } - -### Intended audience - -Developers -{: .label .label-green} - -Project Leads -{: .label .label-blue} - -Operations -{: .label .label-purple} - -## Release v{{ site.data.releases.nightly.version }} Schedule - -**NOTE:** *Dates are subject to change at anytime. Completed release schedules are posted [here]({% link releases/schedule.md %}).* - -Phase | Start | End | Duration --- | -- | -- | -- -Development | {{ site.data.releases.nightly.dev.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.dev.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.dev.days }} days -[Burn Down]({% link releases/process.md %}#burn-down) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest) | {{ site.data.releases.nightly.cudf_burndown.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.cudf_burndown.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.cudf_burndown.days }} days -[Burn Down]({% link releases/process.md %}#burn-down) (others) | {{ site.data.releases.nightly.other_burndown.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.other_burndown.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.other_burndown.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest) | {{ site.data.releases.nightly.cudf_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.cudf_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.cudf_codefreeze.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (others) | {{ site.data.releases.nightly.other_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.other_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.other_codefreeze.days }} days -[Release]({% link releases/process.md %}#releasing) | {{ site.data.releases.nightly.release.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.release.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.nightly.release.days }} days - -## _PROPOSED_ Release v{{ site.data.releases.next_nightly.version }} Schedule - -Phase | Start | End | Duration --- | -- | -- | -- -Development | {{ site.data.releases.next_nightly.dev.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.dev.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.dev.days }} days -[Burn Down]({% link releases/process.md %}#burn-down) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest) | {{ site.data.releases.next_nightly.cudf_burndown.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.cudf_burndown.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.cudf_burndown.days }} days -[Burn Down]({% link releases/process.md %}#burn-down) (others) | {{ site.data.releases.next_nightly.other_burndown.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.other_burndown.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.other_burndown.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest) | {{ site.data.releases.next_nightly.cudf_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.cudf_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.cudf_codefreeze.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (others) | {{ site.data.releases.next_nightly.other_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.other_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.other_codefreeze.days }} days -[Release]({% link releases/process.md %}#releasing) | {{ site.data.releases.next_nightly.release.start | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.release.end | date: "%a, %b %e, %Y" }} | {{ site.data.releases.next_nightly.release.days }} days diff --git a/notices/feed.xml b/notices/feed.xml deleted file mode 100644 index ca243258a2b..00000000000 --- a/notices/feed.xml +++ /dev/null @@ -1,32 +0,0 @@ ---- -layout: ---- - - - - - {{ site.title | xml_escape }} - Notices - Notices are our means to communicate and document changes in the project to contributors, core developers, users, and the community. - {{ "/notices" | absolute_url }} - - {{ site.time | date_to_rfc822 }} - {{ site.time | date_to_rfc822 }} - Jekyll v{{ jekyll.version }} - {% assign notices = site.notices | sort: 'notice_updated' | reverse %} - {% for post in notices %} - - {{ post.title | xml_escape }} - {{ post.content | xml_escape }} - {% if post.notice_updated == null or post.notice_created == post.notice_updated %}{{ post.notice_created | date_to_rfc822 }}{% else %}{{ post.notice_updated | date_to_rfc822 }}{% endif %} - {{ post.url | absolute_url }} - {{ post.url | absolute_url }} - {% for tag in post.tags %} - {{ tag | xml_escape }} - {% endfor %} - {% for cat in post.categories %} - {{ cat | xml_escape }} - {% endfor %} - - {% endfor %} - - diff --git a/notices/index.md b/notices/index.md deleted file mode 100644 index 86f52868559..00000000000 --- a/notices/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -layout: notice-index -title: RAPIDS Notices -nav_order: 7 -has_children: true -has_notice_pin_index: true # shows pinned notices at end ---- - -# RAPIDS Notices - -Notices are our means to communicate and document changes in the project to contributors, core developers, users, and the community. -{: .fs-6 .fw-300 } - -## Notice Types - -Type | Code | Intended Audience | Purpose ---- | --- | --- | --- -[RAPIDS Developer Notice]({% link notices/rdn/index.md %}) | **RDN** | Contributors & Core Developers | Communicate updates to development processes -[RAPIDS General Notice]({% link notices/rgn/index.md %}) | **RGN** | Everyone | Project wide announcements and updates, including breaking changes -[RAPIDS Support Notice]({% link notices/rsn/index.md %}) | **RSN** | Everyone | Updates on RAPIDS support for specific versions of CUDA, Python, OS, platforms, and compilers diff --git a/notices/rdn/index.md b/notices/rdn/index.md deleted file mode 100644 index 38a534ebb26..00000000000 --- a/notices/rdn/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: notice-index -notice_type: rdn -title: RAPIDS Developer Notices -nav_order: 1 -has_children: true -parent: RAPIDS Notices -has_notice_index: true # shows list of notices for this 'notice_type' ---- - -# RAPIDS Developer Notices -{:.no_toc} - -Index of **RDN** notices targeting RAPIDS contributors and developers about updates to development practices. -{: .fs-6 .fw-300 } diff --git a/notices/rgn/index.md b/notices/rgn/index.md deleted file mode 100644 index de25cd999ec..00000000000 --- a/notices/rgn/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: notice-index -notice_type: rgn -title: RAPIDS General Notices -nav_order: 2 -has_children: true -parent: RAPIDS Notices -has_notice_index: true # shows list of notices for this 'notice_type' ---- - -# RAPIDS General Notices -{:.no_toc} - -Index of **RGN** notices targeting **ALL** RAPIDS users for project wide announcements and updates, including breaking changes. -{: .fs-6 .fw-300 } diff --git a/notices/rsn/index.md b/notices/rsn/index.md deleted file mode 100644 index 2978b729d8d..00000000000 --- a/notices/rsn/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -layout: notice-index -notice_type: rsn -title: RAPIDS Support Notices -nav_order: 3 -has_children: true -parent: RAPIDS Notices -has_notice_index: true # shows list of notices for this 'notice_type' ---- - -# RAPIDS Support Notices -{:.no_toc} - -Index of **RSN** notices targeting **ALL** RAPIDS users for announcements and updates around RAPIDS support for specific versions of CUDA, Python, OS, platforms, and compilers. -{: .fs-6 .fw-300 } diff --git a/platform-support/index.md b/platform-support/index.md deleted file mode 100644 index 117acf4729e..00000000000 --- a/platform-support/index.md +++ /dev/null @@ -1,60 +0,0 @@ ---- -layout: default -title: Platform Support -nav_order: 3 -description: | - RAPIDS platform support matrix showing CUDA, Python, driver, and GPU architecture requirements for each release. ---- - -# RAPIDS Platform Support -{: .fs-8 } - -RAPIDS libraries are supported on a specific set of platforms for each release. RAPIDS depends on CUDA and Python, and each release is built and tested against specific versions of these dependencies. - -RAPIDS uses [CUDA compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/){: target="_blank"} to support a range of CUDA toolkit and driver versions. -The NVIDIA Developer documentation contains a reference of [Compute Capability](https://developer.nvidia.com/cuda-gpus){: target="_blank"} for each GPU architecture. -Note that for the list of supported compute capabilities below, newer GPUs are supported via [forward-compatible PTX instructions](https://developer.nvidia.com/blog/understanding-ptx-the-assembly-language-of-cuda-gpu-computing/){: target="_blank"} built for the latest virtual architecture. - -For installation instructions, see the [Installation Guide](/install/). - -**Releases:** {% for release in site.data.platform_support.releases %}[{{ release.version }}{% if release.nightly %} (nightly){% endif %}](#rapids-{{ release.version | replace: ".", "" }}){% unless forloop.last %}, {% endunless %}{% endfor %} - -{% for release in site.data.platform_support.releases %} ---- - -## RAPIDS {{ release.version }}{% if release.nightly %} (nightly){% endif %} - -
- -#### Operating Systems -{: .fs-5 } - -- **Linux (glibc {{ release.glibc_min }}+):** {{ release.cpu_arch | join: ", " }} (tested on {% for os in release.os_support %}{{ os }}{% unless forloop.last %}, {% endunless %}{% endfor %}) -- **Windows:** Supported via [WSL](/install/#wsl2) with a compatible Linux distribution - -#### Python -{: .fs-5 } - -**{{ release.python | join: ", " }}** - -#### CUDA -{: .fs-5 } - -| | {% for cuda in release.cuda %}CUDA {{ cuda.major }}{% unless forloop.last %} | {% endunless %}{% endfor %} | -|:--|{% for cuda in release.cuda %}:--|{% endfor %} -| **Toolkit** | {% for cuda in release.cuda %}{{ cuda.toolkit_min }}{% if cuda.toolkit_min != cuda.toolkit_max %} - {{ cuda.toolkit_max }}{% endif %}{% unless forloop.last %} | {% endunless %}{% endfor %} | -| **Driver** | {% for cuda in release.cuda %}{{ cuda.driver_min }}+{% unless forloop.last %} | {% endunless %}{% endfor %} | -| **Compute Capability** | {% for cuda in release.cuda %}{% for cc in cuda.compute_capability %}{{ cc.name }} ({% if cc.sm.first %}{{ cc.sm | join: ", " }}{% else %}{{ cc.sm }}{% endif %}){% unless forloop.last %}, {% endunless %}{% endfor %} or newer{% unless forloop.last %} | {% endunless %}{% endfor %} | - -#### Source Builds -{: .fs-5 } - -| Dependency | Version | -|:--|:--| -| **GCC** | {{ release.source_build.gcc }} | -| **CCCL** | {{ release.source_build.cccl }} | -| **nvCOMP** | {{ release.source_build.nvcomp }} | - -
- -{% endfor %} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000000..563146b7cd3 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +[project] +name = "rapids-docs" +version = "0.1.0" +description = "NVIDIA RAPIDS documentation portal" +requires-python = ">=3.12" +dependencies = [ + "beautifulsoup4>=4.13.0", + "boto3>=1.40.0", + "html5lib>=1.1", + "jinja2>=3.1.0", + "myst-parser>=4.0.0", + "nvidia-sphinx-theme", + "python-dateutil>=2.9.0", + "python-frontmatter>=1.1.0", + "pyyaml>=6.0.0", + "sphinx>=8.2.0,<9", + "sphinx-copybutton>=0.5.2", + "sphinx-design>=0.6.1", + "sphinx-notfound-page>=1.1.0", +] + +[dependency-groups] +dev = [ + "pre-commit>=4.2.0", + "pytest>=8.4.0", + "ruff>=0.12.0", +] + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["B", "E", "F", "I", "UP"] + +[tool.ruff.lint.per-file-ignores] +"extensions/rapids_docs.py" = ["E501"] + +[tool.pytest.ini_options] +pythonpath = ["."] +testpaths = ["tests"] diff --git a/release_checklist.md b/release_checklist.md deleted file mode 100644 index fce6052c44e..00000000000 --- a/release_checklist.md +++ /dev/null @@ -1,7 +0,0 @@ -# Release Checklist - -On release day, the following changes need to be made to the site: - -- **Update [\_data/releases.json](_data/releases.json)**: Update versions, dates -- **Update [\_data/docs.yml](_data/docs.yml)**: Verify legacy/stable/nightly versions are enabled/disabled appropriately -- **Update [releases/schedule.md](releases/schedule.md)**: Update release schedule diff --git a/releases/index.md b/releases/index.md deleted file mode 100644 index 94b6a74d387..00000000000 --- a/releases/index.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -layout: default -title: Release Docs -parent: Maintainer Docs -nav_order: 6 -has_children: true ---- - -# Releases -{:.no_toc} - -Releases are planned using the processes and schedules outlined below. diff --git a/releases/schedule.md b/releases/schedule.md deleted file mode 100644 index 72f4989604e..00000000000 --- a/releases/schedule.md +++ /dev/null @@ -1,66 +0,0 @@ ---- -layout: default -nav_order: 3 -parent: Release Docs -grand_parent: Maintainer Docs -title: Release Schedule ---- - -### Intended audience -{:.no_toc} - -Community -{: .label .label-yellow} - -Developers -{: .label .label-green} - -Operations -{: .label .label-purple} - -## Table of contents -{: .no_toc .text-delta } - -1. TOC -{:toc} - -## Current release - -The current release schedule is posted on the [RAPIDS Maintainers Docs]({% link maintainers/index.md %}) page. - -## Completed Releases - -Historical list of completed releases - -{% for release in site.data.previous_releases %} -### Release v{{ release.version }} Schedule - -{% if release.dev %} -Phase | Start | End | Duration --- | -- | -- | -- -Development | {{ release.dev.start | date: "%a, %b %e, %Y" }} | {{ release.dev.end | date: "%a, %b %e, %Y" }} | {{ release.dev.days }} days -[Burn Down]({% link releases/process.md %}#burn-down){% if release.other_burndown %} (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf){% endif %} | {{ release.cudf_burndown.start | date: "%a, %b %e, %Y" }} | {{ release.cudf_burndown.end | date: "%a, %b %e, %Y" }} | {{ release.cudf_burndown.days }} days -{% if release.other_burndown %}[Burn Down]({% link releases/process.md %}#burn-down) (others) | {{ release.other_burndown.start | date: "%a, %b %e, %Y" }} | {{ release.other_burndown.end | date: "%a, %b %e, %Y" }} | {{ release.other_burndown.days }} days -{% endif %}{% if release.cudf_codefreeze %}[Code Freeze/Testing]({% link releases/process.md %}#code-freeze){% if release.other_burndown %} (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf){% endif %} | {{ release.cudf_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ release.cudf_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ release.cudf_codefreeze.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (others) | {{ release.other_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ release.other_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ release.other_codefreeze.days }} days -{% else %}[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) | {{ release.codefreeze.start | date: "%a, %b %e, %Y" }} | {{ release.codefreeze.end | date: "%a, %b %e, %Y" }} | {{ release.codefreeze.days }} days -{% endif %}[Release]({% link releases/process.md %}#releasing) | {{ release.release.start | date: "%a, %b %e, %Y" }} | {{ release.release.end | date: "%a, %b %e, %Y" }} | {{ release.release.days }} days - -{% else %} -{% if release.date %} -Phase | Date --- | -- -Release | {{ release.date | date: "%a, %b %e, %Y" }} -{% else %} -Phase | Start | End | Duration --- | -- | -- | -- -Development (cuDF/RMM{% if release.version >= '23.06' %}/rapids-cmake/{% if release.version <= '24.12' %}cugraph-ops/{% endif %}raft{% endif %}) | {{ release.cudf_dev.start | date: "%a, %b %e, %Y" }} | {{ release.cudf_dev.end | date: "%a, %b %e, %Y" }} | {{ release.cudf_dev.days }} days -Development (others) | {{ release.other_dev.start | date: "%a, %b %e, %Y" }} | {{ release.other_dev.end | date: "%a, %b %e, %Y" }} | {{ release.other_dev.days }} days -[Burn Down]({% link releases/process.md %}#burn-down)(cuDF/RMM{% if release.version >= '23.06' %}/rapids-cmake/{% if release.version <= '24.12' %}cugraph-ops/{% endif %}raft{% endif %}) | {{ release.cudf_burndown.start | date: "%a, %b %e, %Y" }} | {{ release.cudf_burndown.end | date: "%a, %b %e, %Y" }} | {{ release.cudf_burndown.days }} days -[Burn Down]({% link releases/process.md %}#burn-down) (others) | {{ release.other_burndown.start | date: "%a, %b %e, %Y" }} | {{ release.other_burndown.end | date: "%a, %b %e, %Y" }} | {{ release.other_burndown.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (cuDF/RMM{% if release.version >= '23.06' %}/rapids-cmake/{% if release.version <= '24.12' %}cugraph-ops/{% endif %}raft{% endif %}) | {{ release.cudf_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ release.cudf_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ release.cudf_codefreeze.days }} days -[Code Freeze/Testing]({% link releases/process.md %}#code-freeze) (others) | {{ release.other_codefreeze.start | date: "%a, %b %e, %Y" }} | {{ release.other_codefreeze.end | date: "%a, %b %e, %Y" }} | {{ release.other_codefreeze.days }} days -[Release]({% link releases/process.md %}#releasing) | {{ release.release.start | date: "%a, %b %e, %Y" }} | {{ release.release.end | date: "%a, %b %e, %Y" }} | {{ release.release.days }} days -{% endif %} -{% endif %} -{% endfor %} diff --git a/resources/index.md b/resources/index.md deleted file mode 100644 index bd682fec317..00000000000 --- a/resources/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -layout: default -title: Resources -parent: Maintainer Docs -nav_order: 6 -has_children: true ---- - -# Resources -{:.no_toc} - -Resources and detailed information referenced in other sections of this documentation. -{: .fs-6 .fw-300 } diff --git a/scripts/compare_routes.py b/scripts/compare_routes.py new file mode 100644 index 00000000000..feb6de59d78 --- /dev/null +++ b/scripts/compare_routes.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Compare Sphinx routes, headings, and normalized content with Jekyll.""" + +from __future__ import annotations + +import argparse +import json +import re +import unicodedata +from collections import Counter +from pathlib import Path + +from bs4 import BeautifulSoup + +SOURCE_COMMIT = "b6afa0cbf4ddfc4c0a21f7c79b18631f214fd759" +MINIMUM_CONTENT_COVERAGE = 0.90 +HEADING_RENAMES = { + "/install/": {"jupyterlab.": "jupyterlab"}, +} +HEADING_OMISSIONS = { + "/visualization/": {"note: web hosted vs local hosted chart interaction"}, +} + + +def routes(root: Path) -> set[str]: + output = set() + for path in root.rglob("index.html"): + relative = path.relative_to(root) + if relative.parts[0] == "assets": + continue + route = "/" + "/".join(relative.parts[:-1]) + output.add(route.rstrip("/") + "/") + return output + + +def _page_path(root: Path, route: str) -> Path: + if route == "/": + return root / "index.html" + return root / route.strip("/") / "index.html" + + +def _normalize(value: str) -> str: + value = unicodedata.normalize("NFKD", value).lower() + value = value.replace("`", "").replace("\u2019", "'").replace("\u2018", "'") + value = re.sub(r"\s+#$", "", value) + return re.sub(r"\s+", " ", value).strip() + + +def _page_data(path: Path, *, jekyll: bool) -> dict: + soup = BeautifulSoup(path.read_text(errors="ignore"), "html.parser") + main = soup.select_one("#main-content" if jekyll else "article.bd-article") or soup + + if jekyll: + # just-the-docs appends child navigation after the authored content. + for heading in list(main.select("h1,h2,h3,h4,h5,h6")): + if _normalize(heading.get_text(" ", strip=True)) == "table of contents": + for following in list(heading.find_all_next()): + following.decompose() + heading.decompose() + + headings = [ + _normalize(heading.get_text(" ", strip=True)) + for heading in main.select("h1,h2,h3,h4,h5,h6") + ] + for element in main.select("script,style,nav,.toc,.headerlink"): + element.decompose() + text = main.get_text(" ", strip=True) + # The Jekyll visualization includes contain malformed en-dash HTML comment + # delimiters. Browsers display those license comments as text, while MyST + # correctly preserves them as non-visible HTML comments. + text = re.sub( + r"", + " ", + text, + flags=re.IGNORECASE, + ) + text = _normalize(text) + words = Counter(re.findall(r"[a-z0-9]+", text)) + return {"headings": headings, "words": dict(sorted(words.items()))} + + +def _write_manifest(jekyll_site: Path, output: Path) -> None: + pages = { + route: _page_data(_page_path(jekyll_site, route), jekyll=True) + for route in sorted(routes(jekyll_site)) + } + manifest = {"source_commit": SOURCE_COMMIT, "pages": pages} + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(json.dumps(manifest, indent=2, sort_keys=True) + "\n") + print(f"Recorded {len(pages)} Jekyll pages from {SOURCE_COMMIT}") + + +def _compare(manifest_path: Path, sphinx_site: Path) -> None: + manifest = json.loads(manifest_path.read_text()) + if manifest["source_commit"] != SOURCE_COMMIT: + raise SystemExit("Baseline manifest has an unexpected source commit") + + failures = [] + coverages = [] + for route, expected in manifest["pages"].items(): + path = _page_path(sphinx_site, route) + if not path.exists(): + failures.append(f"{route}: route is missing") + continue + + actual = _page_data(path, jekyll=False) + missing_headings = [ + heading + for heading in expected["headings"] + if heading not in HEADING_OMISSIONS.get(route, set()) + if HEADING_RENAMES.get(route, {}).get(heading, heading) not in actual["headings"] + ] + if missing_headings: + failures.append(f"{route}: missing headings: {', '.join(missing_headings)}") + + expected_words = Counter(expected["words"]) + actual_words = Counter(actual["words"]) + coverage = sum((expected_words & actual_words).values()) / sum(expected_words.values()) + coverages.append((coverage, route)) + if coverage < MINIMUM_CONTENT_COVERAGE: + failures.append( + f"{route}: normalized content coverage is {coverage:.1%}, " + f"below {MINIMUM_CONTENT_COVERAGE:.0%}" + ) + + if failures: + raise SystemExit("Jekyll parity validation failed:\n- " + "\n- ".join(failures)) + + minimum, minimum_route = min(coverages) + print( + f"Validated {len(manifest['pages'])} Jekyll routes and headings; " + f"minimum normalized content coverage is {minimum:.1%} ({minimum_route})" + ) + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("baseline", type=Path) + parser.add_argument("sphinx", type=Path) + parser.add_argument( + "--write", + action="store_true", + help="write a baseline manifest from a Jekyll site instead of comparing", + ) + args = parser.parse_args() + + if args.write: + _write_manifest(args.baseline, args.sphinx) + else: + _compare(args.baseline, args.sphinx) + + +if __name__ == "__main__": + main() diff --git a/scripts/migrate_from_jekyll.py b/scripts/migrate_from_jekyll.py new file mode 100644 index 00000000000..92db14b2086 --- /dev/null +++ b/scripts/migrate_from_jekyll.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""One-time content migration from the pinned RAPIDS Jekyll snapshot.""" + +from __future__ import annotations + +import re +import shutil +import sys +from pathlib import Path + +SOURCE = Path(sys.argv[1] if len(sys.argv) > 1 else "/tmp/rapidsai-docs-jekyll-upstream-main") +DESTINATION = Path(__file__).resolve().parents[1] / "source" + + +def split_front_matter(text: str) -> tuple[str, str]: + if not text.startswith("---\n"): + return "", text + _, front_matter, body = text.split("---\n", 2) + return front_matter, body.lstrip("\n") + + +def title_from_front_matter(front_matter: str) -> str: + match = re.search(r'^title:\s*["\']?(.*?)["\']?\s*$', front_matter, re.MULTILINE) + return match.group(1) if match else "" + + +def route_for(source_path: str) -> str: + path = Path(source_path) + if path.suffix == ".md": + path = path.with_suffix("") + parts = list(path.parts) + if parts and parts[-1] == "index": + parts.pop() + route = "/" + "/".join(parts) + return route.rstrip("/") + "/" if route != "/" else route + + +def convert_labels(lines: list[str]) -> list[str]: + colors = { + "yellow": "warning", + "green": "success", + "blue": "info", + "purple": "primary", + } + converted: list[str] = [] + for line in lines: + match = re.fullmatch( + r"\{:\s*\.label\s+\.label-(yellow|green|blue|purple)\s*}", line.strip() + ) + if match and converted: + label = converted.pop().strip() + converted.append(f"{{bdg-{colors[match.group(1)]}}}`{label}`") + continue + if re.fullmatch(r"\{:\s*[^}]*}", line.strip()): + continue + converted.append(line) + return converted + + +def convert_markdown(text: str, title: str) -> str: + text = text.replace("{% raw %}\n", "").replace("{% endraw %}\n", "") + text = text.replace("{{ page.title }}", title) + text = text.replace( + "{{ site.data.releases.stable.version }}", "<>" + ) + text = text.replace( + "{{ site.data.releases.nightly.version }}", "<>" + ) + text = text.replace("{{ site.social.slack.url }}", "https://rapids.ai/slack-invite") + text = text.replace("{{ 'notices/feed.xml' | absolute_url }}", "/notices/feed.xml") + text = re.sub( + r"\{%\s*link\s+([^%]+?)\s*%}", + lambda match: route_for(match.group(1).strip()), + text, + ) + text = re.sub(r'\{:\s*target="_blank"\s*}', "", text) + text = re.sub( + r"\{%\s*include\s+([A-Za-z0-9_.-]+)\.html(?:\s+[^%]*)?%}", + lambda match: f'[% include "_includes/{match.group(1)}.html" %]', + text, + ) + text = "\n".join(convert_labels(text.splitlines())) + "\n" + text = re.sub(r"\n1\. TOC\n(?:\n)?", "\n", text) + return text + + +def migrate_page(relative_path: Path) -> None: + source_path = SOURCE / relative_path + front_matter, body = split_front_matter(source_path.read_text()) + title = title_from_front_matter(front_matter) + converted = convert_markdown(body, title) + destination = DESTINATION / relative_path + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(converted) + + +def migrate_notice(source_path: Path) -> None: + front_matter, body = split_front_matter(source_path.read_text()) + converted = convert_markdown(body, title_from_front_matter(front_matter)) + destination = DESTINATION / "notices" / source_path.name + destination.parent.mkdir(parents=True, exist_ok=True) + destination.write_text(f"---\n{front_matter}---\n\n{converted}") + + +def main() -> None: + for filename in ["404.md", "SECURITY.md", "api.md", "index.md"]: + migrate_page(Path(filename)) + + for directory in [ + "contributing", + "install", + "maintainers", + "notices", + "platform-support", + "releases", + "resources", + "user-guide", + "visualization", + ]: + for source_path in sorted((SOURCE / directory).rglob("*.md")): + migrate_page(source_path.relative_to(SOURCE)) + + for source_path in sorted((SOURCE / "_notices").glob("*.md")): + migrate_notice(source_path) + + for directory in ["licenses"]: + target = DESTINATION / directory + if target.exists(): + shutil.rmtree(target) + shutil.copytree(SOURCE / directory, target) + + +if __name__ == "__main__": + main() diff --git a/scripts/validate_site.py b/scripts/validate_site.py new file mode 100644 index 00000000000..ce64c0e1ae0 --- /dev/null +++ b/scripts/validate_site.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Validate the rendered RAPIDS portal and optional assembled documentation tree.""" + +from __future__ import annotations + +import argparse +from pathlib import Path +from xml.etree import ElementTree + +ROOT = Path(__file__).resolve().parents[1] +SOURCE_NOTICES = ROOT / "source" / "notices" + +REQUIRED_PAGES = [ + "index.html", + "LICENSE", + "SECURITY.md", + "404.html", + "api/index.html", + "contributing/index.html", + "install/index.html", + "maintainers/index.html", + "notices/index.html", + "notices/rdn/index.html", + "notices/rgn/index.html", + "notices/rsn/index.html", + "notices/feed.xml", + "platform-support/index.html", + "releases/schedule/index.html", + "resources/index.html", + "user-guide/index.html", + "visualization/index.html", + "assets/css/custom_nvidia.css", + "assets/js/custom.js", + "licenses/CubinLinker.txt", + "licenses/cugraph-ops-EULA.txt", +] + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("site", type=Path) + parser.add_argument("--full", action="store_true") + args = parser.parse_args() + + missing = [relative for relative in REQUIRED_PAGES if not (args.site / relative).exists()] + notice_ids = sorted(path.stem for path in SOURCE_NOTICES.glob("r[dgs]n[0-9][0-9][0-9][0-9].md")) + notice_pages = list((args.site / "notices").glob("r[dgs]n[0-9][0-9][0-9][0-9]/index.html")) + if len(notice_pages) != len(notice_ids): + missing.append(f"expected {len(notice_ids)} notice pages, found {len(notice_pages)}") + + feed = ElementTree.parse(args.site / "notices" / "feed.xml") + feed_items = feed.findall("./channel/item") + if len(feed_items) != len(notice_ids): + missing.append(f"expected {len(notice_ids)} RSS items, found {len(feed_items)}") + + search_index = (args.site / "searchindex.js").read_text(errors="ignore") + missing_search_notices = [ + notice_id for notice_id in notice_ids if f"notices/{notice_id}" not in search_index + ] + if missing_search_notices: + missing.append(f"{len(missing_search_notices)} individual notices are absent from search") + + home = (args.site / "index.html").read_text(errors="ignore") + required_branding = [ + "nvidia-logo-horiz", + "https://github.com/rapidsai/docs", + "018e2d71-40f3-7e89-90b8-e10ec6012ab0-test", + "assets.adobedtm.com", + "fa-download", + "fa-list-check", + "fa-book", + "fa-code", + "fa-chart-bar", + "fa-cloud", + "fa-wrench", + "fa-bullhorn", + 'class="fab fa-github"', + "fa-slack", + "fa-stack-overflow", + ] + missing.extend( + f"home page branding/telemetry: {value}" for value in required_branding if value not in home + ) + if "fa-twitter" in home or "fa-x-twitter" in home: + missing.append("Twitter/X icon remains on the home page") + + analytics = (args.site / "_static" / "js" / "portal-analytics.js").read_text() + if "G-DLJNCEWKZD" not in analytics or "_satellite.pageBottom" not in analytics: + missing.append("GA4 or Adobe page-bottom telemetry is missing") + + # Theme packages contain unrendered Jinja macro files under ``_static``; + # only rendered site pages should be checked for legacy Liquid syntax. + html_files = [path for path in args.site.rglob("*.html") if "_static" not in path.parts] + stale_liquid = [path for path in html_files if "{%" in path.read_text(errors="ignore")] + if stale_liquid: + missing.append(f"Liquid syntax remains in {len(stale_liquid)} rendered files") + + if args.full: + full_paths = [ + "api/cudf/stable", + "api/cudf/latest", + "api/cudf/nightly", + "deployment/stable/index.html", + "deployment/nightly/index.html", + ] + missing.extend(relative for relative in full_paths if not (args.site / relative).exists()) + + if missing: + raise SystemExit("Site validation failed:\n- " + "\n- ".join(missing)) + + print(f"Validated {len(html_files)} HTML files and {len(notice_pages)} notices") + + +if __name__ == "__main__": + main() diff --git a/404.md b/source/404.md similarity index 67% rename from 404.md rename to source/404.md index 1b788d5795e..236231eb33b 100644 --- a/404.md +++ b/source/404.md @@ -1,9 +1,6 @@ --- -layout: default -permalink: /404.html -nav_exclude: true +orphan: true --- - # Page Not Found We could not find the page you were looking for. @@ -12,9 +9,9 @@ We could not find the page you were looking for. if (window.location.pathname.match(/^\/api.*/)) { var redirectEl = document.createElement("p"); redirectEl.innerHTML = "Redirecting you to the latest documentation in 5 seconds..." - document.getElementById("main-content").appendChild(redirectEl); + (document.querySelector("article") || document.body).appendChild(redirectEl); setTimeout(function() { - window.location.href = '/api'; + window.location.href = '/api/'; }, 5000); } diff --git a/source/SECURITY.md b/source/SECURITY.md new file mode 100644 index 00000000000..8c72c173967 --- /dev/null +++ b/source/SECURITY.md @@ -0,0 +1,44 @@ +--- +orphan: true +--- +# Security + +## Reporting Security Issues + +```{warning} +Do not report security vulnerabilities through public GitHub issues! +``` + +Instead, please submit a private vulnerability report, see below. + +## Reporting a Vulnerability + +1. **NVIDIA Vulnerability Disclosure Program (preferred)** + Submit through the NVIDIA Product Security Incident Response Team (PSIRT) web form () + This is the fastest path to triage and tracking. + +2. **Email NVIDIA PSIRT** + `psirt@nvidia.com` — encrypt sensitive reports with the + [NVIDIA PSIRT PGP key](https://www.nvidia.com/en-us/security/pgp-key). + +3. **GitHub Private Vulnerability Reporting** + Use the **Security and quality** tab on this repository → *Report a vulnerability*. + +## Report Details + +We prefer all communications to be in English. + +Reports should include the following: + +* reproducible example showing how the vulnerability can be exploited +* statement about the impact (including affected versions) + +And we'd appreciate if they also include: + +* statement about whether you are interested in implementing the fix yourself + +## Disclosure Policy + +NVIDIA PSIRT will acknowledge receipt and coordinate triage, fix development, and coordinated disclosure. + +More on NVIDIA's response process: . diff --git a/_data/docs.yml b/source/_data/docs.yml similarity index 99% rename from _data/docs.yml rename to source/_data/docs.yml index 939c48953e9..4e11f9e83e7 100644 --- a/_data/docs.yml +++ b/source/_data/docs.yml @@ -152,7 +152,7 @@ apis: cllink: https://github.com/rapidsai/ucxx/blob/main/CHANGELOG.md versions: # enable or disable links; 0 = disabled, 1 = enabled - # TODO: enable 'legacy' once ucxx legacy version moves to 0.46 in `_data/releases.json` + # TODO: enable 'legacy' once ucxx legacy version moves to 0.46 in `source/_data/releases.json` legacy: 0 stable: 1 nightly: 1 diff --git a/_data/platform_support.yml b/source/_data/platform_support.yml similarity index 100% rename from _data/platform_support.yml rename to source/_data/platform_support.yml diff --git a/_data/previous_releases.json b/source/_data/previous_releases.json similarity index 100% rename from _data/previous_releases.json rename to source/_data/previous_releases.json diff --git a/_data/releases.json b/source/_data/releases.json similarity index 100% rename from _data/releases.json rename to source/_data/releases.json diff --git a/_includes/bokeh.html b/source/_includes/bokeh.html similarity index 99% rename from _includes/bokeh.html rename to source/_includes/bokeh.html index 60141c38b3c..0db103944bd 100644 --- a/_includes/bokeh.html +++ b/source/_includes/bokeh.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('12280').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('12280').textContent); const render_items = [{ "docid": "be8ea7fa-c482-4932-a1a7-5c9577aec07d", "root_ids": ["9175", "9216"], "roots": { "9175": "1bd788b2-2761-45b2-ae08-2caece4b658e", "9216": "6035a5f3-9a74-444c-a581-c1fdfdc020b4" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/datashader.html b/source/_includes/datashader.html similarity index 99% rename from _includes/datashader.html rename to source/_includes/datashader.html index 6225c62d915..8032ded0acb 100644 --- a/_includes/datashader.html +++ b/source/_includes/datashader.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('8788').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('8788').textContent); const render_items = [{ "docid": "ad2f45c0-da49-4f5b-be5f-3406fce1fc1e", "root_ids": ["8756", "8768"], "roots": { "8756": "cf6d710b-1908-4b9d-9787-ff5f9bb4ad79", "8768": "cfb375e0-fabb-4d61-a429-60051184efd6" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/holoviews.html b/source/_includes/holoviews.html similarity index 99% rename from _includes/holoviews.html rename to source/_includes/holoviews.html index a9fa78f184c..1b745794deb 100644 --- a/_includes/holoviews.html +++ b/source/_includes/holoviews.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('4808').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('4808').textContent); const render_items = [{ "docid": "3da20d72-bb10-4dad-8c67-9770ce102c17", "root_ids": ["1002", "1078"], "roots": { "1002": "b48c307e-9b2d-4ab7-9c71-fbce2a52bf4d", "1078": "ef5a0967-6748-4ecf-88c9-f5ba27a4dab0" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/hvplot.html b/source/_includes/hvplot.html similarity index 99% rename from _includes/hvplot.html rename to source/_includes/hvplot.html index 57fd369e6c2..7c8660ae86e 100644 --- a/_includes/hvplot.html +++ b/source/_includes/hvplot.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('8755').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('8755').textContent); const render_items = [{ "docid": "b0727922-be35-4515-982e-b49ca8fa100b", "root_ids": ["4809", "4887"], "roots": { "4809": "b1afe886-1efe-473f-b5b3-b36a7f08c990", "4887": "2d8c4606-21c2-4451-a7e4-8bcd373c50d6" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/plotly.html b/source/_includes/plotly.html similarity index 99% rename from _includes/plotly.html rename to source/_includes/plotly.html index c1017fd5511..47cc22d91d4 100644 --- a/_includes/plotly.html +++ b/source/_includes/plotly.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('9134').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('9134').textContent); const render_items = [{ "docid": "976422fb-2b0d-45f8-81cc-46a6add76aa7", "root_ids": ["8789", "8804"], "roots": { "8789": "ec437eae-b74d-4042-8f47-2da685fda62f", "8804": "565aef76-d9e0-425e-805e-6d78328e3e90" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/seaborn.html b/source/_includes/seaborn.html similarity index 99% rename from _includes/seaborn.html rename to source/_includes/seaborn.html index bdfb8f53fce..eb7b4a2736e 100644 --- a/_includes/seaborn.html +++ b/source/_includes/seaborn.html @@ -15,7 +15,7 @@ Bokeh.safely(function () { (function (root) { function embed_document(root) { - const docs_json = document.getElementById('12346').textContent; + const docs_json = root.rapidsStackPanelPanes(document.getElementById('12346').textContent); const render_items = [{ "docid": "fb398535-f4eb-4012-b6fe-cc28dce90368", "root_ids": ["12281", "12293"], "roots": { "12281": "97a50a14-c50a-4103-8735-b007cde7a0fe", "12293": "bc6f7339-30dc-44ba-a622-1960b0855426" } }]; root.Bokeh.embed.embed_items(docs_json, render_items); } diff --git a/_includes/selector.html b/source/_includes/selector.html similarity index 90% rename from _includes/selector.html rename to source/_includes/selector.html index 6bf2875a5ee..8d0a8274fb2 100644 --- a/_includes/selector.html +++ b/source/_includes/selector.html @@ -10,15 +10,45 @@ } .selector-bg { - background-color: #9943ff; - padding: 1rem; - border-radius: 5px; + background: var(--pst-color-surface); + border: 1px solid var(--pst-color-border); + border-top: 4px solid var(--nv-color-green); + border-radius: 0.25rem; + box-shadow: 0 0.125rem 0.5rem color-mix(in srgb, var(--pst-color-shadow) 55%, transparent); + padding: 1.5rem; } .selector .options-section { display: flex; flex-direction: row; flex-wrap: nowrap; + margin-bottom: 0.35rem; + } + + .selector .options-section-additional { + display: grid; + grid-template-columns: 7em repeat(4, minmax(0, 1fr)); + gap: 0.4rem; + margin: 0.55rem 0 0.75rem; + } + + .selector .options-section-additional .option-label { + align-self: center; + grid-row: span 2; + margin: 0 0.75em 0 0; + width: auto; + } + + .selector .options-section-additional .option { + align-items: center; + display: flex; + font-size: 0.9rem; + justify-content: center; + line-height: 1.25; + margin: 0; + min-height: 2.35rem; + padding: 0.35rem 0.45rem; + text-align: center; } .selector .options-section-specific { @@ -30,54 +60,59 @@ } .selector .options .option-label { - color: white; - width: 6em; + color: var(--pst-color-text-base); + width: 7em; text-transform: uppercase; - font-weight: 600; - margin-top: 0.6em; - margin-right: 0.6em; + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.025em; + margin-top: 0.65em; + margin-right: 0.75em; text-align: right; } .selector .options .option { - background: #e3e3e3; - color: #3c3c3c; + background: var(--pst-color-background); + border: 1px solid var(--pst-color-border); + color: var(--pst-color-text-base); flex: 1 1; margin: 0.2em; - padding: 0.3em; + padding: 0.45em 0.55em; cursor: pointer; line-height: 1.5em; - box-shadow: 2px 2px 2px rgba(10, 10, 10, 0.4); - border-radius: 2px; + box-shadow: none; + border-radius: 0.25rem; } .selector .options .option:hover, - .selector .options .option.active:hover, .cmd-button:hover { - background: #ffb500; - box-shadow: 1px 1px 0px rgba(10, 10, 10, 0.7); - -webkit-transition: background-color 0.3s ease-in-out; - -moz-transition: background-color 0.3s ease-in-out; - -o-transition: background-color 0.3s ease-in-out; - transition: background-color 0.3s ease-in-out; - transition: box-shadow 0.1s ease-in-out; + background: color-mix(in srgb, var(--nv-color-green) 18%, var(--pst-color-background)); + border-color: var(--nv-color-green); + box-shadow: none; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; + } + + .selector .options .option.active:hover { + background: var(--nv-color-green); + border-color: var(--nv-color-green-2); } .selector .options .option:active, .selector .options .option.active:active, .cmd-button:active { - color: #9943ff; + color: var(--pst-color-text-base); } .selector .options .note code { - background: #e3e3e3; - color: #000000; + background: var(--pst-color-background); + border: 1px solid var(--pst-color-border-muted); + color: var(--pst-color-text-base); padding: 0.08em 0.15em; border-radius: 4px; } .selector .options .option-blank { - color: white; + color: var(--pst-color-text-base); flex: 1 1; margin: 0.2em; padding: 0.3em; @@ -94,12 +129,13 @@ } .selector .options .option-notice { - color: #e0e0e0; + color: var(--pst-color-text-base); flex: 1 1; margin: 0.2em; padding: 0.3em; line-height: 1.5em; - background: #424242; + background: var(--pst-color-background); + border-left: 4px solid var(--nv-color-green); } .selector .options .option-notice span { @@ -126,22 +162,26 @@ } .selector .options .active { - background: #36c9dd; - font-weight: 600; - color: #424242; + background: var(--nv-color-green); + border-color: var(--nv-color-green-2); + color: #1a1a1a; + font-weight: 700; } .selector .options .active.advanced { - background: #ffb500; - font-weight: 600; - color: #424242; + background: var(--nv-color-green); + border-color: var(--nv-color-green-2); + color: #1a1a1a; + font-weight: 700; } .selector .options .disabled, .selector .options .perm-disabled { - background: #a785e7 !important; - color: #706880; + background: var(--pst-color-surface) !important; + border-color: var(--pst-color-border-muted); + color: var(--pst-color-text-muted); cursor: not-allowed; + opacity: 0.65; } .cmd { @@ -150,33 +190,42 @@ } .cmd-label { - color: white; - width: 6em; + color: var(--pst-color-text-base); + width: 7em; text-transform: uppercase; - font-weight: 600; + font-size: 0.875rem; + font-weight: 700; + letter-spacing: 0.025em; margin-top: 1rem; - margin-right: 0.6em; + margin-right: 0.75em; vertical-align: top; text-align: right } .cmd-box { + border: 1px solid var(--pst-color-border); cursor: text; overflow: auto; - width: calc(100% - 6em); + width: calc(100% - 7em); border-radius: 4px; } + .cmd-box pre { + margin: 0; + } + .cmd-button { - background: #e3e3e3; + background: var(--pst-color-background); + border: 1px solid var(--pst-color-primary); border-radius: 0.3rem; - color: #3c3c3c; + color: var(--pst-color-text-base); flex: 1 1; margin: 0.5em; padding: 0.6em; cursor: pointer; line-height: 1.5em; - box-shadow: 2px 2px 2px rgb(10 10 10 / 40%); + box-shadow: none; + text-decoration: none; } .hidden { @@ -214,7 +263,7 @@ } .selector .option.active .fas { - color: #3c3c3c; + color: #1a1a1a; } .option-note { @@ -297,7 +346,7 @@ class="option" x-text="package"> -
+
Additional Packages
-
+
RAPIDS Packages
- -
-
-
- +
+ +
+
Additional Packages
@@ -356,22 +376,25 @@
-
+
Packages
- -
-
-
- +
+ +
+
From 9f1e7f6717e2c1fedf5ed30e3f7994529f644be5 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Sat, 11 Jul 2026 01:12:08 +0000 Subject: [PATCH 16/22] Split RAPIDS docs extension into modules Signed-off-by: Bradley Dice --- extensions/rapids_docs.py | 530 --------------------- extensions/rapids_docs/__init__.py | 16 + extensions/rapids_docs/api.py | 58 +++ extensions/rapids_docs/data.py | 42 ++ extensions/rapids_docs/dates.py | 26 + extensions/rapids_docs/lifecycle.py | 73 +++ extensions/rapids_docs/notices.py | 126 +++++ extensions/rapids_docs/output.py | 30 ++ extensions/rapids_docs/platform_support.py | 84 ++++ extensions/rapids_docs/releases.py | 133 ++++++ pyproject.toml | 2 +- tests/test_rendering.py | 82 +++- 12 files changed, 647 insertions(+), 555 deletions(-) delete mode 100644 extensions/rapids_docs.py create mode 100644 extensions/rapids_docs/__init__.py create mode 100644 extensions/rapids_docs/api.py create mode 100644 extensions/rapids_docs/data.py create mode 100644 extensions/rapids_docs/dates.py create mode 100644 extensions/rapids_docs/lifecycle.py create mode 100644 extensions/rapids_docs/notices.py create mode 100644 extensions/rapids_docs/output.py create mode 100644 extensions/rapids_docs/platform_support.py create mode 100644 extensions/rapids_docs/releases.py diff --git a/extensions/rapids_docs.py b/extensions/rapids_docs.py deleted file mode 100644 index a366e6e3f0c..00000000000 --- a/extensions/rapids_docs.py +++ /dev/null @@ -1,530 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. -# SPDX-License-Identifier: Apache-2.0 - -"""Data-driven rendering and notice support for the RAPIDS documentation portal.""" - -from __future__ import annotations - -import email.utils -import html -import json -import re -import shutil -from datetime import UTC, datetime -from pathlib import Path -from xml.etree import ElementTree - -import frontmatter -import yaml -from bs4 import BeautifulSoup -from dateutil import parser as date_parser -from jinja2 import Environment, FileSystemLoader, StrictUndefined - - -def _source_dir(app) -> Path: - return Path(app.srcdir) - - -def _date(value) -> datetime: - if isinstance(value, datetime): - return value - if hasattr(value, "year") and hasattr(value, "month") and hasattr(value, "day"): - return datetime(value.year, value.month, value.day) - return date_parser.parse(str(value)) - - -def _long_date(value) -> str: - parsed = _date(value) - return f"{parsed.strftime('%B')} {parsed.day}, {parsed.year}" - - -def _short_date(value) -> str: - parsed = _date(value) - return f"{parsed.strftime('%a, %b')} {parsed.day}, {parsed.year}" - - -def _load_data(app) -> dict: - data_dir = _source_dir(app) / "_data" - with (data_dir / "docs.yml").open() as file: - docs = yaml.safe_load(file) - with (data_dir / "platform_support.yml").open() as file: - platform_support = yaml.safe_load(file) - with (data_dir / "releases.json").open() as file: - releases = json.load(file) - with (data_dir / "previous_releases.json").open() as file: - previous_releases = json.load(file) - - notices = [] - for path in sorted((_source_dir(app) / "notices").glob("r[dgs]n[0-9][0-9][0-9][0-9].md")): - post = frontmatter.load(path) - metadata = dict(post.metadata) - metadata["docname"] = f"notices/{path.stem}" - metadata["body"] = post.content - notices.append(metadata) - - return { - "docs": docs, - "notices": notices, - "platform_support": platform_support, - "previous_releases": previous_releases, - "releases": releases, - } - - -def _version_label(project: dict, version_name: str, releases: dict) -> str: - override = project.get("version-overrides", {}).get(version_name) - if override: - return str(override) - version_key = "ucxx_version" if "ucxx" in project["path"].lower() else "version" - return str(releases[version_name][version_key]) - - -def _api_docs(data: dict, section: str) -> str: - cards = [] - for project in data["docs"][section].values(): - if project.get("hidden", False): - continue - versions = [] - for name in ("nightly", "stable", "legacy"): - if project["versions"].get(name) == 1: - label = _version_label(project, name, data["releases"]) - versions.append(f"[{name.title()} ({label})](/api/{project['path']}/{name}/)") - links = [] - if project.get("cllink"): - links.append(f"[Changelog]({project['cllink']})") - links.append(f"[GitHub]({project['ghlink']})") - footer = [] - if versions: - footer.extend(["**Documentation:** " + " · ".join(versions), ""]) - footer.append("**Resources:** " + " · ".join(links)) - cards.append( - "\n".join( - [ - f":::{{grid-item-card}} {project['name']}", - ":class-card: rapids-api-card", - ":class-title: rapids-api-card-title", - ":class-footer: rapids-api-card-footer", - "", - project["desc"], - "", - "+++", - *footer, - ":::", - ] - ) - ) - return "\n".join( - [ - "::::{grid} 1 1 1 1", - ":gutter: 2", - ":class-container: rapids-api-grid", - "", - "\n\n".join(cards), - "::::", - ] - ) - - -def _compute_capability(cuda: dict) -> str: - capabilities = [] - for capability in cuda["compute_capability"]: - sms = capability["sm"] - if not isinstance(sms, list): - sms = [sms] - capabilities.append(f"{capability['name']} ({', '.join(map(str, sms))})") - return ", ".join(capabilities) + " or newer" - - -def _platform_support(data: dict) -> str: - releases = data["platform_support"]["releases"] - links = ", ".join( - f"[{release['version']}{' (nightly)' if release.get('nightly') else ''}]" - f"(#rapids-{str(release['version']).replace('.', '')})" - for release in releases - ) - sections = [f"**Releases:** {links}"] - for release in releases: - title = f"## RAPIDS {release['version']}" - if release.get("nightly"): - title += " (nightly)" - cuda_headers = [f"CUDA {cuda['major']}" for cuda in release["cuda"]] - cuda_rows = [ - ( - "Toolkit", - [ - f"{cuda['toolkit_min']}" - + ( - f" - {cuda['toolkit_max']}" - if cuda["toolkit_min"] != cuda["toolkit_max"] - else "" - ) - for cuda in release["cuda"] - ], - ), - ("Driver", [f"{cuda['driver_min']}+" for cuda in release["cuda"]]), - ("Compute Capability", [_compute_capability(cuda) for cuda in release["cuda"]]), - ] - table = [ - "| | " + " | ".join(cuda_headers) + " |", - "|:--|" + "|".join(":--" for _ in cuda_headers) + "|", - ] - table.extend(f"| **{name}** | " + " | ".join(values) + " |" for name, values in cuda_rows) - sections.append( - "\n".join( - [ - "---", - "", - title, - "", - '### Operating Systems', - "", - f'- ' - f"**Linux (glibc {release['glibc_min']}+):** {', '.join(release['cpu_arch'])} " - f"(tested on {', '.join(release['os_support'])})", - '- ' - "**Windows:** Supported via [WSL](/install/#wsl2) with a compatible Linux distribution", - "", - '### Python', - "", - f"**{', '.join(release['python'])}**", - "", - '### CUDA', - "", - *table, - "", - '### Source Builds', - "", - "| Dependency | Version |", - "|:--|:--|", - f"| **GCC** | {release['source_build']['gcc']} |", - f"| **CCCL** | {release['source_build']['cccl']} |", - f"| **nvCOMP** | {release['source_build']['nvcomp']} |", - ] - ) - ) - return "\n\n".join(sections) - - -def _schedule_table(release: dict) -> str: - rows = [ - ("Development", release["dev"]), - ( - "[Burn Down](/releases/process/#burn-down) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", - release["cudf_burndown"], - ), - ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), - ( - "[Code Freeze/Testing](/releases/process/#code-freeze) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", - release["cudf_codefreeze"], - ), - ( - "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", - release["other_codefreeze"], - ), - ("[Release](/releases/process/#releasing)", release["release"]), - ] - output = ["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"] - output.extend( - f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" - for name, values in rows - ) - return "\n".join(output) - - -def _current_schedules(data: dict) -> str: - releases = data["releases"] - return "\n\n".join( - [ - f"## Release v{releases['nightly']['version']} Schedule", - "**NOTE:** *Dates are subject to change at any time. Completed release schedules are posted " - "[here](/releases/schedule/).*", - _schedule_table(releases["nightly"]), - f"## *PROPOSED* Release v{releases['next_nightly']['version']} Schedule", - _schedule_table(releases["next_nightly"]), - ] - ) - - -def _old_project_group(version: str) -> str: - group = "cuDF/RMM" - if version >= "23.06": - group += "/rapids-cmake/" - if version <= "24.12": - group += "cugraph-ops/" - group += "raft" - return group - - -def _previous_schedules(data: dict) -> str: - sections = [] - for release in data["previous_releases"]: - output = [f"### Release v{release['version']} Schedule", ""] - if release.get("dev"): - rows = [("Development", release["dev"])] - group_suffix = ( - " (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf)" - if release.get("other_burndown") - else "" - ) - rows.append( - ( - f"[Burn Down](/releases/process/#burn-down){group_suffix}", - release.get("cudf_burndown") or release["burndown"], - ) - ) - if release.get("other_burndown"): - rows.append( - ( - "[Burn Down](/releases/process/#burn-down) (others)", - release["other_burndown"], - ) - ) - if release.get("cudf_codefreeze"): - rows.append( - ( - f"[Code Freeze/Testing](/releases/process/#code-freeze){group_suffix}", - release["cudf_codefreeze"], - ) - ) - rows.append( - ( - "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", - release["other_codefreeze"], - ) - ) - else: - rows.append( - ("[Code Freeze/Testing](/releases/process/#code-freeze)", release["codefreeze"]) - ) - rows.append(("[Release](/releases/process/#releasing)", release["release"])) - output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) - output.extend( - f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" - for name, values in rows - ) - elif release.get("date"): - output.extend( - ["| Phase | Date |", "|:--|:--|", f"| Release | {_short_date(release['date'])} |"] - ) - else: - group = _old_project_group(release["version"]) - rows = [ - (f"Development ({group})", release["cudf_dev"]), - ("Development (others)", release["other_dev"]), - (f"[Burn Down](/releases/process/#burn-down) ({group})", release["cudf_burndown"]), - ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), - ( - f"[Code Freeze/Testing](/releases/process/#code-freeze) ({group})", - release["cudf_codefreeze"], - ), - ( - "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", - release["other_codefreeze"], - ), - ("[Release](/releases/process/#releasing)", release["release"]), - ] - output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) - output.extend( - f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" - for name, values in rows - ) - sections.append("\n".join(output)) - return "\n\n".join(sections) - - -def _notice_date(notice: dict) -> datetime: - return _date(notice.get("notice_updated") or notice["notice_created"]) - - -def _notice_table(data: dict, notice_type: str | None = None, pinned: bool = False) -> str: - notices = data["notices"] - if notice_type: - notices = [notice for notice in notices if notice["notice_type"] == notice_type] - if pinned: - notices = [ - notice for notice in notices if str(notice.get("notice_pin", "")).lower() == "true" - ] - notices = sorted(notices, key=_notice_date, reverse=True) - if not notices: - return "## No current notices" - output = [ - "| Notice | Title | Topic | RAPIDS Version | Updated |", - "|:--|:--|:--|:--|:--|", - ] - for notice in notices: - updated = notice.get("notice_updated") - if not updated or _date(updated).date() == _date(notice["notice_created"]).date(): - updated = notice["notice_created"] - output.append( - f"| **{notice['notice_type'].upper()} {notice['notice_id']}**
**{notice['notice_status']}** " - f"| [{notice['title']}](/notices/{Path(notice['docname']).name}/) " - f"| {notice['notice_topic']} | {notice['notice_rapids_version']} | {_long_date(updated)} |" - ) - return "\n".join(output) - - -def _jinja_environment(app) -> Environment: - return Environment( - loader=FileSystemLoader(app.srcdir), - undefined=StrictUndefined, - autoescape=False, - keep_trailing_newline=True, - ) - - -def _context(app) -> dict: - data = app.rapids_portal_data - return { - **data, - "api_docs": lambda section: _api_docs(data, section), - "current_schedules": lambda: _current_schedules(data), - "notice_table": lambda notice_type=None, pinned=False: _notice_table( - data, notice_type, pinned - ), - "platform_support_content": lambda: _platform_support(data), - "previous_schedules": lambda: _previous_schedules(data), - } - - -def _builder_inited(app) -> None: - app.rapids_portal_data = _load_data(app) - app.rapids_portal_jinja = _jinja_environment(app) - - -def _notice_header(metadata: dict) -> str: - updated = metadata.get("notice_updated") - if not updated or _date(updated).date() == _date(metadata["notice_created"]).date(): - updated_display = "N/A" - else: - updated_display = _long_date(updated) - return "\n".join( - [ - "---", - "orphan: true", - "---", - f"# {metadata['notice_type'].upper()} {metadata['notice_id']} - {metadata['title']}", - "", - "| | |", - "|:--|:--|", - f"| **Author** | {metadata['notice_author']} |", - f"| **Status** | **{metadata['notice_status']}** |", - f"| **Topic** | {metadata['notice_topic']} |", - f"| **RAPIDS Version** | {metadata['notice_rapids_version']} |", - f"| **Created** | {_long_date(metadata['notice_created'])} |", - f"| **Updated** | {updated_display} |", - "", - ] - ) - - -_GITHUB_ALERT_RE = re.compile( - r"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n((?:>.*(?:\n|$))*)", - re.MULTILINE, -) - - -def _convert_github_alerts(text: str) -> str: - """Convert GitHub alerts to MyST admonitions for the rendered portal.""" - - def replace(match: re.Match) -> str: - body = "\n".join( - line.removeprefix("> ").removeprefix(">") for line in match.group(2).splitlines() - ) - return f"```{{{match.group(1).lower()}}}\n{body}\n```\n" - - return _GITHUB_ALERT_RE.sub(replace, text) - - -def _source_read(app, docname: str, source: list[str]) -> None: - raw = source[0] - if docname.startswith("notices/") and Path(docname).name[:3] in {"rdn", "rgn", "rsn"}: - post = frontmatter.loads(raw) - raw = _notice_header(post.metadata) + post.content - elif docname == "SECURITY": - raw = "---\norphan: true\n---\n\n" + _convert_github_alerts(raw) - template = app.rapids_portal_jinja.from_string(raw) - source[0] = template.render(_context(app)) - - -def _rss_date(value) -> str: - parsed = _date(value) - if parsed.tzinfo is None: - parsed = parsed.replace(tzinfo=UTC) - return email.utils.format_datetime(parsed) - - -def _build_rss(app, exception) -> None: - if exception is not None or app.builder.name not in {"html", "dirhtml"}: - return - data = app.rapids_portal_data - ElementTree.register_namespace("atom", "http://www.w3.org/2005/Atom") - rss = ElementTree.Element("rss", version="2.0") - channel = ElementTree.SubElement(rss, "channel") - ElementTree.SubElement(channel, "title").text = "NVIDIA RAPIDS Documentation - Notices" - ElementTree.SubElement( - channel, "description" - ).text = "Notices communicate and document changes in RAPIDS for contributors, developers, users, and the community." - ElementTree.SubElement(channel, "link").text = "https://docs.rapids.ai/notices/" - ElementTree.SubElement( - channel, - "{http://www.w3.org/2005/Atom}link", - href="https://docs.rapids.ai/notices/feed.xml", - rel="self", - type="application/rss+xml", - ) - now = email.utils.format_datetime(datetime.now(UTC)) - ElementTree.SubElement(channel, "pubDate").text = now - ElementTree.SubElement(channel, "lastBuildDate").text = now - ElementTree.SubElement(channel, "generator").text = "Sphinx" - - for notice in sorted(data["notices"], key=_notice_date, reverse=True): - item = ElementTree.SubElement(channel, "item") - ElementTree.SubElement(item, "title").text = str(notice["title"]) - output_path = Path(app.outdir) / notice["docname"] / "index.html" - if output_path.exists(): - soup = BeautifulSoup(output_path.read_text(), "html.parser") - article = soup.select_one("article.bd-article") or soup.select_one("main") - description = article.decode_contents() if article else notice["body"] - else: - description = notice["body"] - ElementTree.SubElement(item, "description").text = html.unescape(description) - published = notice.get("notice_updated") or notice["notice_created"] - ElementTree.SubElement(item, "pubDate").text = _rss_date(published) - url = f"https://docs.rapids.ai/notices/{Path(notice['docname']).name}/" - ElementTree.SubElement(item, "link").text = url - ElementTree.SubElement(item, "guid", isPermaLink="true").text = url - for category in [*notice.get("tags", []), *notice.get("categories", [])]: - ElementTree.SubElement(item, "category").text = str(category) - - output = Path(app.outdir) / "notices" / "feed.xml" - output.parent.mkdir(parents=True, exist_ok=True) - ElementTree.ElementTree(rss).write(output, encoding="utf-8", xml_declaration=True) - - -def _copy_portal_files(app, exception) -> None: - """Copy trees whose root paths are part of the published site interface.""" - if exception is not None or app.builder.name not in {"html", "dirhtml"}: - return - source_dir = _source_dir(app) - output_dir = Path(app.outdir) - for directory in ("assets", "licenses"): - shutil.copytree( - source_dir / directory, - output_dir / directory, - dirs_exist_ok=True, - ) - for filename in ("LICENSE", "SECURITY.md"): - shutil.copy2(source_dir / filename, output_dir / filename) - - # ``dirhtml`` correctly creates pretty URLs everywhere except the hosting - # platform's required top-level error document. sphinx-notfound-page has - # already rewritten this page's resource and navigation links as absolute. - shutil.copy2(output_dir / "404" / "index.html", output_dir / "404.html") - - -def setup(app): - app.connect("builder-inited", _builder_inited) - app.connect("source-read", _source_read) - app.connect("build-finished", _build_rss) - app.connect("build-finished", _copy_portal_files) - return {"version": "1.0", "parallel_read_safe": False, "parallel_write_safe": True} diff --git a/extensions/rapids_docs/__init__.py b/extensions/rapids_docs/__init__.py new file mode 100644 index 00000000000..1e937586298 --- /dev/null +++ b/extensions/rapids_docs/__init__.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Data-driven rendering and notice support for the RAPIDS documentation portal.""" + +from .lifecycle import _builder_inited, _source_read +from .notices import _build_rss +from .output import _copy_portal_files + + +def setup(app): + app.connect("builder-inited", _builder_inited) + app.connect("source-read", _source_read) + app.connect("build-finished", _build_rss) + app.connect("build-finished", _copy_portal_files) + return {"version": "1.0", "parallel_read_safe": False, "parallel_write_safe": True} diff --git a/extensions/rapids_docs/api.py b/extensions/rapids_docs/api.py new file mode 100644 index 00000000000..85e34289258 --- /dev/null +++ b/extensions/rapids_docs/api.py @@ -0,0 +1,58 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Render the API documentation listings.""" + + +def _version_label(project: dict, version_name: str, releases: dict) -> str: + override = project.get("version-overrides", {}).get(version_name) + if override: + return str(override) + version_key = "ucxx_version" if "ucxx" in project["path"].lower() else "version" + return str(releases[version_name][version_key]) + + +def _api_docs(data: dict, section: str) -> str: + cards = [] + for project in data["docs"][section].values(): + if project.get("hidden", False): + continue + versions = [] + for name in ("nightly", "stable", "legacy"): + if project["versions"].get(name) == 1: + label = _version_label(project, name, data["releases"]) + versions.append(f"[{name.title()} ({label})](/api/{project['path']}/{name}/)") + links = [] + if project.get("cllink"): + links.append(f"[Changelog]({project['cllink']})") + links.append(f"[GitHub]({project['ghlink']})") + footer = [] + if versions: + footer.extend(["**Documentation:** " + " · ".join(versions), ""]) + footer.append("**Resources:** " + " · ".join(links)) + cards.append( + "\n".join( + [ + f":::{{grid-item-card}} {project['name']}", + ":class-card: rapids-api-card", + ":class-title: rapids-api-card-title", + ":class-footer: rapids-api-card-footer", + "", + project["desc"], + "", + "+++", + *footer, + ":::", + ] + ) + ) + return "\n".join( + [ + "::::{grid} 1 1 1 1", + ":gutter: 2", + ":class-container: rapids-api-grid", + "", + "\n\n".join(cards), + "::::", + ] + ) diff --git a/extensions/rapids_docs/data.py b/extensions/rapids_docs/data.py new file mode 100644 index 00000000000..223c6d6f9c0 --- /dev/null +++ b/extensions/rapids_docs/data.py @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Load the portal's structured source data.""" + +import json +from pathlib import Path + +import frontmatter +import yaml + + +def _source_dir(app) -> Path: + return Path(app.srcdir) + + +def _load_data(app) -> dict: + data_dir = _source_dir(app) / "_data" + with (data_dir / "docs.yml").open() as file: + docs = yaml.safe_load(file) + with (data_dir / "platform_support.yml").open() as file: + platform_support = yaml.safe_load(file) + with (data_dir / "releases.json").open() as file: + releases = json.load(file) + with (data_dir / "previous_releases.json").open() as file: + previous_releases = json.load(file) + + notices = [] + for path in sorted((_source_dir(app) / "notices").glob("r[dgs]n[0-9][0-9][0-9][0-9].md")): + post = frontmatter.load(path) + metadata = dict(post.metadata) + metadata["docname"] = f"notices/{path.stem}" + metadata["body"] = post.content + notices.append(metadata) + + return { + "docs": docs, + "notices": notices, + "platform_support": platform_support, + "previous_releases": previous_releases, + "releases": releases, + } diff --git a/extensions/rapids_docs/dates.py b/extensions/rapids_docs/dates.py new file mode 100644 index 00000000000..6b15cd64e1c --- /dev/null +++ b/extensions/rapids_docs/dates.py @@ -0,0 +1,26 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Shared date parsing and formatting helpers.""" + +from datetime import datetime + +from dateutil import parser as date_parser + + +def _date(value) -> datetime: + if isinstance(value, datetime): + return value + if hasattr(value, "year") and hasattr(value, "month") and hasattr(value, "day"): + return datetime(value.year, value.month, value.day) + return date_parser.parse(str(value)) + + +def _long_date(value) -> str: + parsed = _date(value) + return f"{parsed.strftime('%B')} {parsed.day}, {parsed.year}" + + +def _short_date(value) -> str: + parsed = _date(value) + return f"{parsed.strftime('%a, %b')} {parsed.day}, {parsed.year}" diff --git a/extensions/rapids_docs/lifecycle.py b/extensions/rapids_docs/lifecycle.py new file mode 100644 index 00000000000..dc5c8b71526 --- /dev/null +++ b/extensions/rapids_docs/lifecycle.py @@ -0,0 +1,73 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Manage Sphinx lifecycle events and source rendering.""" + +import re +from pathlib import Path + +import frontmatter +from jinja2 import Environment, FileSystemLoader, StrictUndefined + +from .api import _api_docs +from .data import _load_data +from .notices import _notice_header, _notice_table +from .platform_support import _platform_support +from .releases import _current_schedules, _previous_schedules + + +def _jinja_environment(app) -> Environment: + return Environment( + loader=FileSystemLoader(app.srcdir), + undefined=StrictUndefined, + autoescape=False, + keep_trailing_newline=True, + ) + + +def _context(app) -> dict: + data = app.rapids_portal_data + return { + **data, + "api_docs": lambda section: _api_docs(data, section), + "current_schedules": lambda: _current_schedules(data), + "notice_table": lambda notice_type=None, pinned=False: _notice_table( + data, notice_type, pinned + ), + "platform_support_content": lambda: _platform_support(data), + "previous_schedules": lambda: _previous_schedules(data), + } + + +def _builder_inited(app) -> None: + app.rapids_portal_data = _load_data(app) + app.rapids_portal_jinja = _jinja_environment(app) + + +_GITHUB_ALERT_RE = re.compile( + r"^> \[!(NOTE|TIP|IMPORTANT|WARNING|CAUTION)\]\n((?:>.*(?:\n|$))*)", + re.MULTILINE, +) + + +def _convert_github_alerts(text: str) -> str: + """Convert GitHub alerts to MyST admonitions for the rendered portal.""" + + def replace(match: re.Match) -> str: + body = "\n".join( + line.removeprefix("> ").removeprefix(">") for line in match.group(2).splitlines() + ) + return f"```{{{match.group(1).lower()}}}\n{body}\n```\n" + + return _GITHUB_ALERT_RE.sub(replace, text) + + +def _source_read(app, docname: str, source: list[str]) -> None: + raw = source[0] + if docname.startswith("notices/") and Path(docname).name[:3] in {"rdn", "rgn", "rsn"}: + post = frontmatter.loads(raw) + raw = _notice_header(post.metadata) + post.content + elif docname == "SECURITY": + raw = "---\norphan: true\n---\n\n" + _convert_github_alerts(raw) + template = app.rapids_portal_jinja.from_string(raw) + source[0] = template.render(_context(app)) diff --git a/extensions/rapids_docs/notices.py b/extensions/rapids_docs/notices.py new file mode 100644 index 00000000000..8127e499545 --- /dev/null +++ b/extensions/rapids_docs/notices.py @@ -0,0 +1,126 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Render notices and generate their RSS feed.""" + +import email.utils +import html +from datetime import UTC, datetime +from pathlib import Path +from xml.etree import ElementTree + +from bs4 import BeautifulSoup + +from .dates import _date, _long_date + + +def _notice_date(notice: dict) -> datetime: + return _date(notice.get("notice_updated") or notice["notice_created"]) + + +def _notice_table(data: dict, notice_type: str | None = None, pinned: bool = False) -> str: + notices = data["notices"] + if notice_type: + notices = [notice for notice in notices if notice["notice_type"] == notice_type] + if pinned: + notices = [ + notice for notice in notices if str(notice.get("notice_pin", "")).lower() == "true" + ] + notices = sorted(notices, key=_notice_date, reverse=True) + if not notices: + return "## No current notices" + output = [ + "| Notice | Title | Topic | RAPIDS Version | Updated |", + "|:--|:--|:--|:--|:--|", + ] + for notice in notices: + updated = notice.get("notice_updated") + if not updated or _date(updated).date() == _date(notice["notice_created"]).date(): + updated = notice["notice_created"] + output.append( + f"| **{notice['notice_type'].upper()} {notice['notice_id']}**
**{notice['notice_status']}** " + f"| [{notice['title']}](/notices/{Path(notice['docname']).name}/) " + f"| {notice['notice_topic']} | {notice['notice_rapids_version']} | {_long_date(updated)} |" + ) + return "\n".join(output) + + +def _notice_header(metadata: dict) -> str: + updated = metadata.get("notice_updated") + if not updated or _date(updated).date() == _date(metadata["notice_created"]).date(): + updated_display = "N/A" + else: + updated_display = _long_date(updated) + return "\n".join( + [ + "---", + "orphan: true", + "---", + f"# {metadata['notice_type'].upper()} {metadata['notice_id']} - {metadata['title']}", + "", + "| | |", + "|:--|:--|", + f"| **Author** | {metadata['notice_author']} |", + f"| **Status** | **{metadata['notice_status']}** |", + f"| **Topic** | {metadata['notice_topic']} |", + f"| **RAPIDS Version** | {metadata['notice_rapids_version']} |", + f"| **Created** | {_long_date(metadata['notice_created'])} |", + f"| **Updated** | {updated_display} |", + "", + ] + ) + + +def _rss_date(value) -> str: + parsed = _date(value) + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=UTC) + return email.utils.format_datetime(parsed) + + +def _build_rss(app, exception) -> None: + if exception is not None or app.builder.name not in {"html", "dirhtml"}: + return + data = app.rapids_portal_data + ElementTree.register_namespace("atom", "http://www.w3.org/2005/Atom") + rss = ElementTree.Element("rss", version="2.0") + channel = ElementTree.SubElement(rss, "channel") + ElementTree.SubElement(channel, "title").text = "NVIDIA RAPIDS Documentation - Notices" + ElementTree.SubElement( + channel, "description" + ).text = "Notices communicate and document changes in RAPIDS for contributors, developers, users, and the community." + ElementTree.SubElement(channel, "link").text = "https://docs.rapids.ai/notices/" + ElementTree.SubElement( + channel, + "{http://www.w3.org/2005/Atom}link", + href="https://docs.rapids.ai/notices/feed.xml", + rel="self", + type="application/rss+xml", + ) + now = email.utils.format_datetime(datetime.now(UTC)) + ElementTree.SubElement(channel, "pubDate").text = now + ElementTree.SubElement(channel, "lastBuildDate").text = now + ElementTree.SubElement(channel, "generator").text = "Sphinx" + + for notice in sorted(data["notices"], key=_notice_date, reverse=True): + item = ElementTree.SubElement(channel, "item") + ElementTree.SubElement(item, "title").text = str(notice["title"]) + output_path = Path(app.outdir) / notice["docname"] / "index.html" + if output_path.exists(): + soup = BeautifulSoup(output_path.read_text(), "html.parser") + article = soup.select_one("article.bd-article") or soup.select_one("main") + description = article.decode_contents() if article else notice["body"] + else: + description = notice["body"] + ElementTree.SubElement(item, "description").text = html.unescape(description) + published = notice.get("notice_updated") or notice["notice_created"] + ElementTree.SubElement(item, "pubDate").text = _rss_date(published) + url = f"https://docs.rapids.ai/notices/{Path(notice['docname']).name}/" + ElementTree.SubElement(item, "link").text = url + ElementTree.SubElement(item, "guid", isPermaLink="true").text = url + for category in [*notice.get("tags", []), *notice.get("categories", [])]: + ElementTree.SubElement(item, "category").text = str(category) + + output = Path(app.outdir) / "notices" / "feed.xml" + output.parent.mkdir(parents=True, exist_ok=True) + ElementTree.ElementTree(rss).write(output, encoding="utf-8", xml_declaration=True) diff --git a/extensions/rapids_docs/output.py b/extensions/rapids_docs/output.py new file mode 100644 index 00000000000..6c047c68073 --- /dev/null +++ b/extensions/rapids_docs/output.py @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Copy additional files into the built portal.""" + +import shutil +from pathlib import Path + +from .data import _source_dir + + +def _copy_portal_files(app, exception) -> None: + """Copy trees whose root paths are part of the published site interface.""" + if exception is not None or app.builder.name not in {"html", "dirhtml"}: + return + source_dir = _source_dir(app) + output_dir = Path(app.outdir) + for directory in ("assets", "licenses"): + shutil.copytree( + source_dir / directory, + output_dir / directory, + dirs_exist_ok=True, + ) + for filename in ("LICENSE", "SECURITY.md"): + shutil.copy2(source_dir / filename, output_dir / filename) + + # ``dirhtml`` correctly creates pretty URLs everywhere except the hosting + # platform's required top-level error document. sphinx-notfound-page has + # already rewritten this page's resource and navigation links as absolute. + shutil.copy2(output_dir / "404" / "index.html", output_dir / "404.html") diff --git a/extensions/rapids_docs/platform_support.py b/extensions/rapids_docs/platform_support.py new file mode 100644 index 00000000000..8b4f2feef34 --- /dev/null +++ b/extensions/rapids_docs/platform_support.py @@ -0,0 +1,84 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Render the platform support page.""" + + +def _compute_capability(cuda: dict) -> str: + capabilities = [] + for capability in cuda["compute_capability"]: + sms = capability["sm"] + if not isinstance(sms, list): + sms = [sms] + capabilities.append(f"{capability['name']} ({', '.join(map(str, sms))})") + return ", ".join(capabilities) + " or newer" + + +def _platform_support(data: dict) -> str: + releases = data["platform_support"]["releases"] + links = ", ".join( + f"[{release['version']}{' (nightly)' if release.get('nightly') else ''}]" + f"(#rapids-{str(release['version']).replace('.', '')})" + for release in releases + ) + sections = [f"**Releases:** {links}"] + for release in releases: + title = f"## RAPIDS {release['version']}" + if release.get("nightly"): + title += " (nightly)" + cuda_headers = [f"CUDA {cuda['major']}" for cuda in release["cuda"]] + cuda_rows = [ + ( + "Toolkit", + [ + f"{cuda['toolkit_min']}" + + ( + f" - {cuda['toolkit_max']}" + if cuda["toolkit_min"] != cuda["toolkit_max"] + else "" + ) + for cuda in release["cuda"] + ], + ), + ("Driver", [f"{cuda['driver_min']}+" for cuda in release["cuda"]]), + ("Compute Capability", [_compute_capability(cuda) for cuda in release["cuda"]]), + ] + table = [ + "| | " + " | ".join(cuda_headers) + " |", + "|:--|" + "|".join(":--" for _ in cuda_headers) + "|", + ] + table.extend(f"| **{name}** | " + " | ".join(values) + " |" for name, values in cuda_rows) + sections.append( + "\n".join( + [ + "---", + "", + title, + "", + '### Operating Systems', + "", + f'- ' + f"**Linux (glibc {release['glibc_min']}+):** {', '.join(release['cpu_arch'])} " + f"(tested on {', '.join(release['os_support'])})", + '- ' + "**Windows:** Supported via [WSL](/install/#wsl2) with a compatible Linux distribution", + "", + '### Python', + "", + f"**{', '.join(release['python'])}**", + "", + '### CUDA', + "", + *table, + "", + '### Source Builds', + "", + "| Dependency | Version |", + "|:--|:--|", + f"| **GCC** | {release['source_build']['gcc']} |", + f"| **CCCL** | {release['source_build']['cccl']} |", + f"| **nvCOMP** | {release['source_build']['nvcomp']} |", + ] + ) + ) + return "\n\n".join(sections) diff --git a/extensions/rapids_docs/releases.py b/extensions/rapids_docs/releases.py new file mode 100644 index 00000000000..49dce9a121c --- /dev/null +++ b/extensions/rapids_docs/releases.py @@ -0,0 +1,133 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. +# SPDX-License-Identifier: Apache-2.0 + +"""Render current and historical release schedules.""" + +from .dates import _short_date + + +def _schedule_table(release: dict) -> str: + rows = [ + ("Development", release["dev"]), + ( + "[Burn Down](/releases/process/#burn-down) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", + release["cudf_burndown"], + ), + ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf/nvForest)", + release["cudf_codefreeze"], + ), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ), + ("[Release](/releases/process/#releasing)", release["release"]), + ] + output = ["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"] + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + return "\n".join(output) + + +def _current_schedules(data: dict) -> str: + releases = data["releases"] + return "\n\n".join( + [ + f"## Release v{releases['nightly']['version']} Schedule", + "**NOTE:** *Dates are subject to change at any time. Completed release schedules are posted " + "[here](/releases/schedule/).*", + _schedule_table(releases["nightly"]), + f"## *PROPOSED* Release v{releases['next_nightly']['version']} Schedule", + _schedule_table(releases["next_nightly"]), + ] + ) + + +def _old_project_group(version: str) -> str: + group = "cuDF/RMM" + if version >= "23.06": + group += "/rapids-cmake/" + if version <= "24.12": + group += "cugraph-ops/" + group += "raft" + return group + + +def _previous_schedules(data: dict) -> str: + sections = [] + for release in data["previous_releases"]: + output = [f"### Release v{release['version']} Schedule", ""] + if release.get("dev"): + rows = [("Development", release["dev"])] + group_suffix = ( + " (cuDF/RMM/rapids-cmake/raft/dask-cuda/KvikIO/ucxx/rapidsmpf)" + if release.get("other_burndown") + else "" + ) + rows.append( + ( + f"[Burn Down](/releases/process/#burn-down){group_suffix}", + release.get("cudf_burndown") or release["burndown"], + ) + ) + if release.get("other_burndown"): + rows.append( + ( + "[Burn Down](/releases/process/#burn-down) (others)", + release["other_burndown"], + ) + ) + if release.get("cudf_codefreeze"): + rows.append( + ( + f"[Code Freeze/Testing](/releases/process/#code-freeze){group_suffix}", + release["cudf_codefreeze"], + ) + ) + rows.append( + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ) + ) + else: + rows.append( + ("[Code Freeze/Testing](/releases/process/#code-freeze)", release["codefreeze"]) + ) + rows.append(("[Release](/releases/process/#releasing)", release["release"])) + output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + elif release.get("date"): + output.extend( + ["| Phase | Date |", "|:--|:--|", f"| Release | {_short_date(release['date'])} |"] + ) + else: + group = _old_project_group(release["version"]) + rows = [ + (f"Development ({group})", release["cudf_dev"]), + ("Development (others)", release["other_dev"]), + (f"[Burn Down](/releases/process/#burn-down) ({group})", release["cudf_burndown"]), + ("[Burn Down](/releases/process/#burn-down) (others)", release["other_burndown"]), + ( + f"[Code Freeze/Testing](/releases/process/#code-freeze) ({group})", + release["cudf_codefreeze"], + ), + ( + "[Code Freeze/Testing](/releases/process/#code-freeze) (others)", + release["other_codefreeze"], + ), + ("[Release](/releases/process/#releasing)", release["release"]), + ] + output.extend(["| Phase | Start | End | Duration |", "|:--|:--|:--|:--|"]) + output.extend( + f"| {name} | {_short_date(values['start'])} | {_short_date(values['end'])} | {values['days']} days |" + for name, values in rows + ) + sections.append("\n".join(output)) + return "\n\n".join(sections) diff --git a/pyproject.toml b/pyproject.toml index 563146b7cd3..d76fd0126ae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,7 +37,7 @@ target-version = "py312" select = ["B", "E", "F", "I", "UP"] [tool.ruff.lint.per-file-ignores] -"extensions/rapids_docs.py" = ["E501"] +"extensions/rapids_docs/*.py" = ["E501"] [tool.pytest.ini_options] pythonpath = ["."] diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 346dc6e1eb5..e98c4a63ca8 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -5,38 +5,47 @@ from types import SimpleNamespace from extensions import rapids_docs +from extensions.rapids_docs import api, lifecycle, notices, platform_support, releases +from extensions.rapids_docs import data as portal_data ROOT = Path(__file__).resolve().parents[1] APP = SimpleNamespace(srcdir=str(ROOT)) -def test_data_driven_content() -> None: - data = rapids_docs._load_data(APP) +def test_api_docs() -> None: + data = portal_data._load_data(APP) stable_version = data["releases"]["stable"]["version"] nightly_version = data["releases"]["nightly"]["version"] legacy_version = data["releases"]["legacy"]["version"] - api = rapids_docs._api_docs(data, "apis") - assert f"Stable ({stable_version})" in api - assert "/api/cudf/stable/" in api - assert "::::{grid} 1 1 1 1" in api - assert ":::{grid-item-card} cuDF" in api - assert "**Documentation:**" in api - assert "[GitHub]" in api - assert "DOCS" not in api - assert api.index(f"Nightly ({nightly_version})") < api.index(f"Stable ({stable_version})") - assert api.index(f"Stable ({stable_version})") < api.index(f"Legacy ({legacy_version})") - - inactive = rapids_docs._api_docs(data, "inactive-projects") + rendered = api._api_docs(data, "apis") + assert f"Stable ({stable_version})" in rendered + assert "/api/cudf/stable/" in rendered + assert "::::{grid} 1 1 1 1" in rendered + assert ":::{grid-item-card} cuDF" in rendered + assert "**Documentation:**" in rendered + assert "[GitHub]" in rendered + assert "DOCS" not in rendered + assert rendered.index(f"Nightly ({nightly_version})") < rendered.index( + f"Stable ({stable_version})" + ) + assert rendered.index(f"Stable ({stable_version})") < rendered.index( + f"Legacy ({legacy_version})" + ) + + inactive = api._api_docs(data, "inactive-projects") inactive_project = next( project for project in data["docs"]["inactive-projects"].values() if not project.get("hidden", False) and project["versions"].get("stable") == 1 ) - inactive_version = rapids_docs._version_label(inactive_project, "stable", data["releases"]) + inactive_version = api._version_label(inactive_project, "stable", data["releases"]) assert f"Stable ({inactive_version})" in inactive - platform = rapids_docs._platform_support(data) + +def test_platform_support() -> None: + data = portal_data._load_data(APP) + platform = platform_support._platform_support(data) platform_release = data["platform_support"]["releases"][0] assert f"RAPIDS {platform_release['version']}" in platform assert f"CUDA {platform_release['cuda'][0]['major']}" in platform @@ -47,41 +56,66 @@ def test_data_driven_content() -> None: assert 'class="fas fa-microchip"' in platform assert 'class="fas fa-hammer"' in platform - schedules = rapids_docs._current_schedules(data) + +def test_release_schedules() -> None: + data = portal_data._load_data(APP) + nightly_version = data["releases"]["nightly"]["version"] + schedules = releases._current_schedules(data) assert f"Release v{nightly_version} Schedule" in schedules assert "PROPOSED" in schedules + previous_version = data["previous_releases"][0]["version"] + assert f"Release v{previous_version} Schedule" in releases._previous_schedules(data) def test_standard_jinja_syntax_and_raw_blocks() -> None: app = SimpleNamespace(srcdir=str(ROOT)) - app.rapids_portal_data = rapids_docs._load_data(app) - template = rapids_docs._jinja_environment(app).from_string( + app.rapids_portal_data = portal_data._load_data(app) + template = lifecycle._jinja_environment(app).from_string( "{{ releases.stable.version }}\n{% raw %}${{ matrix.PY_VER }}{% endraw %}\n" ) - rendered = template.render(rapids_docs._context(app)) + rendered = template.render(lifecycle._context(app)) stable_version = app.rapids_portal_data["releases"]["stable"]["version"] assert rendered == stable_version + "\n${{ matrix.PY_VER }}\n" def test_notice_metadata_and_indexes() -> None: - data = rapids_docs._load_data(APP) + data = portal_data._load_data(APP) source_notices = list((ROOT / "notices").glob("r[dgs]n[0-9][0-9][0-9][0-9].md")) assert len(data["notices"]) == len(source_notices) - support_notices = rapids_docs._notice_table(data, "rsn") + support_notices = notices._notice_table(data, "rsn") support_notice = next(notice for notice in data["notices"] if notice["notice_type"] == "rsn") assert f"RSN {support_notice['notice_id']}" in support_notices assert support_notice["title"] in support_notices - pinned = rapids_docs._notice_table(data, pinned=True) + pinned = notices._notice_table(data, pinned=True) pinned_notice = next(notice for notice in data["notices"] if notice.get("notice_pin")) assert f"{pinned_notice['notice_type'].upper()} {pinned_notice['notice_id']}" in pinned def test_github_alert_conversion() -> None: source = "> [!WARNING]\n> Do not report security vulnerabilities publicly!\n" - converted = rapids_docs._convert_github_alerts(source) + converted = lifecycle._convert_github_alerts(source) assert converted == ("```{warning}\nDo not report security vulnerabilities publicly!\n```\n") + + +def test_extension_setup() -> None: + connections = [] + app = SimpleNamespace(connect=lambda event, callback: connections.append((event, callback))) + + metadata = rapids_docs.setup(app) + + assert [event for event, _ in connections] == [ + "builder-inited", + "source-read", + "build-finished", + "build-finished", + ] + assert metadata == { + "version": "1.0", + "parallel_read_safe": False, + "parallel_write_safe": True, + } From 50032db50d17cd2ad18307603b6287e3cabc54fd Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Sun, 12 Jul 2026 09:31:34 -0500 Subject: [PATCH 17/22] Restore notice status labels Signed-off-by: Bradley Dice --- extensions/rapids_docs/notices.py | 14 +++++++++++-- sphinx/_static/css/custom.css | 33 +++++++++++++++++++++++++++++++ tests/test_rendering.py | 21 ++++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/extensions/rapids_docs/notices.py b/extensions/rapids_docs/notices.py index 8127e499545..70d6ce2c437 100644 --- a/extensions/rapids_docs/notices.py +++ b/extensions/rapids_docs/notices.py @@ -13,11 +13,21 @@ from .dates import _date, _long_date +_NOTICE_STATUS_COLORS = {"blue", "green", "purple", "red", "yellow"} + def _notice_date(notice: dict) -> datetime: return _date(notice.get("notice_updated") or notice["notice_created"]) +def _notice_status_label(notice: dict) -> str: + color = str(notice.get("notice_status_color", "blue")).lower() + if color not in _NOTICE_STATUS_COLORS: + color = "blue" + status = html.escape(str(notice["notice_status"])) + return f'{status}' + + def _notice_table(data: dict, notice_type: str | None = None, pinned: bool = False) -> str: notices = data["notices"] if notice_type: @@ -38,7 +48,7 @@ def _notice_table(data: dict, notice_type: str | None = None, pinned: bool = Fal if not updated or _date(updated).date() == _date(notice["notice_created"]).date(): updated = notice["notice_created"] output.append( - f"| **{notice['notice_type'].upper()} {notice['notice_id']}**
**{notice['notice_status']}** " + f"| **{notice['notice_type'].upper()} {notice['notice_id']}**
{_notice_status_label(notice)} " f"| [{notice['title']}](/notices/{Path(notice['docname']).name}/) " f"| {notice['notice_topic']} | {notice['notice_rapids_version']} | {_long_date(updated)} |" ) @@ -61,7 +71,7 @@ def _notice_header(metadata: dict) -> str: "| | |", "|:--|:--|", f"| **Author** | {metadata['notice_author']} |", - f"| **Status** | **{metadata['notice_status']}** |", + f"| **Status** | {_notice_status_label(metadata)} |", f"| **Topic** | {metadata['notice_topic']} |", f"| **RAPIDS Version** | {metadata['notice_rapids_version']} |", f"| **Created** | {_long_date(metadata['notice_created'])} |", diff --git a/sphinx/_static/css/custom.css b/sphinx/_static/css/custom.css index d326ce37c4f..5ea37dee429 100644 --- a/sphinx/_static/css/custom.css +++ b/sphinx/_static/css/custom.css @@ -74,6 +74,39 @@ margin-bottom: 0; } +.notice-status-label { + display: inline-block; + padding: 0.16em 0.42em; + color: #fff; + font-size: inherit; + font-weight: 700; + text-transform: uppercase; + vertical-align: inherit; + white-space: nowrap; +} + +.notice-status-blue { + background-color: #2869e6; +} + +.notice-status-green { + color: #1a1a1a; + background-color: var(--nv-color-green); +} + +.notice-status-purple { + background-color: #5e41d0; +} + +.notice-status-red { + background-color: #e94c4c; +} + +.notice-status-yellow { + color: #44434d; + background-color: #f7d12e; +} + @media (max-width: 720px) { .selector-bg .container-padding { min-width: 40rem; diff --git a/tests/test_rendering.py b/tests/test_rendering.py index e98c4a63ca8..2861030dd5e 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -89,12 +89,33 @@ def test_notice_metadata_and_indexes() -> None: support_notice = next(notice for notice in data["notices"] if notice["notice_type"] == "rsn") assert f"RSN {support_notice['notice_id']}" in support_notices assert support_notice["title"] in support_notices + assert 'class="notice-status-label notice-status-green">Completed' in support_notices + assert 'class="notice-status-label notice-status-yellow">In Progress' in support_notices pinned = notices._notice_table(data, pinned=True) pinned_notice = next(notice for notice in data["notices"] if notice.get("notice_pin")) assert f"{pinned_notice['notice_type'].upper()} {pinned_notice['notice_id']}" in pinned +def test_notice_status_labels() -> None: + assert ( + notices._notice_status_label({"notice_status": "Completed", "notice_status_color": "green"}) + == 'Completed' + ) + assert ( + notices._notice_status_label( + {"notice_status": "In Progress", "notice_status_color": "yellow"} + ) + == 'In Progress' + ) + assert ( + notices._notice_status_label( + {"notice_status": "", "notice_status_color": "invalid"} + ) + == '<Unknown>' + ) + + def test_github_alert_conversion() -> None: source = "> [!WARNING]\n> Do not report security vulnerabilities publicly!\n" converted = lifecycle._convert_github_alerts(source) From 41887bb7d70c5b84eafb429ee94fb50436231403 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Sun, 12 Jul 2026 10:20:23 -0500 Subject: [PATCH 18/22] Cleanup --- CONTRIBUTING.md | 2 +- README.md | 5 ----- sphinx/_templates/layout.html | 3 --- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9835d0b63ab..86073808732 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ The server uses port 8000 by default. Pass a different port when needed: make serve PORT=8080 ``` -Run the complete credential-free validation suite before submitting a change: +Run site validation before submitting a change: ```shell make check diff --git a/README.md b/README.md index 21e57e3a5de..814fb0106e8 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,3 @@ a portal-only preview. Merges to `main` continue to deploy the production site. - `ci/` downloads and post-processes versioned API and deployment documentation. - `scripts/` and `tests/` validate rendered routes, content, and publication behavior. - -## Migration history - -The Sphinx portal was initially migrated from the Jekyll site at -[`rapidsai/docs@b6afa0c`](https://github.com/rapidsai/docs/commit/b6afa0cbf4ddfc4c0a21f7c79b18631f214fd759). diff --git a/sphinx/_templates/layout.html b/sphinx/_templates/layout.html index 720d3329fbb..5742c502487 100644 --- a/sphinx/_templates/layout.html +++ b/sphinx/_templates/layout.html @@ -3,7 +3,4 @@ {% block extrahead %} {{ super() }} - - - {% endblock %} From e6ee6ef0cdaa0c9e07231f58b9e179135d05beb5 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 7 Jul 2026 16:54:50 -0500 Subject: [PATCH 19/22] RSN 60: mark complete (#808) https://github.com/rapidsai/cuxfilter is now archived and the 26.06 release is done. The RSN about archiving `cuxfilter` can be marked complete. Authors: - James Lamb (https://github.com/jameslamb) Approvers: - Bradley Dice (https://github.com/bdice) URL: https://github.com/rapidsai/docs/pull/808 (cherry picked from commit ff3968c5293af0624190e7335dff2f40a9164581) Signed-off-by: Bradley Dice --- notices/rsn0060.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notices/rsn0060.md b/notices/rsn0060.md index 51f555386ce..59d83a50572 100644 --- a/notices/rsn0060.md +++ b/notices/rsn0060.md @@ -10,8 +10,8 @@ notice_pin: true # set to true to pin to notice page title: "Sunsetting cuxfilter after RAPIDS Release v26.06" notice_author: RAPIDS TPM -notice_status: In Progress -notice_status_color: yellow +notice_status: Completed +notice_status_color: green # 'notice_status' and 'notice_status_color' combinations: # "Proposal" - "blue" # "Completed" - "green" @@ -22,7 +22,7 @@ notice_topic: Platform Support Change notice_rapids_version: "v26.06+" notice_created: 2026-05-22 # 'notice_updated' should match 'notice_created' until an update is made -notice_updated: 2026-05-22 +notice_updated: 2026-07-07 --- ## Overview From 26b7bb9c944421910c9d0899f40bd62712a9b04e Mon Sep 17 00:00:00 2001 From: James Lamb Date: Tue, 7 Jul 2026 17:12:02 -0500 Subject: [PATCH 20/22] move cuxfilter to 'inactive' projects, remove references (#809) Contributes to https://github.com/rapidsai/build-planning/issues/291 * moves `cuxfilter` to the "inactive projects' section of the API docs * removes references encouraging its use Authors: - James Lamb (https://github.com/jameslamb) Approvers: - Bradley Dice (https://github.com/bdice) URL: https://github.com/rapidsai/docs/pull/809 (cherry picked from commit 922b6932626d39a2c383f4dabd53913e885d6b0e) Signed-off-by: Bradley Dice --- _data/docs.yml | 27 +++++++++++++--------- _includes/selector.html | 6 ++--- ci/customization/projects-to-versions.json | 9 ++++---- user-guide/index.md | 4 ---- visualization/index.md | 18 ++------------- 5 files changed, 25 insertions(+), 39 deletions(-) diff --git a/_data/docs.yml b/_data/docs.yml index 939c48953e9..3724be766fc 100644 --- a/_data/docs.yml +++ b/_data/docs.yml @@ -46,17 +46,6 @@ apis: legacy: 1 stable: 1 nightly: 1 - cuxfilter: - name: cuxfilter - path: cuxfilter - desc: 'cuxfilter acts as a connector library, which provides the connections between different visualization libraries and a GPU dataframe without much hassle. This also allows the user to use charts from different libraries in a single dashboard, while also providing the interaction.' - ghlink: https://github.com/rapidsai/cuxfilter - cllink: https://github.com/rapidsai/cuxfilter/blob/main/CHANGELOG.md - versions: - # enable or disable links; 0 = disabled, 1 = enabled - legacy: 1 - stable: 1 - nightly: 1 cudf-java: name: 'Java + cuDF' path: cudf-java @@ -294,6 +283,22 @@ inactive-projects: version-overrides: legacy: "25.02" stable: "25.04" + cuxfilter: + name: cuxfilter + path: cuxfilter + desc: 'cuxfilter acts as a connector library, which provides the connections between different visualization libraries and a GPU dataframe without much hassle. This also allows the user to use charts from different libraries in a single dashboard, while also providing the interaction.' + ghlink: https://github.com/rapidsai/cuxfilter + cllink: https://github.com/rapidsai/cuxfilter/blob/main/CHANGELOG.md + versions: + # enable or disable links; 0 = disabled, 1 = enabled + legacy: 1 + stable: 1 + nightly: 0 + # always use these specific versions for these specific paths, regardless + # of the current state of RAPIDS + version-overrides: + legacy: "26.04" + stable: "26.06" libcuproj: name: libcuproj path: libcuproj diff --git a/_includes/selector.html b/_includes/selector.html index 423b58326c0..9852779e163 100644 --- a/_includes/selector.html +++ b/_includes/selector.html @@ -467,11 +467,11 @@ img_loc: ["NGC", "Docker Hub"], img_types: ["Base", "Notebooks"], packages: ["Standard", "Choose Specific Packages"], - additional_pip_packages: ["cuDF", "dask-cuDF", "cuML", "cuGraph/nx-cugraph", "cuxfilter", "cuCIM", "RAFT", "cuVS", "nvForest"], - additional_rapids_packages: ["cuDF", "cuML", "cuGraph", "cuxfilter", "cuCIM", "RAFT", "cuVS", "nvForest"], + additional_pip_packages: ["cuDF", "dask-cuDF", "cuML", "cuGraph/nx-cugraph", "cuCIM", "RAFT", "cuVS", "nvForest"], + additional_rapids_packages: ["cuDF", "cuML", "cuGraph", "cuCIM", "RAFT", "cuVS", "nvForest"], additional_packages: ["Graphistry", "JupyterLab", "NetworkX + nx-cugraph", "Plotly Dash", "PyTorch", "TensorFlow", "Xarray-Spatial"], note_prefix: "", - rapids_meta_pkgs: ["cuDF", "cuML", "cuGraph", "nx-cugraph", "cuxfilter", "cuCIM", "RAFT", "cuVS"], + rapids_meta_pkgs: ["cuDF", "cuML", "cuGraph", "nx-cugraph", "cuCIM", "RAFT", "cuVS"], getStableVersion() { return "{{ releases.stable.version }}"; }, diff --git a/ci/customization/projects-to-versions.json b/ci/customization/projects-to-versions.json index db0c801f2f1..cc1439289ad 100644 --- a/ci/customization/projects-to-versions.json +++ b/ci/customization/projects-to-versions.json @@ -19,11 +19,6 @@ "stable": "26.06", "nightly": "26.08" }, - "cuxfilter": { - "legacy": "26.04", - "stable": "26.06", - "nightly": "26.08" - }, "cudf-java": { "legacy": "26.04", "stable": "26.06" @@ -115,6 +110,10 @@ "legacy": "25.02", "stable": "25.04" }, + "cuxfilter": { + "legacy": "26.04", + "stable": "26.06" + }, "libcuproj": { "legacy": "25.02", "stable": "25.04" diff --git a/user-guide/index.md b/user-guide/index.md index a489d776de1..320d57fd692 100644 --- a/user-guide/index.md +++ b/user-guide/index.md @@ -30,10 +30,6 @@ The RAPIDS data science framework is a collection of libraries for running end-t Start with the [cuSpatial User Guide](/api/cuspatial/stable/user_guide/cuspatial_api_examples/) for an intro to GPU Accelerated Spatial Analytics. -** Accelerated Cross Filtered Visualization with [cuxfilter](https://github.com/rapidsai/cuxfilter)**: - Start with [10 Minutes to Cuxfilter](api/cuxfilter/stable/user_guide/10_minutes_to_cuxfilter/) to get an overview of how to quickly create a dashboard. There are also broader examples in the [RAPIDS Visualization Guides](https://github.com/rapidsai/cuxfilter/tree/HEAD/notebooks/RAPIDS%20Visualization%20Guide). - - ** Computer Vision and Analytics with [cuCIM](https://github.com/rapidsai/cucim)**: Start with the [Welcome Notebook](https://github.com/rapidsai/cucim/blob/branch-{{ releases.stable.version }}/notebooks/Welcome.ipynb) for links to resources guides and a good overview of the project structure. diff --git a/visualization/index.md b/visualization/index.md index 99b525a0e6b..32777861444 100644 --- a/visualization/index.md +++ b/visualization/index.md @@ -17,7 +17,6 @@ RAPIDS libraries can easily fit in visualization workflows. This catalog of feat ## Other Notable Libraries - **[Panel](#panel):** A high-level app and dashboarding solution for the Python ecosystem. - **[PyDeck](#pydeck):** Python bindings for interactive spatial visualizations with webGL powered deck.gl, optimized for a Jupyter environment. -- **[cuxfilter](#cuxfilter):** RAPIDS developed cross filtering dashboarding tool that integrates many of the libraries above. - **[node RAPIDS](#noderapids):** RAPIDS bindings in nodeJS, a high performance JS/TypeScript visualization alternative to using Python. @@ -27,13 +26,11 @@ The below libraries directly use RAPIDS cuDF/Dask-cuDF and/or cuSpatial to creat - **[Holoviews with Linked Brushing User Guide](https://holoviews.org/user_guide/Linked_Brushing.html?highlight=linked%20brushing)** - **[Datashader User Guide](https://datashader.org/user_guide/Performance.html)** - **[Plotly Dash with Holoviews Docs](https://dash.plotly.com/holoviews#gpu-accelerating-datashader-and-linked-selections-with-rapids)** -- **[cuxfilter GitHub](https://github.com/rapidsai/cuxfilter)** - :::{note} **Web Hosted vs Local Hosted Chart Interaction** -When interacting with this page through a website, the interactive examples below are all **static and use pre-computed data.** To run a true interactive version, host through the **active** instance found on our [cuxfilter GitHub Notebooks](https://github.com/rapidsai/cuxfilter/tree/branch-{{ releases.stable.version }}/notebooks/RAPIDS%20Visualization%20Guide). +When interacting with this page through a website, the interactive examples below are all **static and use pre-computed data.** ::: {% include "_includes/viz-cdn-js-css.html" %} @@ -73,7 +70,7 @@ When interacting with this page through a website, the interactive examples belo - Datashader is a graphics pipeline system for creating meaningful representations of large datasets quickly and flexibly. -- Datashader is able to render a variety of chart types statically, and interactively when combined with other libraries like HoloViews or cuxfilter. +- Datashader is able to render a variety of chart types statically, and interactively when combined with other libraries like HoloViews. - Read about Datashader at [datashader.org](https://datashader.org) and explore its examples. - Read about [RAPIDS compatibility](https://datashader.org/user_guide/Performance.html?highlight=cudf#data-objects).
@@ -150,17 +147,6 @@ When interacting with this page through a website, the interactive examples belo - Further [Documentation](https://pydeck.gl/layer.html). -
- -cuxfilter - - -
-- cuxfilter is a RAPIDS developed cross filtering library which enables GPU accelerated dashboards, using best in class charting libraries, with just a few lines of Python. -- Read about cuxfilter at [github.com/rapidsai/cuxfilter](https://github.com/rapidsai/cuxfilter) and explore its examples [docs.rapids.ai/api/cuxfilter/stable/examples/examples.html](https://docs.rapids.ai/api/cuxfilter/stable/examples/examples.html). -- Further [Documentation](https://docs.rapids.ai/api/cuxfilter/stable/10_minutes_to_cuxfilter.html). - -
nodeRAPIDS From 42f070eadcbb7715412eb37a546899d586c68ca3 Mon Sep 17 00:00:00 2001 From: Bradley Dice Date: Fri, 10 Jul 2026 09:53:50 -0500 Subject: [PATCH 21/22] Remove TensorFlow and enable PyTorch with CUDA 13 (#811) ## Summary - Removes TensorFlow from the install selector's **Additional Packages** options and deletes obsolete compatibility notes. - Allows PyTorch to be selected with CUDA 13. I validated that current official PyTorch wheels and conda-forge packages support CUDA 13. Authors: - Bradley Dice (https://github.com/bdice) Approvers: - James Lamb (https://github.com/jameslamb) URL: https://github.com/rapidsai/docs/pull/811 (cherry picked from commit 792777e0d26b9d6f559388f97be4f83c3cb6cde3) Signed-off-by: Bradley Dice --- _includes/selector.html | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/_includes/selector.html b/_includes/selector.html index 9852779e163..24069595cc2 100644 --- a/_includes/selector.html +++ b/_includes/selector.html @@ -469,7 +469,7 @@ packages: ["Standard", "Choose Specific Packages"], additional_pip_packages: ["cuDF", "dask-cuDF", "cuML", "cuGraph/nx-cugraph", "cuCIM", "RAFT", "cuVS", "nvForest"], additional_rapids_packages: ["cuDF", "cuML", "cuGraph", "cuCIM", "RAFT", "cuVS", "nvForest"], - additional_packages: ["Graphistry", "JupyterLab", "NetworkX + nx-cugraph", "Plotly Dash", "PyTorch", "TensorFlow", "Xarray-Spatial"], + additional_packages: ["Graphistry", "JupyterLab", "NetworkX + nx-cugraph", "Plotly Dash", "PyTorch", "Xarray-Spatial"], note_prefix: "", rapids_meta_pkgs: ["cuDF", "cuML", "cuGraph", "nx-cugraph", "cuCIM", "RAFT", "cuVS"], getStableVersion() { @@ -749,13 +749,6 @@ if (this.active_additional_packages.length) { notes = [...notes, "Third-party packages are not tested."]; - - if (this.active_additional_packages.includes("PyTorch")) { - notes = [...notes, "PyTorch requires CUDA 12."]; - } - if (this.active_additional_packages.includes("TensorFlow")) { - notes = [...notes, "TensorFlow requires CUDA 12."]; - } } return notes.map(note => this.note_prefix + " " + note); @@ -833,9 +826,6 @@ }, disableUnsupportedCuda(cuda_version) { var isDisabled = false; - // PyTorch and TensorFlow do not support CUDA 13 yet - if (this.active_additional_packages.includes("PyTorch") && (!cuda_version.startsWith("12"))) isDisabled = true; - if (this.active_additional_packages.includes("TensorFlow") && (!cuda_version.startsWith("12"))) isDisabled = true; return isDisabled; }, disableUnsupportedPython(python_version) { @@ -996,8 +986,6 @@ return; } this.active_additional_packages = [...this.active_additional_packages, package]; - if (this.active_additional_packages.includes("PyTorch") && this.active_conda_cuda_ver !== "12") this.active_conda_cuda_ver = "12"; - if (this.active_additional_packages.includes("TensorFlow") && this.active_conda_cuda_ver !== "12") this.active_conda_cuda_ver = "12"; }, copyToClipboard() { let range = document.createRange(); From c03a97a58f0c52bfe47b50682ef64c129884ba9d Mon Sep 17 00:00:00 2001 From: Mike McCarty Date: Mon, 13 Jul 2026 12:42:37 -0400 Subject: [PATCH 22/22] Use concise site navigation titles Signed-off-by: Mike McCarty --- index.md | 12 ++++----- install/index.md | 56 +++++++++++++++++++++--------------------- visualization/index.md | 4 +-- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/index.md b/index.md index c0e75faa70c..fb8f2a0dcae 100644 --- a/index.md +++ b/index.md @@ -79,12 +79,12 @@ you. Visit [RAPIDS.ai](https://rapids.ai) for more information on the overall pr ```{toctree} :hidden: -install/index -platform-support/index -user-guide/index -api -visualization/index -maintainers/index +Installation Guide +Platform Support +User Guide +API Docs +Visualization Guide +Maintainer Docs contributing/index notices/index ``` diff --git a/install/index.md b/install/index.md index db5a359a748..8231d4625f8 100644 --- a/install/index.md +++ b/install/index.md @@ -28,7 +28,7 @@ RAPIDS has several methods for installation, depending on the preferred environm
-# Install RAPIDS +## Install RAPIDS Use the selector tool below to select your preferred method, packages, and environment to install RAPIDS. Certain combinations may not be possible and are dimmed automatically. {% include "_includes/selector.html" %} @@ -36,9 +36,9 @@ Use the selector tool below to select your preferred method, packages, and envir
-## Installation Troubleshooting +### Installation Troubleshooting -### Conda Issues +#### Conda Issues A `conda create error` occurs:
To resolve this error please follow one of these steps: - If the Conda installation is older than `23.10`, please update to the latest version. This will include [libmamba](https://conda.org/blog/2023-11-06-conda-23-10-0-release/) to significantly accelerate environment solving @@ -65,7 +65,7 @@ Note that if you installed conda with [Miniforge](https://conda-forge.org/downlo In general [mixing `conda-forge` and `defaults` channels is not supported](https://conda-forge.org/docs/user/transitioning_from_defaults/). RAPIDS packages are published to a separate `rapidsai` channel that is designed for compatibility with `conda-forge`, not `defaults`. -### Docker Issues +#### Docker Issues RAPIDS `23.08` brought significant Docker changes.
To learn more about these changes, please see the [RAPIDS Container README](https://hub.docker.com/r/rapidsai/base). Some key notes below: - `Development` images are no longer being published, RAPIDS now uses [Dev Containers](https://code.visualstudio.com/docs/devcontainers/containers) for development @@ -80,7 +80,7 @@ To learn more about these changes, please see the [RAPIDS Container README](http - For a full list of changes please see this [RAPIDS Docker Issue](https://github.com/rapidsai/docker/issues/539) -### pip Issues +#### pip Issues pip installations require using the matching wheel to the system's installed CUDA toolkit. For example, if you have the CUDA 12 toolkit, install the `-cu12` wheels.
Infiniband is not supported yet.
These packages are not compatible with Tensorflow pip packages. Please use the [NGC containers](https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tensorflow) or conda packages instead.
@@ -97,15 +97,15 @@ Check the suggestions below for possible resolutions:
-### WSL2 Issues +#### WSL2 Issues See the WSL2 setup [troubleshooting section](#wsl2-troubleshooting).
-# System Requirements -## OS / GPU Driver / CUDA Versions +## System Requirements +### OS / GPU Driver / CUDA Versions All provisioned systems need to be RAPIDS capable. Below is a list of requirements for the current release. For requirements of historical RAPIDS versions, see [Platform Support](/platform-support/). **GPU:** NVIDIA Volta™ or higher with [compute capability](https://developer.nvidia.com/cuda-gpus) 7.0+ @@ -128,9 +128,9 @@ All provisioned systems need to be RAPIDS capable. Below is a list of requiremen See [CUDA compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/) for details. -## CUDA Support Notes +### CUDA Support Notes -### pip +#### pip - pip installations require using a wheel matching the system's installed CUDA toolkit. - RAPIDS pip packages require NVRTC for Numba to function properly. For Docker users, this means that RAPIDS wheels require the `devel` flavor of `nvidia/cuda` images for full functionality. The `base` and `runtime` flavors of `nvidia/cuda` Docker images are currently not sufficient. @@ -139,7 +139,7 @@ See [CUDA compatibility](https://docs.nvidia.com/deploy/cuda-compatibility/) for
-## System Recommendations +### System Recommendations Aside from the system requirements, other considerations for best performance include: - SSD drive (NVMe preferred) @@ -149,7 +149,7 @@ Aside from the system requirements, other considerations for best performance in
-## Cloud Instance GPUs +### Cloud Instance GPUs If you do not have access to GPU hardware, there are several cloud service providers (CSP) that are RAPIDS enabled. Learn how to deploy RAPIDS on AWS, Azure, GCP, and IBM cloud on our [Cloud Deployment Page](https://docs.rapids.ai/deployment/stable/cloud/index.html). Several services also offer **free and limited** trials with GPU resources: @@ -160,13 +160,13 @@ Several services also offer **free and limited** trials with GPU resources:
-# Environment Setup +## Environment Setup For most installations, you will need a Conda or Docker environments installed for RAPIDS. Note, these examples are structured for installing on **Ubuntu**. Please modify appropriately for Rocky Linux. **Windows 11** has a [WSL2 specific install](#wsl2).
-## Conda +### Conda RAPIDS can be used with any conda distribution. Below is an installation guide using miniforge. @@ -192,7 +192,7 @@ conda config --set channel_priority flexible
-## Docker +### Docker RAPIDS requires Docker Engine and [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) installed. **1. Download and Install.** Copy command below to download and install the latest Docker Engine: @@ -215,7 +215,7 @@ docker run --gpus all nvcr.io/nvidia/k8s/cuda-sample:nbody nbody -gpu -benchmark
-### JupyterLab +#### JupyterLab The command provided from the selector for the `notebooks` Docker image will run [JupyterLab](https://jupyterlab.readthedocs.io/en/stable/) on your host machine at port: `8888`. **Running Multi-Node / Multi-GPU (MNMG) Environment.** To start the container in an MNMG environment: @@ -228,13 +228,13 @@ The standard docker command may be sufficient, but the additional arguments ensu
-## pip +### pip RAPIDS pip packages are available on the NVIDIA Python Package Index.
-## SDK Manager (Ubuntu Only) +### SDK Manager (Ubuntu Only) [NVIDIA SDK Manager](https://developer.nvidia.com/sdk-manager) gives a users a Graphical User Interface (GUI) option to install RAPIDS. It also attempts to fix any environment issues before installing RAPIDS or updating RAPIDS, making it ideal for new Linux users. 1. Download [SDK Manager's Ubuntu version from their website](https://developer.nvidia.com/sdk-manager) (requires sign up or login to NVIDIA's Developer community). Do not install yet. It is assumed that your home directory's `Downloads` folder is where the `.deb` file will be stored. If not, please move `sdkmanager_[version]-[build#]_amd64.deb` file to your current Download folder. 2. Install and run SDK Manager [using the installation guide here](https://docs.nvidia.com/sdk-manager/download-run-sdkm/index.html). For Ubuntu, use the following commands: @@ -248,17 +248,17 @@ sdkmanager
-## Windows WSL2 +### Windows WSL2 Windows users can now tap into GPU accelerated data science on their local machines using RAPIDS on [Windows Subsystem for Linux 2](https://learn.microsoft.com/en-us/windows/wsl/install). WSL2 is a Windows feature that enables users to run native Linux command line tools directly on Windows. Using this feature does not require a dual boot environment, removing complexity and saving you time. -### WSL2 Additional Prerequisites +#### WSL2 Additional Prerequisites **OS:** Windows 11 with a WSL2 installation of Ubuntu.
**WSL Version:** WSL2 (WSL1 not supported).
**GPU:** GPUs with [Compute Capability](https://developer.nvidia.com/cuda-gpus) 7.0 or higher (16GB+ GPU RAM is recommended). -### Limitations +#### Limitations Only single GPU is supported.
GPU Direct Storage is not supported. @@ -266,7 +266,7 @@ Windows users can now tap into GPU accelerated data science on their local machi
-### Troubleshooting +#### Troubleshooting When installing with Conda, if an `http 000 connection error` occurs when accessing the repository data, run `wsl --shutdown` and then [restart the WSL instance](https://stackoverflow.com/a/69601760). @@ -275,7 +275,7 @@ Windows users can now tap into GPU accelerated data science on their local machi
-### WSL2 SDK Manager Install +#### WSL2 SDK Manager Install [NVIDIA's SDK Manager](https://developer.nvidia.com/sdk-manager) gives Windows users a Graphical User Interface (GUI) option to install RAPIDS. It automates Windows Subsystem for Linux (WSL) setup for RAPIDS SDK 25.04 and later, simplifying the installation process. 1. Download [SDK Manager's Windows version from their website](https://developer.nvidia.com/sdk-manager) (requires sign up or login to NVIDIA's Developer community). 2. [Follow SDK Manager's RAPIDS installation instructions here](https://docs.nvidia.com/sdk-manager/install-with-sdkm-rapids/index.html). @@ -283,7 +283,7 @@ Windows users can now tap into GPU accelerated data science on their local machi
-### WSL2 Conda Install +#### WSL2 Conda Install 1. Install WSL2 and the Ubuntu distribution [using Microsoft's instructions](https://docs.microsoft.com/en-us/windows/wsl/install). 2. Install the [latest NVIDIA Drivers](https://www.nvidia.com/download/index.aspx) on the Windows host. @@ -299,7 +299,7 @@ print(cudf.Series([1, 2, 3]))
-### WSL2 Docker Desktop Install +#### WSL2 Docker Desktop Install 1. Install WSL2 and the Ubuntu distribution [using Microsoft's instructions](https://docs.microsoft.com/en-us/windows/wsl/install). 2. Install the [latest NVIDIA Drivers](https://www.nvidia.com/download/index.aspx) on the Windows host. @@ -315,7 +315,7 @@ print(cudf.Series([1, 2, 3]))
-### WSL2 pip Install +#### WSL2 pip Install 1. Install WSL2 and the Ubuntu distribution [using Microsoft's instructions](https://docs.microsoft.com/en-us/windows/wsl/install). 2. Install the [latest NVIDIA Drivers](https://www.nvidia.com/download/index.aspx) on the Windows host. @@ -332,14 +332,14 @@ print(cudf.Series([1, 2, 3]))
-## Build from Source +### Build from Source To build from source, find the library on the [RAPIDS GitHub](https://github.com/rapidsai). Libraries provide guidance on building from source in `README.md` or `CONTRIBUTING.md`. If additional help is needed, file an issue on GitHub or reach out on our [Slack Channel](https://rapids.ai/slack-invite).
-# Next Steps +## Next Steps After installing the RAPIDS libraries, the best place to get started is our [User Guide](/user-guide). Our [RAPIDS.ai](https://rapids.ai/) home page also provides a great deal of information, as does our [Blog Page](https://medium.com/rapids-ai) and the [NVIDIA Developer Blog](https://developer.nvidia.com/blog/?search_posts_filter=rapids). We are also always available on our [RAPIDS GoAi Slack Channel](https://rapids.ai/slack-invite).

diff --git a/visualization/index.md b/visualization/index.md index 32777861444..ac0977bb7cb 100644 --- a/visualization/index.md +++ b/visualization/index.md @@ -36,7 +36,7 @@ When interacting with this page through a website, the interactive examples belo {% include "_includes/viz-cdn-js-css.html" %}

-# Featured Libraries +## Featured Libraries
@@ -122,7 +122,7 @@ When interacting with this page through a website, the interactive examples belo

-# Other Notable Libraries +## Other Notable Libraries