This changes to pass full path of MAINTAINERS.yml to get_maintainer.Maintainers(). Without this, Maintainers would use git to find the top level of Zephyr tree. This restricts building of doc only when the build directory is under Zephyr root. Since we have ZEPHYR_BASE in gh_utils, we can pass full path of MAINTAINERS.yml to Maintainers() so that doc build directory no longer has to be under Zephyr root. Fixes #65037 Signed-off-by: Daniel Leung <daniel.leung@intel.com>
257 lines
7.3 KiB
Python
257 lines
7.3 KiB
Python
"""
|
|
Git/GitHub utilities for Sphinx
|
|
###############################
|
|
|
|
Copyright (c) 2021 Nordic Semiconductor ASA
|
|
Copyright (c) 2023 The Linux Foundation
|
|
|
|
SPDX-License-Identifier: Apache-2.0
|
|
|
|
Introduction
|
|
============
|
|
|
|
This Sphinx extension can be used to obtain various Git and GitHub related metadata for a page.
|
|
This is useful, for example, when adding features like "Open on GitHub" on top
|
|
of pages, direct links to open a GitHub issue regarding a page, or date of the most recent commit
|
|
to a page.
|
|
|
|
The extension installs the following Jinja filter:
|
|
|
|
* ``gh_link_get_blob_url``: Returns a URL to the source of a page on GitHub.
|
|
* ``gh_link_get_edit_url``: Returns a URL to edit the given page on GitHub.
|
|
* ``gh_link_get_open_issue_url``: Returns a URL to open a new issue regarding the given page.
|
|
* ``git_info``: Returns the date and SHA1 of the last commit made to a page (if this page is
|
|
managed by Git).
|
|
|
|
Configuration options
|
|
=====================
|
|
|
|
- ``gh_link_version``: GitHub version to use in the URL (e.g. "main")
|
|
- ``gh_link_base_url``: Base URL used as a prefix for generated URLs.
|
|
- ``gh_link_prefixes``: Mapping of pages (regex) <> GitHub prefix.
|
|
- ``gh_link_exclude``: List of pages (regex) that will not report a URL. Useful
|
|
for, e.g., auto-generated pages not in Git.
|
|
"""
|
|
|
|
from functools import partial
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from textwrap import dedent
|
|
from typing import Final, Optional, Tuple
|
|
from urllib.parse import quote
|
|
|
|
from sphinx.application import Sphinx
|
|
from sphinx.util.i18n import format_date
|
|
|
|
ZEPHYR_BASE : Final[str] = Path(__file__).parents[3]
|
|
SCRIPTS : Final[str] = ZEPHYR_BASE / "scripts"
|
|
sys.path.insert(0, str(SCRIPTS))
|
|
|
|
from get_maintainer import Maintainers
|
|
|
|
MAINTAINERS : Final[Maintainers] = Maintainers(filename=f"{ZEPHYR_BASE}/MAINTAINERS.yml")
|
|
|
|
|
|
__version__ = "0.1.0"
|
|
|
|
|
|
def get_page_prefix(app: Sphinx, pagename: str) -> str:
|
|
"""Return the prefix that needs to be added to the page path to get its location in the
|
|
repository.
|
|
|
|
If pagename refers to a page that is automatically generated by Sphinx or if it matches one of
|
|
the patterns in ``gh_link_exclude`` configuration option, return None.
|
|
|
|
Args:
|
|
app: Sphinx instance.
|
|
pagename: Page name (path).
|
|
|
|
Returns:
|
|
Prefix if applicable, None otherwise.
|
|
"""
|
|
|
|
if not os.path.isfile(app.env.doc2path(pagename)):
|
|
return None
|
|
|
|
for exclude in app.config.gh_link_exclude:
|
|
if re.match(exclude, pagename):
|
|
return None
|
|
|
|
found_prefix = ""
|
|
for pattern, prefix in app.config.gh_link_prefixes.items():
|
|
if re.match(pattern, pagename):
|
|
found_prefix = prefix
|
|
break
|
|
|
|
return found_prefix
|
|
|
|
|
|
def gh_link_get_url(app: Sphinx, pagename: str, mode: str = "blob") -> Optional[str]:
|
|
"""Obtain GitHub URL for the given page.
|
|
|
|
Args:
|
|
app: Sphinx instance.
|
|
mode: Typically "edit", or "blob".
|
|
pagename: Page name (path).
|
|
|
|
Returns:
|
|
GitHub URL if applicable, None otherwise.
|
|
"""
|
|
|
|
page_prefix = get_page_prefix(app, pagename)
|
|
if page_prefix is None:
|
|
return None
|
|
|
|
return "/".join(
|
|
[
|
|
app.config.gh_link_base_url,
|
|
mode,
|
|
app.config.gh_link_version,
|
|
page_prefix,
|
|
app.env.doc2path(pagename, False),
|
|
]
|
|
)
|
|
|
|
|
|
def gh_link_get_open_issue_url(app: Sphinx, pagename: str, sha1: str) -> Optional[str]:
|
|
"""Link to open a new Github issue regarding "pagename" with title, body, and
|
|
labels already pre-filled with useful information.
|
|
|
|
Args:
|
|
app: Sphinx instance.
|
|
pagename: Page name (path).
|
|
|
|
Returns:
|
|
URL to open a new issue if applicable, None otherwise.
|
|
"""
|
|
|
|
page_prefix = get_page_prefix(app, pagename)
|
|
if page_prefix is None:
|
|
return None
|
|
|
|
rel_path = os.path.join(
|
|
os.path.relpath(ZEPHYR_BASE),
|
|
page_prefix,
|
|
app.env.doc2path(pagename, False),
|
|
)
|
|
|
|
title = quote(f"doc: Documentation issue in '{pagename}'")
|
|
labels = quote("area: Documentation")
|
|
areas = MAINTAINERS.path2areas(rel_path)
|
|
if areas:
|
|
labels += "," + ",".join([label for area in areas for label in area.labels])
|
|
body = quote(
|
|
dedent(
|
|
f"""\
|
|
**Describe the bug**
|
|
|
|
<< Please describe the issue here >>
|
|
<< You may also want to update the automatically generated issue title above. >>
|
|
|
|
**Environment**
|
|
|
|
* Page: `{pagename}`
|
|
* Version: {app.config.gh_link_version}
|
|
* SHA-1: {sha1}
|
|
"""
|
|
)
|
|
)
|
|
|
|
return f"{app.config.gh_link_base_url}/issues/new?title={title}&labels={labels}&body={body}"
|
|
|
|
|
|
def git_info_filter(app: Sphinx, pagename) -> Optional[Tuple[str, str]]:
|
|
"""Return a tuple with the date and SHA1 of the last commit made to a page.
|
|
|
|
Arguments:
|
|
app {Sphinx} -- Sphinx application object
|
|
pagename {str} -- Page name
|
|
|
|
Returns:
|
|
Optional[Tuple[str, str]] -- Tuple with the date and SHA1 of the last commit made to the
|
|
page, or None if the page is not in the repo (generated file, or manually authored file not
|
|
yet tracked by git).
|
|
"""
|
|
|
|
page_prefix = get_page_prefix(app, pagename)
|
|
if page_prefix is None:
|
|
return None
|
|
|
|
orig_path = os.path.join(
|
|
ZEPHYR_BASE,
|
|
page_prefix,
|
|
app.env.doc2path(pagename, False),
|
|
)
|
|
|
|
# Check if the file is tracked by git
|
|
try:
|
|
subprocess.check_output(
|
|
["git", "ls-files", "--error-unmatch", orig_path],
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
except subprocess.CalledProcessError:
|
|
return None
|
|
|
|
try:
|
|
date_and_sha1 = (
|
|
subprocess.check_output(
|
|
[
|
|
"git",
|
|
"log",
|
|
"-1",
|
|
"--format=%ad %H",
|
|
"--date=unix",
|
|
orig_path,
|
|
],
|
|
stderr=subprocess.STDOUT,
|
|
)
|
|
.decode("utf-8")
|
|
.strip()
|
|
)
|
|
date, sha1 = date_and_sha1.split(" ", 1)
|
|
date_object = datetime.fromtimestamp(int(date))
|
|
last_update_fmt = app.config.html_last_updated_fmt
|
|
if last_update_fmt is not None:
|
|
date = format_date(last_update_fmt, date=date_object, language=app.config.language)
|
|
|
|
return (date, sha1)
|
|
except subprocess.CalledProcessError:
|
|
return None
|
|
|
|
def add_jinja_filter(app: Sphinx):
|
|
if app.builder.format != "html":
|
|
return
|
|
|
|
app.builder.templates.environment.filters["gh_link_get_blob_url"] = partial(
|
|
gh_link_get_url, app, mode="blob"
|
|
)
|
|
|
|
app.builder.templates.environment.filters["gh_link_get_edit_url"] = partial(
|
|
gh_link_get_url, app, mode="edit"
|
|
)
|
|
|
|
app.builder.templates.environment.filters["gh_link_get_open_issue_url"] = partial(
|
|
gh_link_get_open_issue_url, app
|
|
)
|
|
|
|
app.builder.templates.environment.filters["git_info"] = partial(git_info_filter, app)
|
|
|
|
|
|
def setup(app: Sphinx):
|
|
app.add_config_value("gh_link_version", "", "")
|
|
app.add_config_value("gh_link_base_url", "", "")
|
|
app.add_config_value("gh_link_prefixes", {}, "")
|
|
app.add_config_value("gh_link_exclude", [], "")
|
|
|
|
app.connect("builder-inited", add_jinja_filter)
|
|
|
|
return {
|
|
"version": __version__,
|
|
"parallel_read_safe": True,
|
|
"parallel_write_safe": True,
|
|
}
|