diff --git a/.github/workflows/deploy_docs.yml b/.github/workflows/deploy_docs.yml new file mode 100644 index 00000000..0424bd8e --- /dev/null +++ b/.github/workflows/deploy_docs.yml @@ -0,0 +1,187 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* +# Workflow to build and deploy Sphinx documentation to GitHub Pages with versioning +name: Deploy Documentation to GitHub Pages +on: + push: + branches: [main] + tags: ['v*'] + pull_request: + branches: [main] + paths: + - 'bazel/rules/rules_score/docs/**' + - '.github/workflows/deploy_docs.yml' + workflow_dispatch: +permissions: + contents: write # needed for uploading release assets + pages: write + id-token: write +concurrency: + group: "pages" + cancel-in-progress: false +jobs: + build-docs: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6.0.2 + with: + fetch-depth: 0 # full history needed for tag-based version detection + - name: Setup Bazel with cache + uses: bazel-contrib/setup-bazel@0.19.0 + with: + disk-cache: true + repository-cache: true + bazelisk-cache: true + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y pkg-config libcairo2-dev + - name: Determine version + id: version + run: | + if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then + echo "version=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT" + echo "is_tag=true" >> "$GITHUB_OUTPUT" + elif [[ "${GITHUB_REF}" == refs/heads/main ]]; then + echo "version=latest" >> "$GITHUB_OUTPUT" + echo "is_tag=false" >> "$GITHUB_OUTPUT" + else + BRANCH_NAME="${GITHUB_REF#refs/heads/}" + echo "version=preview/${BRANCH_NAME//\//-}" >> "$GITHUB_OUTPUT" + echo "is_tag=false" >> "$GITHUB_OUTPUT" + fi + - name: Build Sphinx documentation + run: bazel build //bazel/rules/rules_score:rules_score_doc + - name: Prepare documentation output + run: | + BAZEL_BIN="$(bazel info bazel-bin)" + HTML_DIR="${BAZEL_BIN}/bazel/rules/rules_score/rules_score_doc/html" + + if [[ ! -d "${HTML_DIR}" ]]; then + echo "::error::Bazel HTML output not found at ${HTML_DIR}" + exit 1 + fi + + mkdir -p docs_output + cp -r "${HTML_DIR}/." docs_output/ + chmod -R u+w docs_output/ + + # Prevent Jekyll from ignoring _static directories + touch docs_output/.nojekyll + + # Inject version flyout CSS and JS into all HTML files + REPO_NAME="${{ github.event.repository.name }}" + find docs_output -name '*.html' -exec sed -i \ + -e "s||\n|" \ + -e "s||\n|" \ + {} + + - name: Verify build output + run: | + if [[ ! -f docs_output/index.html ]]; then + echo "::error::Documentation build failed - no index.html found" + exit 1 + fi + echo "Documentation built successfully" + find docs_output -type f | wc -l | xargs -I{} echo "Total files: {}" + - name: Upload docs to release + if: startsWith(github.ref, 'refs/tags/v') + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + VERSION="${{ steps.version.outputs.version }}" + tar czf "docs-${VERSION}.tar.gz" -C docs_output . + gh release create "${VERSION}" --draft=false --notes "Release ${VERSION}" 2>/dev/null || true + gh release upload "${VERSION}" "docs-${VERSION}.tar.gz" --clobber + - name: Assemble publish tree + if: github.event_name != 'pull_request' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + VERSION="${{ steps.version.outputs.version }}" + IS_TAG="${{ steps.version.outputs.is_tag }}" + REPO_URL="https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}" + + # --- current build --- + mkdir -p "publish/${VERSION}" + cp -a docs_output/. "publish/${VERSION}/" + + # --- restore previously released versions --- + for tag in $(gh api "repos/${{ github.repository }}/releases" --paginate --jq '.[].tag_name' | grep '^v' || true); do + [[ "${tag}" == "${VERSION}" ]] && continue + if gh release download "${tag}" --pattern "docs-${tag}.tar.gz" --dir /tmp 2>/dev/null; then + mkdir -p "publish/${tag}" + tar xzf "/tmp/docs-${tag}.tar.gz" -C "publish/${tag}" + rm -f "/tmp/docs-${tag}.tar.gz" + echo "Restored ${tag} from release artifact" + else + echo "::warning::No docs artifact found for release ${tag}" + fi + done + + # --- set stable alias to newest tagged version --- + if [[ "${IS_TAG}" == "true" ]]; then + cp -a docs_output/. publish/stable/ + else + NEWEST_TAG="$(find publish -maxdepth 1 -mindepth 1 -type d -name 'v*' -printf '%f\n' | sort -rV | head -1)" + if [[ -n "${NEWEST_TAG}" ]]; then + cp -a "publish/${NEWEST_TAG}/." publish/stable/ + fi + fi + + # --- shared assets --- + mkdir -p publish/_shared/css publish/_shared/js + cp docs/_static/css/version_flyout.css publish/_shared/css/ + cp docs/_static/js/version_flyout.js publish/_shared/js/ + + # --- root index & Jekyll bypass --- + cp docs/_gh_pages/index.html publish/index.html + touch publish/.nojekyll + + # --- generate switcher.json --- + { + echo '[' + echo " {\"name\": \"latest\", \"version\": \"latest\", \"url\": \"${REPO_URL}/latest/\"}" + + if [[ -d publish/stable ]]; then + echo " ,{\"name\": \"stable\", \"version\": \"stable\", \"url\": \"${REPO_URL}/stable/\", \"preferred\": true}" + fi + + for dir in $(find publish -maxdepth 1 -mindepth 1 -type d -name 'v*' | sort -rV); do + ver="$(basename "${dir}")" + echo " ,{\"name\": \"${ver}\", \"version\": \"${ver}\", \"url\": \"${REPO_URL}/${ver}/\"}" + done + + echo ']' + } > publish/switcher.json + - name: Upload Pages artifact + if: github.event_name != 'pull_request' + uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1 + with: + path: publish + - name: Print preview URL + if: github.event_name != 'pull_request' + run: | + echo "::notice::Documentation deployed to: https://${{ github.repository_owner }}.github.io/${{ github.event.repository.name }}/${{ steps.version.outputs.version }}/" + deploy-pages: + needs: build-docs + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/bazel/rules/rules_score/templates/conf.template.py b/bazel/rules/rules_score/templates/conf.template.py index 454254ed..7730c694 100644 --- a/bazel/rules/rules_score/templates/conf.template.py +++ b/bazel/rules/rules_score/templates/conf.template.py @@ -153,4 +153,8 @@ # HTML theme html_theme = "sphinx_rtd_theme" + +# Note: version_flyout.css and version_flyout.js are injected by the +# deploy workflow via _shared/ paths so they load once across all versions. + logger.debug("#" * 80) diff --git a/docs/_gh_pages/index.html b/docs/_gh_pages/index.html new file mode 100644 index 00000000..f25bd0fe --- /dev/null +++ b/docs/_gh_pages/index.html @@ -0,0 +1,26 @@ + + + + + + Redirecting to latest documentation... + + + + +

