mirror of
https://github.com/orbbec/OrbbecSDK_ROS2.git
synced 2026-09-12 03:00:20 +08:00
269 lines
7.8 KiB
Python
269 lines
7.8 KiB
Python
#!/usr/bin/env python3
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
DOCS_DIR = REPO_ROOT / "docs"
|
|
MANIFEST_PATH = DOCS_DIR / "version-manifest.json"
|
|
SITE_DIR = REPO_ROOT / "site"
|
|
WORKTREE_ROOT = REPO_ROOT / ".docs-worktrees"
|
|
LANGUAGES = ("en", "zh")
|
|
|
|
|
|
def run(command: list[str], *, cwd: Path | None = None, env: dict[str, str] | None = None) -> subprocess.CompletedProcess[str]:
|
|
print("+", " ".join(command))
|
|
return subprocess.run(
|
|
command,
|
|
cwd=str(cwd or REPO_ROOT),
|
|
env=env,
|
|
check=True,
|
|
text=True,
|
|
)
|
|
|
|
|
|
def capture(command: list[str], *, cwd: Path | None = None) -> str:
|
|
result = subprocess.run(
|
|
command,
|
|
cwd=str(cwd or REPO_ROOT),
|
|
check=True,
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
return result.stdout.strip()
|
|
|
|
|
|
def load_manifest() -> dict:
|
|
return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
|
|
|
|
|
|
def ensure_ref_available(ref: str) -> None:
|
|
if ref == "HEAD":
|
|
return
|
|
|
|
if ref_resolves(ref) or resolve_remote_ref(ref) is not None:
|
|
return
|
|
|
|
fetch_targets = [ref]
|
|
if "/" not in ref and not ref.startswith("refs/"):
|
|
fetch_targets.append(f"refs/heads/{ref}")
|
|
|
|
for remote in list_remotes():
|
|
for target in fetch_targets:
|
|
try:
|
|
run(["git", "fetch", "--no-tags", remote, target])
|
|
if ref_resolves(ref) or resolve_remote_ref(ref) is not None:
|
|
return
|
|
except subprocess.CalledProcessError:
|
|
continue
|
|
|
|
raise RuntimeError(f"Unable to resolve git ref '{ref}'.")
|
|
|
|
|
|
def list_remotes() -> list[str]:
|
|
remotes = capture(["git", "remote"]).splitlines()
|
|
ordered: list[str] = []
|
|
for candidate in ("origin", "github"):
|
|
if candidate in remotes:
|
|
ordered.append(candidate)
|
|
for remote in remotes:
|
|
if remote not in ordered:
|
|
ordered.append(remote)
|
|
return ordered
|
|
|
|
|
|
def ref_resolves(ref: str) -> bool:
|
|
result = subprocess.run(
|
|
["git", "rev-parse", "--verify", f"{ref}^{{commit}}"],
|
|
cwd=str(REPO_ROOT),
|
|
text=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
)
|
|
return result.returncode == 0
|
|
|
|
|
|
def resolve_ref(ref: str) -> str:
|
|
if ref_resolves(ref):
|
|
return ref
|
|
|
|
remote_ref = resolve_remote_ref(ref)
|
|
if remote_ref is not None:
|
|
return remote_ref
|
|
|
|
raise RuntimeError(f"Unable to resolve git ref '{ref}'.")
|
|
|
|
|
|
def resolve_remote_ref(ref: str) -> str | None:
|
|
for remote in list_remotes():
|
|
remote_ref = f"refs/remotes/{remote}/{ref}"
|
|
if ref_resolves(remote_ref):
|
|
return remote_ref
|
|
return None
|
|
|
|
|
|
def should_use_workspace(version: dict) -> bool:
|
|
flag = os.getenv("DOCS_BUILD_LATEST_FROM_WORKSPACE", "0").lower()
|
|
return version.get("is_latest", False) and flag in {"1", "true", "yes", "on"}
|
|
|
|
|
|
def prepare_site_dir() -> None:
|
|
if SITE_DIR.exists():
|
|
shutil.rmtree(SITE_DIR)
|
|
SITE_DIR.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
|
def create_root_redirect(default_language: str) -> None:
|
|
target = f"{default_language}/index.html"
|
|
(SITE_DIR / "index.html").write_text(
|
|
(
|
|
"<!DOCTYPE html><html><head>"
|
|
f'<meta http-equiv="refresh" content="0; url={target}">'
|
|
f'<link rel="canonical" href="{target}">'
|
|
"</head><body>Redirecting...</body></html>"
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
(SITE_DIR / ".nojekyll").touch()
|
|
|
|
|
|
def copy_tree(source_dir: Path, target_dir: Path) -> None:
|
|
if target_dir.exists():
|
|
shutil.rmtree(target_dir)
|
|
shutil.copytree(source_dir, target_dir)
|
|
|
|
|
|
def resolve_source_dir(content_root: Path, language: str) -> Path:
|
|
candidates = [
|
|
content_root / "docs" / language,
|
|
content_root / "docs",
|
|
]
|
|
for candidate in candidates:
|
|
if candidate.exists() and ((candidate / "index.rst").exists() or (candidate / "index.md").exists()):
|
|
return candidate
|
|
|
|
fallback = REPO_ROOT / "docs" / language
|
|
if content_root != REPO_ROOT and fallback.exists():
|
|
print(
|
|
"WARNING: "
|
|
f"Falling back to current workspace docs for language '{language}' "
|
|
f"because {content_root} does not contain a buildable multilingual docs layout."
|
|
)
|
|
return fallback
|
|
|
|
raise RuntimeError(f"Missing documentation source directory for language '{language}' in {content_root}")
|
|
|
|
|
|
def build_language(version: dict, content_root: Path, language: str, output_dir: Path) -> None:
|
|
source_dir = resolve_source_dir(content_root, language)
|
|
conf_dir = DOCS_DIR / language
|
|
|
|
env = os.environ.copy()
|
|
env.update(
|
|
{
|
|
"DOCS_VERSION_SLUG": version["slug"],
|
|
"DOCS_VERSION_LABEL": version.get("label", version["slug"]),
|
|
"DOCS_VERSION_IS_LATEST": "1" if version.get("is_latest") else "0",
|
|
"DOCS_VERSION_MANIFEST_JSON": str(MANIFEST_PATH),
|
|
"DOCS_CONTENT_ROOT": str(source_dir),
|
|
}
|
|
)
|
|
|
|
run(
|
|
[
|
|
"sphinx-build",
|
|
"-b",
|
|
"html",
|
|
"-c",
|
|
str(conf_dir),
|
|
str(source_dir),
|
|
str(output_dir),
|
|
],
|
|
env=env,
|
|
)
|
|
|
|
|
|
def build_version(version: dict) -> None:
|
|
version_root = get_content_root(version)
|
|
try:
|
|
for language in LANGUAGES:
|
|
temp_output_dir = WORKTREE_ROOT / ".build" / version["slug"] / language
|
|
if temp_output_dir.exists():
|
|
shutil.rmtree(temp_output_dir)
|
|
temp_output_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
build_language(version, version_root, language, temp_output_dir)
|
|
|
|
if version.get("is_latest"):
|
|
target_dir = SITE_DIR / language
|
|
else:
|
|
target_dir = SITE_DIR / "versions" / version["slug"] / language
|
|
target_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
copy_tree(temp_output_dir, target_dir)
|
|
finally:
|
|
cleanup_content_root(version_root)
|
|
|
|
|
|
def get_content_root(version: dict) -> Path:
|
|
if should_use_workspace(version):
|
|
return REPO_ROOT
|
|
|
|
ref = version.get("ref", "HEAD")
|
|
ensure_ref_available(ref)
|
|
resolved_ref = resolve_ref(ref)
|
|
|
|
worktree_dir = WORKTREE_ROOT / version["slug"]
|
|
if worktree_dir.exists():
|
|
run(["git", "worktree", "remove", "--force", str(worktree_dir)])
|
|
|
|
worktree_dir.parent.mkdir(parents=True, exist_ok=True)
|
|
run(["git", "worktree", "add", "--detach", str(worktree_dir), resolved_ref])
|
|
return worktree_dir
|
|
|
|
|
|
def cleanup_content_root(content_root: Path) -> None:
|
|
if content_root == REPO_ROOT:
|
|
return
|
|
|
|
if content_root.exists():
|
|
run(["git", "worktree", "remove", "--force", str(content_root)])
|
|
|
|
|
|
def validate_manifest(manifest: dict) -> list[dict]:
|
|
published = [version for version in manifest.get("versions", []) if version.get("published")]
|
|
if not published:
|
|
raise RuntimeError("No published documentation versions found in manifest.")
|
|
if sum(1 for version in published if version.get("is_latest")) != 1:
|
|
raise RuntimeError("Manifest must contain exactly one published latest version.")
|
|
return published
|
|
|
|
|
|
def main() -> int:
|
|
manifest = load_manifest()
|
|
published_versions = validate_manifest(manifest)
|
|
|
|
prepare_site_dir()
|
|
WORKTREE_ROOT.mkdir(parents=True, exist_ok=True)
|
|
|
|
for version in published_versions:
|
|
build_version(version)
|
|
|
|
create_root_redirect(manifest.get("default_language", "en"))
|
|
print(f"Built documentation site in {SITE_DIR}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
try:
|
|
raise SystemExit(main())
|
|
except Exception as error: # noqa: BLE001
|
|
print(f"ERROR: {error}", file=sys.stderr)
|
|
raise
|