From structured Markdown to a versioned documentation site and deterministic PDF, with one configuration for local preview and GitHub Pages.
English · 简体中文
This repository is designed for SDKs, hardware platforms, robotics systems, and other developer-facing technical documentation. Authors maintain only the Markdown, reStructuredText, and image assets under projects/; the build system discovers languages, generates navigation, isolates Chinese and English builds, typesets PDFs, manages versions, and publishes the static site.
It is more than a Markdown-to-HTML script. Content structure, site information architecture, bilingual routing, PDF fonts, and the CI environment are governed by the same verifiable build contract so local previews and remote outputs stay aligned.
Quick start · Core design · Content model · Configuration · PDF · Versions · Troubleshooting
| Goal | Current mechanism |
|---|---|
| Separate content from tooling | projects/ contains documentation; source/ contains the build system; a manifest owns synchronized copies |
| Support two content models | Document trees preserve directory READMEs; project catalogs select only entry READMEs and declared assets |
| Keep every output on one catalog | Synchronization, language detection, HTML navigation, and PDF scanning share one DocumentCatalog |
| Enable bilingual UI only when valid | Chinese-only, English-only, and bilingual repositories are detected automatically; the switch appears only for bilingual builds |
| Keep outputs reproducible | Python versions are pinned, and PDF builds require XeLaTeX plus exact fonts with no silent fallback |
| Align local and CI builds | Local builds and GitHub Actions use the same configuration, font roles, and PDF path |
| Isolate documentation versions | Every configured Git branch is built in its own worktree without switching the primary working directory |
- A static documentation site built with Sphinx 8 and the Read the Docs Theme.
- Markdown, tables, math, task lists, and extended syntax through MyST Parser.
- Isolated search indexes and static assets for Chinese and English, with exact counterpart links.
- Chinese and English PDFs generated by XeLaTeX with a cover, contents, page numbers, bookmarks, and article page breaks.
- Consistent PDF treatment for code blocks, inline code, tables, links, images, and WebP assets.
- Multi-version navigation and GitHub Pages deployment driven by
.github/versions.json. - Optional giscus comments, edit links, dark styling, a version menu, and PDF downloads.
| Scenario | Required environment |
|---|---|
| HTML build | Python 3.11 recommended; Python packages can be installed automatically |
| PDF build | XeLaTeX and every font declared in source/config.yaml |
| Multi-version build | Git and locally accessible branches referenced by .github/versions.json |
git clone https://github.com/kurisaW/sdk_build_doc_template.git
cd sdk_build_doc_template
python -m venv .venv# Windows PowerShell
.\.venv\Scripts\Activate.ps1
cd source
python build_local.py --check# Linux/macOS
source .venv/bin/activate
cd source
python build_local.py --check--check validates both package availability and the exact versions pinned in requirements.txt. Missing packages are installed by default; add --no-auto-install for a read-only check.
projects/
`-- getting_started/
|-- README_zh.md
|-- README.md
|-- install_zh.md
|-- install.md
`-- figures/
`-- installation.png
Chinese documents use the _zh suffix. English documents use the same base name without _zh. Update the project metadata, categories, and ordering in source/config.yaml, then build.
cd source
python build_local.py --clean --serveOpen http://localhost:8000. Final artifacts are written to:
source/_build/html/index.html
source/_build/html/_static/<project.name>.pdf
source/_build/html/_static/<project.name>_EN.pdf # generated when English is detected
Skip PDF generation during routine web content review:
python build_local.py --clean --no-pdf --serveprojects/ + repository README
|
v
language detection
|
v
doc_generator.py
sync sources + create navigation
|
+-------+-------+
| |
v v
Sphinx HTML Sphinx LaTeX
zh/en isolated + XeLaTeX
| |
+-------+-------+
v
source/_build/html/
A local build performs the following steps:
- Validate pinned Python dependencies and install missing packages through an available package index.
- Synchronize allowed documents and assets from
projects/into the Sphinx source directory. - Synchronize the shared document catalog and generate language-specific navigation.
- Build Chinese and English HTML independently, then merge them into one site.
- Generate one or two PDFs according to the detected languages and validate each output file.
- Remove synchronized copies, LaTeX files, doctrees, and build metadata while retaining final HTML and PDFs.
.
|-- projects/ # Primary documentation source of truth
| |-- README_zh.md # Optional: projects Chinese home/language marker
| |-- README.md # Optional: projects English home/language marker
| `-- <section>/
| |-- README_zh.md # Optional: Chinese directory landing page
| |-- README.md # Optional: English directory landing page
| |-- <topic>_zh.md # Chinese article
| |-- <topic>.md # English article
| `-- figures/ # Image assets
|-- source/
| |-- build_local.py # Local single-version entry point
| |-- build.py # Multi-version entry point
| |-- build_manager.py # Worktree and version orchestration
| |-- doc_generator.py # Synchronization and navigation generation
| |-- config.yaml # Project configuration
| |-- conf.py # Sphinx and LaTeX configuration
| |-- requirements.txt # Pinned dependencies
| |-- tests/ # Build mechanism tests
| |-- utils/ # Build modules
| |-- _static/ # Website assets
| `-- _templates/ # Page templates
|-- .github/versions.json # Version definitions
|-- .github/workflows/build-docs.yml # CI and Pages deployment
|-- README_zh.md # Chinese repository README/home fallback
`-- README.md # English repository README/home fallback
projects/ is the content source of truth. Do not edit same-named directories or READMEs that appear under source/ during a build; they are tracked by .doc_generator_manifest.json and cleaned automatically.
| Document type | Chinese | English |
|---|---|---|
| Site or directory home | README_zh.md |
README.md |
| Markdown article | <name>_zh.md |
<name>.md |
| reStructuredText article | <name>_zh.rst |
<name>.rst |
Counterpart pages should be placed in the same directory and share the same base name. For example, install_zh.md pairs with install.md. In a bilingual site, the switch falls back to the target-language home page when an exact counterpart does not exist.
generation.language_detection uses README_zh.md and README.md as markers by default. recursive_tree follows this precedence:
- If either marker exists at the root of
projects/, those root markers are authoritative and completely determine the available languages. - If no marker exists at the root of
projects/, repository-root READMEs and directory READMEs are inspected together. - A single detected language produces a single-language site with no language switch.
- Detecting both Chinese and English produces isolated bilingual builds and enables the switch.
- With no marker anywhere, PDF generation stops with an explicit language-detection error.
Consequently, projects/README_zh.md without projects/README.md keeps the site Chinese-only even when an English README exists deeper in the tree. This prevents a partial translation from exposing an incomplete bilingual site.
project_catalog counts only selected project entry READMEs plus home READMEs at projects_dir or repository root. Individual projects may provide partial language coverage; the bilingual site and switch are enabled when the final catalog contains both languages. Vendor packages, board sources, and unselected nested READMEs never affect language detection.
Each language resolves its home page independently:
generation.default_page
|
| missing
v
repository-root README fallback
|
| missing
v
generated language index
generation.default_pageis relative torepository.projects_dir; absolute paths and..are rejected.- Repository-root README fallback is enabled only when the root of
projects/has no language README at all. - The fallback README is copied to a manifest-owned temporary location. The repository source file is never modified.
- Markdown, MyST, RST, and HTML image references in a fallback README are resolved and synchronized with the same relative URLs. Missing images and path escapes fail the build.
Source selection and navigation are separate concerns:
| Repository shape | generation.discovery.mode |
generation.navigation.mode |
Behavior |
|---|---|---|---|
| Tutorials, manuals, knowledge bases | recursive_tree |
directory_tree |
Recursively synchronize supported files and use directory READMEs as section homes |
| SDK/BSP example collections | project_catalog |
categories |
Synchronize only selected project-root READMEs and asset_globs, then group them by category |
In project_catalog, patterns without / match only first-level directories under projects_dir; nested project roots require an explicit relative path such as dual_core/core0. The build fails on empty pattern matches, duplicate category matches, projects without an entry README, unknown navigation categories, path escapes, and missing or unsynchronized README images.
HTML, language detection, source synchronization, and PDF scanning all consume the same DocumentCatalog, preventing the website from showing only project pages while PDF independently pulls in vendor READMEs.
For every directory and language, the landing page is selected in this order:
- The README configured by
generation.directory_index. - An existing
index_zh.rst/index.rst, or the corresponding Markdown index. - A generated language-specific index.
This rule applies to recursive_tree. A directory README may contain an introduction or only a level-one heading. The generator appends a hidden toctree only to its synchronized copy. In PDF output, a navigation README contributes its level-one heading but not its body; if the README is the directory's only document, it is treated as a standalone article. In project_catalog, a project-root README is project content: its project title and body are both retained in PDF.
- Top-level sections follow
generation.navigation.order; configured but unlisted categories are appended naturally.generation.output_structureremains a legacy compatibility key. - Documents are naturally sorted by path. Prefixes such as
01_and02_provide stable explicit ordering. categories.<path>.nameandname_encontrol generated page and PDF section labels.- Each article should normally contain one level-one heading without duplicating its filename prefix.
- Common manual heading numbers are stripped only for LaTeX output; HTML and source Markdown remain unchanged.
Start from a complete template in source/config_templates/: use recursive_tree.yaml for documentation repositories and project_catalog.yaml for SDK/BSP example collections.
The following configuration covers directory-tree generation, bilingual homes, section ordering, and strict PDF fonts:
project:
name: "Example_SDK"
title: "Example SDK Developer Documentation"
title_en: "Example SDK Documentation"
description: "Example SDK development, integration, and debugging documentation."
description_en: "Development, integration, and debugging documentation for Example SDK."
version: "1.0.0"
author: "Example Team"
copyright: "2026, Example Team"
website: "https://example.com"
repository:
name: "example-sdk-docs"
projects_dir: "../projects"
docs_dir: "."
categories:
getting_started:
name: "快速上手篇"
name_en: "Getting Started"
description: "环境准备和首个示例。"
description_en: "Environment setup and first example."
applications:
name: "应用篇"
name_en: "Applications"
generation:
mode: "directory_tree"
discovery:
mode: "recursive_tree"
navigation:
mode: "directory_tree"
order:
- "getting_started"
- "applications"
language_detection:
zh: "README_zh.md"
en: "README.md"
default_language: "zh"
default_page:
zh: "README_zh.md"
en: "README.md"
directory_index:
zh: "README_zh.md"
en: "README.md"
sync_extensions:
- ".md"
- ".rst"
- ".png"
- ".jpg"
- ".jpeg"
- ".gif"
- ".svg"
- ".webp"
pdf_style: "web"
pdf_fonts:
latin: "TeX Gyre Termes"
cjk_body: "FandolSong-Regular.otf"
cjk_heading: "FandolHei-Regular.otf"
cjk_emphasis: "FandolKai-Regular.otf"
code: "Source Code Pro"
sphinx:
theme: "sphinx_rtd_theme"
extensions:
- "myst_parser"
source_suffix:
".rst": "restructuredtext"
".md": "markdown"
giscus:
enabled: false| Setting | Purpose |
|---|---|
project.name |
Project identifier, PDF filename, and default PDF cover title |
project.title |
HTML site title |
project.title_en |
Optional title used by a generated English home page |
project.description / description_en |
Generated home description and localized PDF cover summary |
repository.projects_dir |
Documentation source directory, relative to source/config.yaml |
repository.docs_dir |
Sphinx source directory; normally . |
generation.discovery.mode |
recursive_tree for a document tree or project_catalog for a strict project catalog |
generation.navigation.mode |
directory_tree for source directories or categories for configured groups |
generation.navigation.order |
Preferred top-level directory or category order |
generation.default_language |
Preferred language; an actually detected language is selected when unavailable |
generation.default_page |
Per-language site home, relative to projects_dir |
generation.directory_index |
Per-language directory landing filename |
generation.sync_extensions |
Document and asset suffixes allowed into the Sphinx source |
generation.pdf_style |
web by default; thesis, graduate, or academic enables thesis preview parameters |
generation.pdf_fonts |
Exact font assigned to each PDF text role |
giscus.enabled |
Loads giscus when repository and category identifiers are also configured |
Use strict project-catalog mode for an SDK/BSP example repository:
categories:
basic:
name: "基础篇"
name_en: "Basics"
patterns:
- "Titan_basic_*"
multicore:
name: "多核通信篇"
name_en: "Multicore Communication"
patterns:
- "Titan_dual_core/Titan_dual_core0"
- "Titan_dual_core/Titan_dual_core1"
generation:
mode: "project_catalog" # Compatibility marker for older tooling
discovery:
mode: "project_catalog"
entry_files:
zh: "README_zh.md"
en: "README.md"
asset_globs:
- "figures/**"
unmatched_projects: "error"
duplicate_categories: "error"
navigation:
mode: "categories"
order:
- "basic"
- "multicore"Run these commands from source/:
| Command | Purpose |
|---|---|
python build_local.py |
Build HTML and PDF for the current working tree |
python build_local.py --clean |
Remove old HTML and perform a complete rebuild |
python build_local.py --serve |
Start a local static server after building |
python build_local.py --serve --port 8080 |
Preview on a custom port |
python build_local.py --no-pdf |
Build HTML only |
python build_local.py --check |
Validate and, when needed, install Python dependencies |
python build_local.py --check --no-auto-install |
Validate dependencies without installing |
python build_local.py --check-branch |
Validate the current branch-to-version mapping |
Automatic installation probes PyPI, the Tsinghua mirror, and the Alibaba Cloud mirror concurrently, then ranks reachable indexes by latency. An existing PIP_INDEX_URL is tried first, followed by detected fallback indexes.
# Windows PowerShell
$env:DOCS_PIP_MIRROR = "tsinghua" # auto | official | china | tsinghua | aliyun
python build_local.py --check
$env:DOCS_PIP_INDEX_URL = "https://your-mirror.example/simple/"
python build_local.py --check# Linux/macOS
DOCS_PIP_MIRROR=official python build_local.py --check
DOCS_PIP_INDEX_URL=https://your-mirror.example/simple/ python build_local.py --checkPDF generation has one supported path: Sphinx LaTeX -> XeLaTeX. It does not fall back to browser printing or another engine, preventing silent differences between local and CI layouts.
| Text role | Font |
|---|---|
| English, numbers, and other Latin text | TeX Gyre Termes |
| Chinese body text | FandolSong-Regular.otf |
| Chinese headings | FandolHei-Regular.otf |
| Chinese emphasis | FandolKai-Regular.otf |
| Code blocks | Source Code Pro |
Validate the toolchain independently:
cd source
python utils/pdf_environment.py --no-auto-installThe local script can attempt to install system dependencies through apt-get on Linux, Homebrew on macOS, or Chocolatey on Windows. This is best-effort and depends on system privileges and the package manager. Otherwise install TeX Live or MiKTeX manually and expose xelatex on PATH or in a supported standard location.
PDF layout rules include:
- Generate Chinese and English PDFs according to detected languages; English filenames receive
_EN. - Separate the cover and contents, retain centered page numbers, and show the project name in body headers.
- Represent top-level directories as level-one bookmarks, articles as level-two bookmarks, and include headings through level three.
- Start each regular article on a new page and keep directory README navigation prose out of the PDF.
- Control first-line indentation, margins, paragraph spacing, heading spacing, and list spacing centrally in LaTeX.
- Wrap table content to the text width; render code blocks with a monospaced font, restrained background, and controlled long-line wrapping.
- Keep inline code at body size and use the body Latin font for English and numbers.
- Convert WebP images to JPEG during LaTeX preparation for XeLaTeX compatibility.
Font names must match config.yaml exactly. The build fails when any font is unavailable and never substitutes a merely similar font.
.github/versions.json is the source of truth for documentation versions:
{
"versions": [
{
"name": "main",
"display_name": "latest",
"branch": "main",
"url_path": "latest",
"description": "Latest development version"
},
{
"name": "v1.0",
"display_name": "v1.0",
"branch": "v1.0",
"url_path": "v1.0",
"description": "Stable 1.0 documentation"
}
],
"default_version": "main",
"latest_version": "main"
}Every version requires name, display_name, branch, and url_path. Version maintenance commands:
cd source
python build.py --validate
python build.py --list-versions
python build.py --cleanbuild.py isolates branches with Git worktrees and writes the final site to source/source_build/html/. Use build_local.py for routine local preview to avoid unnecessary all-version builds.
GitHub Actions validates versions, installs dependencies, verifies XeLaTeX and fonts, builds all versions, uploads artifacts, and publishes gh-pages on Python 3.11/Ubuntu. The workflow watches source/**, projects/**, and .github/versions.json, and also supports manual workflow_dispatch runs.
python build_local.py --clean removes stale output before rebuilding, but never deletes documentation sources:
| Preserved | Removed automatically |
|---|---|
All source files under projects/ |
Manifest-recorded synchronized copies under source/ |
| Repository-root READMEs | source/_build/latex/ |
Final site under source/_build/html/ |
source/_build/html/.doctrees/ |
PDFs under source/_build/html/_static/ |
source/_build/html/.buildinfo |
| Build scripts, configuration, templates, and static assets | Empty synchronized directories and the generation manifest |
Failed builds may retain intermediate files for diagnosis. Fix the cause and run python build_local.py --clean again.
| Symptom | What to check |
|---|---|
| No language detected for PDF | Ensure a configured README marker exists in projects/, a document directory, or the repository root |
| Language switch is missing | The authoritative detection result must include both README_zh.md and README.md |
| Switching returns to the other home page | The current article has no same-path, same-base-name counterpart |
| Unexpected site home | Check root markers in projects/, generation.default_page, and stale output |
| Python dependency installation fails | Check proxy/certificates and set DOCS_PIP_MIRROR or DOCS_PIP_INDEX_URL |
| XeLaTeX or font validation fails | Run python utils/pdf_environment.py --no-auto-install for the exact missing item |
| PDF has no body content | Navigation READMEs do not become body articles when regular content is expected |
| Preview port is busy | Run python build_local.py --serve --port 8081 |
| Multi-version worktree fails | Verify target branches, git worktree list, and the installed Git version |
Validate dependencies without installation:
python build_local.py --check --no-auto-installInstall pinned dependencies manually:
python -m pip install -r requirements.txtAfter changing the build system, run from source/:
python -m unittest discover -s tests -v
python build_local.py --check --no-auto-install
python build_local.py --clean
python build.py --validateOwnership boundaries:
- Documentation and images:
projects/ - Site and PDF configuration:
source/config.yaml,source/conf.py - Build logic:
source/*.py,source/utils/ - Website styling and behavior:
source/_static/,source/_templates/ - Version definitions:
.github/versions.json - CI and Pages deployment:
.github/workflows/build-docs.yml
Do not commit generated _build/, source_build/, temporary LaTeX files, or synchronized source copies.