Redirecting to latest documentation...

+ + diff --git a/docs/_static/css/version_flyout.css b/docs/_static/css/version_flyout.css new file mode 100644 index 00000000..a86e870d --- /dev/null +++ b/docs/_static/css/version_flyout.css @@ -0,0 +1,138 @@ +/* ******************************************************************************* + * Copyright (c) 2026 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + * ******************************************************************************* */ + +/* RTD-style version flyout panel */ +.version-flyout { + position: fixed; + bottom: 0; + right: 20px; + z-index: 9999; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; + font-size: 14px; +} + +.version-flyout__toggle { + display: flex; + align-items: center; + gap: 8px; + padding: 8px 16px; + background: #1f1f2e; + color: #fff; + border: none; + border-radius: 6px 6px 0 0; + cursor: pointer; + font-size: 14px; + font-weight: 500; +} + +.version-flyout__toggle:hover { + background: #2a2a3d; +} + +.version-flyout__toggle .flyout-icon { + font-size: 16px; +} + +.version-flyout__toggle .flyout-current { + color: #27ae60; + font-weight: bold; +} + +.version-flyout__toggle .flyout-arrow { + margin-left: auto; + transition: transform 0.2s; +} + +.version-flyout__toggle.active .flyout-arrow { + transform: rotate(180deg); +} + +.version-flyout__panel { + display: none; + background: #1f1f2e; + color: #ccc; + padding: 16px; + border-radius: 6px 6px 0 0; + min-width: 260px; + box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.3); +} + +.version-flyout__panel.open { + display: block; +} + +.version-flyout__panel h4 { + color: #fff; + margin: 0 0 8px 0; + font-size: 13px; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.version-flyout__panel .flyout-section { + margin-bottom: 12px; +} + +.version-flyout__panel .flyout-versions { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.version-flyout__panel .flyout-versions a { + color: #55b4d4; + text-decoration: none; + padding: 2px 8px; + border-radius: 3px; + font-size: 13px; +} + +.version-flyout__panel .flyout-versions a:hover { + background: rgba(255, 255, 255, 0.1); + color: #7dd3fc; +} + +.version-flyout__panel .flyout-versions a.active { + color: #27ae60; + font-weight: bold; +} + +.version-flyout__panel .flyout-links { + border-top: 1px solid #333; + padding-top: 10px; + margin-top: 10px; +} + +.version-flyout__panel .flyout-links a { + display: inline-block; + color: #55b4d4; + text-decoration: none; + margin-right: 12px; + font-size: 13px; +} + +.version-flyout__panel .flyout-links a:hover { + color: #7dd3fc; +} + +.version-flyout__footer { + font-size: 11px; + color: #888; + margin-top: 10px; + text-align: center; +} + +.version-flyout__footer a { + color: #55b4d4; + text-decoration: none; +} diff --git a/docs/_static/js/version_flyout.js b/docs/_static/js/version_flyout.js new file mode 100644 index 00000000..3879310c --- /dev/null +++ b/docs/_static/js/version_flyout.js @@ -0,0 +1,102 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +/** + * RTD-style version flyout for GitHub Pages. + * Reads versions from switcher.json and renders a floating panel. + */ +(function () { + "use strict"; + + // Determine base URL and current version from the URL path + var pathParts = window.location.pathname.split("/").filter(Boolean); + // Expect: ///... + var repoName = pathParts[0] || ""; + var currentVersion = pathParts[1] || "latest"; + // Handle preview/ pattern + if (currentVersion === "preview" && pathParts[2]) { + currentVersion = "preview/" + pathParts[2]; + } + + var baseUrl = + window.location.origin + "/" + repoName; + var switcherUrl = baseUrl + "/switcher.json"; + + function createFlyout(versions) { + var container = document.createElement("div"); + container.className = "version-flyout"; + + // Build version links + var versionLinks = versions + .map(function (v) { + var activeClass = v.version === currentVersion ? ' class="active"' : ""; + return '" + v.name + ""; + }) + .join("\n"); + + container.innerHTML = + '
' + + '
' + + "

Versions

" + + '
' + + versionLinks + + "
" + + "
" + + ' " + + ' " + + "
" + + '"; + + document.body.appendChild(container); + + // Toggle behavior + var toggle = document.getElementById("flyout-toggle"); + var panel = document.getElementById("flyout-panel"); + toggle.addEventListener("click", function () { + panel.classList.toggle("open"); + toggle.classList.toggle("active"); + }); + } + + function getGitHubRepo() { + // Try to extract from meta or fallback + var metaRepo = document.querySelector('meta[name="github-repo"]'); + if (metaRepo) return metaRepo.getAttribute("content"); + // Fallback based on GitHub Pages domain + var host = window.location.hostname; + var user = host.split(".")[0]; + return user + "/" + repoName; + } + + // Fetch switcher.json and build the flyout + fetch(switcherUrl) + .then(function (response) { + if (!response.ok) throw new Error("switcher.json not found"); + return response.json(); + }) + .then(function (versions) { + createFlyout(versions); + }) + .catch(function (err) { + console.warn("Version flyout: Could not load switcher.json", err); + }); +})();