mirror of
https://github.com/microsoft/mscclpp.git
synced 2026-06-08 15:30:41 +00:00
This PR fix the issue of generating docs when we take https://github.com/microsoft/mscclpp/pull/724 into main branch. Build docs for main branch separately. Use HEAD request instead of GET to check if a page exist. Filter out versions before v0.4.0 in generate_versions.py. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Binyang Li <binyli@microsoft.com>
This commit is contained in:
2
.github/workflows/doc-build.yaml
vendored
2
.github/workflows/doc-build.yaml
vendored
@@ -34,5 +34,5 @@ jobs:
|
||||
cd docs
|
||||
rm -rf doxygen _build py_api
|
||||
doxygen
|
||||
make html
|
||||
make multiversion
|
||||
touch _build/html/.nojekyll
|
||||
|
||||
3
.github/workflows/gh-pages.yml
vendored
3
.github/workflows/gh-pages.yml
vendored
@@ -43,7 +43,8 @@ jobs:
|
||||
cd docs
|
||||
rm -rf doxygen _build py_api
|
||||
doxygen
|
||||
make html
|
||||
# Use multiversion target to build all versions
|
||||
make multiversion
|
||||
touch _build/html/.nojekyll
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -6,4 +6,5 @@ __pycache__
|
||||
.*.swp
|
||||
.idea/
|
||||
*.so
|
||||
_codeql_detected_source_root
|
||||
docs/_static/versions.js
|
||||
_codeql_detected_source_root
|
||||
@@ -5,17 +5,46 @@
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SPHINXMULTIVERSION ?= sphinx-multiversion
|
||||
SOURCEDIR = .
|
||||
BUILDDIR = _build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@echo "Usage:"
|
||||
@echo " make html - Build single-version HTML (fast, for development)"
|
||||
@echo " make multiversion - Build all versions with sphinx-multiversion"
|
||||
@echo " make clean - Remove build directory"
|
||||
@echo ""
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
.PHONY: help Makefile generate-versions multiversion clean
|
||||
|
||||
# Generate versions.js from git tags before building
|
||||
generate-versions:
|
||||
@python3 generate_versions.py
|
||||
|
||||
# Build all documentation versions using sphinx-multiversion
|
||||
# NOTE: This target should only be run from the main branch to ensure
|
||||
# the root docs correctly represent "main (dev)". For local development
|
||||
# on feature branches, use "make html" instead.
|
||||
# GitHub Actions workflow runs this on main branch only.
|
||||
multiversion: generate-versions
|
||||
@cd .. && python3 -m setuptools_scm --force-write-version-files
|
||||
@export LC_ALL=C.UTF-8; $(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@echo "Building versioned docs from tags..."
|
||||
@export LC_ALL=C.UTF-8; $(SPHINXMULTIVERSION) "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O)
|
||||
@mkdir -p $(BUILDDIR)/html/_static
|
||||
@cp -f _static/versions.js $(BUILDDIR)/html/_static/ 2>/dev/null || true
|
||||
@cp -f _static/version-selector.js $(BUILDDIR)/html/_static/ 2>/dev/null || true
|
||||
|
||||
# Clean build directory
|
||||
clean:
|
||||
@rm -rf $(BUILDDIR)
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
# This builds single-version only (fast for development).
|
||||
%: Makefile generate-versions
|
||||
@cd .. && python3 -m setuptools_scm --force-write-version-files
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
@export LC_ALL=C.UTF-8; $(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
211
docs/_static/version-selector.js
vendored
Normal file
211
docs/_static/version-selector.js
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
/**
|
||||
* Version selector for sphinx-multiversion documentation.
|
||||
*
|
||||
* The DEFINED_VERSIONS array is auto-generated from git tags by generate_versions.py
|
||||
* which runs automatically during 'make html'. This ensures the version list stays
|
||||
* in sync with sphinx-multiversion without manual updates.
|
||||
*
|
||||
* The versions.js file (loaded before this script) defines DEFINED_VERSIONS.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// DEFINED_VERSIONS is defined in versions.js (auto-generated from git tags)
|
||||
// Fallback to main only if versions.js failed to load
|
||||
const versions = (typeof DEFINED_VERSIONS !== 'undefined') ? DEFINED_VERSIONS : [
|
||||
{ name: 'main (dev)', path: '', version: 'main' }
|
||||
];
|
||||
|
||||
/**
|
||||
* Detect the base path for GitHub Pages project sites.
|
||||
* For project sites, the URL is like /repository-name/v0.8.0/guide.html
|
||||
* For root sites or local development, the URL is like /v0.8.0/guide.html
|
||||
* @returns {string} The base path (e.g., '/mscclpp' or '')
|
||||
*/
|
||||
function detectBasePath() {
|
||||
const path = window.location.pathname;
|
||||
// Match pattern: /base-path/vX.Y.Z/... or /base-path/main/...
|
||||
// The base path is everything before the version or main directory
|
||||
const match = path.match(/^(\/[^\/]+)?(?=\/(v\d+\.\d+\.\d+|main)\/)/);
|
||||
if (match && match[1]) {
|
||||
return match[1];
|
||||
}
|
||||
// Check if we're at a root that's actually a project site
|
||||
// Look for common indicators like the repository name in the path
|
||||
const projectMatch = path.match(/^(\/[^\/]+)(?=\/)/);
|
||||
if (projectMatch) {
|
||||
// Verify this isn't a version path at root
|
||||
const potentialBase = projectMatch[1];
|
||||
if (!potentialBase.match(/^\/v\d+\.\d+\.\d+$/) && potentialBase !== '/main') {
|
||||
// Check if the remaining path contains version info
|
||||
const remainingPath = path.substring(potentialBase.length);
|
||||
if (remainingPath.match(/^\/(v\d+\.\d+\.\d+|main)\//)) {
|
||||
return potentialBase;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
function detectCurrentVersion() {
|
||||
const path = window.location.pathname;
|
||||
// Check for version tags first
|
||||
// Match version tags in the format v0.0.0 within the URL path
|
||||
// This works for both /v0.8.0/... and /mscclpp/v0.8.0/...
|
||||
const match = path.match(/\/(v\d+\.\d+\.\d+)\//);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
// Check for main branch directory
|
||||
if (path.includes('/main/')) {
|
||||
return 'main';
|
||||
}
|
||||
// If at root (no version in path), it's main
|
||||
return 'main';
|
||||
}
|
||||
|
||||
function createVersionSelector() {
|
||||
const currentVersion = detectCurrentVersion();
|
||||
const basePath = detectBasePath();
|
||||
const searchDiv = document.querySelector('.wy-side-nav-search');
|
||||
|
||||
if (!searchDiv) return;
|
||||
|
||||
// Find the title link (mscclpp)
|
||||
const titleLink = searchDiv.querySelector('a.icon-home');
|
||||
|
||||
// Create version selector container
|
||||
const selectorDiv = document.createElement('div');
|
||||
selectorDiv.style.padding = '10px';
|
||||
selectorDiv.style.paddingTop = '5px';
|
||||
selectorDiv.style.paddingBottom = '10px';
|
||||
|
||||
const select = document.createElement('select');
|
||||
select.id = 'version-selector';
|
||||
select.style.width = '100%';
|
||||
select.style.padding = '5px';
|
||||
select.style.backgroundColor = '#2c2c2c';
|
||||
select.style.color = '#ffffff';
|
||||
select.style.border = '1px solid #404040';
|
||||
select.style.borderRadius = '3px';
|
||||
|
||||
// Add options
|
||||
versions.forEach(function(version) {
|
||||
const option = document.createElement('option');
|
||||
const isSelected = currentVersion === version.version;
|
||||
|
||||
// Build the URL - use absolute paths with base path for GitHub Pages
|
||||
let url;
|
||||
const currentPath = window.location.pathname;
|
||||
|
||||
// Extract the page path relative to the version directory
|
||||
// For /mscclpp/v0.7.0/design/design.html -> design/design.html
|
||||
// For /v0.7.0/design/design.html -> design/design.html
|
||||
// For /mscclpp/index.html -> index.html
|
||||
// For /index.html -> index.html
|
||||
let relativePath;
|
||||
|
||||
// Remove base path first if present
|
||||
let pathWithoutBase = currentPath;
|
||||
if (basePath && currentPath.startsWith(basePath)) {
|
||||
pathWithoutBase = currentPath.substring(basePath.length);
|
||||
}
|
||||
|
||||
const versionMatch = pathWithoutBase.match(/^\/(v\d+\.\d+\.\d+)\/(.*)/);
|
||||
if (versionMatch) {
|
||||
// We're in a versioned directory
|
||||
relativePath = versionMatch[2] || 'index.html';
|
||||
} else {
|
||||
// We're at root (main/dev) - remove leading slash
|
||||
relativePath = pathWithoutBase.substring(1) || 'index.html';
|
||||
// Handle /main/ path
|
||||
const mainMatch = pathWithoutBase.match(/^\/main\/(.*)/);
|
||||
if (mainMatch) {
|
||||
relativePath = mainMatch[1] || 'index.html';
|
||||
}
|
||||
}
|
||||
|
||||
if (version.version === 'main' && version.path === '') {
|
||||
// For main (dev) at root
|
||||
url = basePath + '/' + relativePath;
|
||||
} else {
|
||||
// For versioned releases
|
||||
url = basePath + '/' + version.path + '/' + relativePath;
|
||||
}
|
||||
|
||||
option.value = url;
|
||||
option.textContent = version.name;
|
||||
if (isSelected) {
|
||||
option.selected = true;
|
||||
}
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
select.addEventListener('change', function() {
|
||||
if (this.value) {
|
||||
const baseUrl = this.value;
|
||||
const currentHash = window.location.hash;
|
||||
const targetUrl = baseUrl + currentHash;
|
||||
|
||||
// Calculate fallback URL (version index page)
|
||||
let fallbackUrl;
|
||||
const versionMatch = baseUrl.match(/\/(v\d+\.\d+\.\d+)\//);
|
||||
if (versionMatch) {
|
||||
fallbackUrl = basePath + '/' + versionMatch[1] + '/index.html';
|
||||
} else {
|
||||
fallbackUrl = basePath + '/index.html';
|
||||
}
|
||||
|
||||
// Check if the target page exists using HEAD request
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(function() { controller.abort(); }, 2000);
|
||||
|
||||
fetch(baseUrl, {
|
||||
method: 'HEAD',
|
||||
signal: controller.signal
|
||||
})
|
||||
.then(function(response) {
|
||||
clearTimeout(timeoutId);
|
||||
if (response.ok) {
|
||||
window.location.href = targetUrl;
|
||||
} else {
|
||||
// Page doesn't exist, navigate to version index
|
||||
window.location.href = fallbackUrl;
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
clearTimeout(timeoutId);
|
||||
// On network error or timeout, try fallback first
|
||||
// This handles cases where the page truly doesn't exist
|
||||
window.location.href = fallbackUrl;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
selectorDiv.appendChild(select);
|
||||
|
||||
// Insert after the title link in the searchDiv
|
||||
if (titleLink) {
|
||||
// Insert after the title link element
|
||||
const nextElement = titleLink.nextSibling;
|
||||
if (nextElement) {
|
||||
searchDiv.insertBefore(selectorDiv, nextElement);
|
||||
} else {
|
||||
searchDiv.appendChild(selectorDiv);
|
||||
}
|
||||
} else {
|
||||
// Fallback: insert at the beginning of searchDiv
|
||||
searchDiv.insertBefore(selectorDiv, searchDiv.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', createVersionSelector);
|
||||
} else {
|
||||
createVersionSelector();
|
||||
}
|
||||
})();
|
||||
59
docs/conf.py
59
docs/conf.py
@@ -43,8 +43,19 @@ extensions = [
|
||||
"sphinx.ext.napoleon",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx_autodoc_typehints",
|
||||
"sphinx_multiversion",
|
||||
]
|
||||
|
||||
# sphinx-multiversion configuration
|
||||
# Only build tagged versions - main branch is built separately via sphinx-build
|
||||
# to avoid issues with config loading during ref checkout
|
||||
smv_tag_whitelist = r"^v\d+\.\d+\.\d+$"
|
||||
smv_branch_whitelist = r"^$" # Don't build any branches, only tags
|
||||
smv_remote_whitelist = None
|
||||
smv_released_pattern = r"^tags/.*$"
|
||||
smv_outputdir_format = "{ref.name}"
|
||||
smv_prefer_remote_refs = False
|
||||
|
||||
autosummary_generate = True
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
@@ -77,3 +88,51 @@ mermaid_init_js = "mermaid.initialize({startOnLoad:true});"
|
||||
|
||||
html_theme = "sphinx_rtd_theme"
|
||||
html_static_path = ["_static"]
|
||||
|
||||
# Build html_js_files dynamically - only include files that exist
|
||||
# versions.js is auto-generated by generate_versions.py before build
|
||||
html_js_files = []
|
||||
_static_dir = Path(__file__).parent / "_static"
|
||||
if (_static_dir / "versions.js").exists():
|
||||
html_js_files.append("versions.js")
|
||||
if (_static_dir / "version-selector.js").exists():
|
||||
html_js_files.append("version-selector.js")
|
||||
|
||||
|
||||
def setup(app):
|
||||
"""Set up custom Sphinx build hooks for sphinx-multiversion support.
|
||||
|
||||
This function registers a build-finished event handler that copies the
|
||||
version selector JavaScript files to a shared location accessible by all
|
||||
versioned documentation builds.
|
||||
|
||||
Args:
|
||||
app: The Sphinx application instance.
|
||||
"""
|
||||
import shutil
|
||||
|
||||
def copy_version_files(app, exception):
|
||||
"""Copy version JS files to the root build directory after a successful build.
|
||||
|
||||
When using sphinx-multiversion, each version's documentation is built into
|
||||
its own subdirectory (e.g., _build/html/v0.8.0/). The version selector
|
||||
JavaScript files need to be available at the root _static directory
|
||||
(_build/html/_static/) so they can be shared across all versions and
|
||||
properly navigate between different documentation versions.
|
||||
|
||||
Args:
|
||||
app: The Sphinx application instance.
|
||||
exception: Exception raised during build, or None if build succeeded.
|
||||
"""
|
||||
if exception is None: # Only copy if build succeeded
|
||||
source_static = Path(app.srcdir) / "_static"
|
||||
dest_root = Path(app.outdir).parent / "_static"
|
||||
dest_root.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Copy both versions.js and version-selector.js
|
||||
for filename in ["versions.js", "version-selector.js"]:
|
||||
source = source_static / filename
|
||||
if source.exists():
|
||||
shutil.copy2(source, dest_root / filename)
|
||||
|
||||
app.connect("build-finished", copy_version_files)
|
||||
|
||||
94
docs/generate_versions.py
Normal file
94
docs/generate_versions.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# Copyright (c) Microsoft Corporation.
|
||||
# Licensed under the MIT License.
|
||||
|
||||
"""Generate versions.js from git tags for the documentation version selector.
|
||||
|
||||
This script reads git tags matching the sphinx-multiversion pattern (vX.Y.Z)
|
||||
and generates a JavaScript file containing the version list. This ensures the
|
||||
version selector stays in sync with available documentation versions without
|
||||
requiring manual updates.
|
||||
|
||||
Usage:
|
||||
python generate_versions.py
|
||||
|
||||
The script should be run before building documentation with sphinx-multiversion.
|
||||
"""
|
||||
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def get_git_tags():
|
||||
"""Get all version tags from git matching vX.Y.Z pattern.
|
||||
|
||||
Filters out versions before v0.4.0 as they don't have compatible docs structure
|
||||
for sphinx-multiversion.
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "tag", "-l", "v*.*.*"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
tags = result.stdout.strip().split("\n")
|
||||
# Filter to match sphinx-multiversion pattern: ^v\d+\.\d+\.\d+$
|
||||
version_pattern = re.compile(r"^v\d+\.\d+\.\d+$")
|
||||
valid_tags = []
|
||||
for tag in tags:
|
||||
if tag and version_pattern.match(tag):
|
||||
# Filter out versions before v0.4.0 (no compatible docs structure)
|
||||
match = re.match(r"v(\d+)\.(\d+)\.(\d+)", tag)
|
||||
if match:
|
||||
major, minor, patch = int(match.group(1)), int(match.group(2)), int(match.group(3))
|
||||
# Include v0.4.0 and later
|
||||
if major > 0 or (major == 0 and minor >= 4):
|
||||
valid_tags.append(tag)
|
||||
return valid_tags
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
|
||||
def version_sort_key(version):
|
||||
"""Extract (major, minor, patch) tuple for sorting."""
|
||||
match = re.match(r"v(\d+)\.(\d+)\.(\d+)", version)
|
||||
if match:
|
||||
return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
|
||||
return (0, 0, 0)
|
||||
|
||||
|
||||
def generate_versions_js(output_path):
|
||||
"""Generate versions.js file from git tags."""
|
||||
tags = get_git_tags()
|
||||
|
||||
# Sort versions in descending order (newest first)
|
||||
tags.sort(key=version_sort_key, reverse=True)
|
||||
|
||||
# Build the version list with main (dev) first
|
||||
version_list = [{"name": "main (dev)", "path": "", "version": "main"}]
|
||||
|
||||
for i, version in enumerate(tags):
|
||||
name = f"{version} (latest)" if i == 0 else version
|
||||
version_list.append({"name": name, "path": version, "version": version})
|
||||
|
||||
# Generate JavaScript content
|
||||
js_content = f"""\
|
||||
// Auto-generated from git tags by generate_versions.py - do not edit manually
|
||||
// Run 'python generate_versions.py' or 'make html' to regenerate
|
||||
const DEFINED_VERSIONS = {json.dumps(version_list, indent=4)};
|
||||
"""
|
||||
|
||||
# Write to output path
|
||||
output_path = Path(output_path)
|
||||
output_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
output_path.write_text(js_content)
|
||||
print(f"Generated {output_path} with {len(version_list)} versions")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Generate versions.js in _static directory
|
||||
script_dir = Path(__file__).parent
|
||||
output_file = script_dir / "_static" / "versions.js"
|
||||
generate_versions_js(output_file)
|
||||
@@ -5,4 +5,5 @@ pybind11
|
||||
sphinx_rtd_theme
|
||||
sphinxcontrib-mermaid
|
||||
sphinx-autodoc-typehints
|
||||
sphinx-multiversion==0.2.4
|
||||
setuptools_scm
|
||||
|
||||
Reference in New Issue
Block a user