1# Copyright (c) 2019 Intel Corporation 2# 3# SPDX-License-Identifier: Apache-2.0 4 5# based on http://protips.readthedocs.io/link-roles.html 6 7import re 8import subprocess 9from collections.abc import Sequence 10from pathlib import Path 11from typing import Any, Final 12 13from docutils import nodes 14from sphinx.util import logging 15 16ZEPHYR_BASE: Final[str] = Path(__file__).parents[3] 17 18try: 19 import west.manifest 20 21 try: 22 west_manifest = west.manifest.Manifest.from_file() 23 except west.util.WestNotFound: 24 west_manifest = None 25except ImportError: 26 west_manifest = None 27 28 29logger = logging.getLogger(__name__) 30 31 32def get_github_rev(): 33 try: 34 output = subprocess.check_output( 35 "git describe --exact-match", shell=True, stderr=subprocess.DEVNULL 36 ) 37 except subprocess.CalledProcessError: 38 return "main" 39 40 return output.strip().decode("utf-8") 41 42 43def setup(app): 44 app.add_role("zephyr_file", modulelink("zephyr")) 45 app.add_role("zephyr_raw", modulelink("zephyr", format="raw")) 46 app.add_role("module_file", modulelink()) 47 48 app.add_config_value("link_roles_manifest_baseurl", None, "env") 49 app.add_config_value("link_roles_manifest_project", None, "env") 50 app.add_config_value("link_roles_manifest_project_broken_links_ignore_globs", [], "env") 51 52 # The role just creates new nodes based on information in the 53 # arguments; its behavior doesn't depend on any other documents. 54 return { 55 "parallel_read_safe": True, 56 "parallel_write_safe": True, 57 } 58 59 60def modulelink(default_module=None, format="blob"): 61 def role( 62 name: str, 63 rawtext: str, 64 text: str, 65 lineno: int, 66 inliner, 67 options: dict[str, Any] | None = None, 68 content: Sequence[str] = (), 69 ): 70 if options is None: 71 options = {} 72 module = default_module 73 rev = get_github_rev() 74 config = inliner.document.settings.env.app.config 75 baseurl = config.link_roles_manifest_baseurl 76 source, line = inliner.reporter.get_source_and_line(lineno) 77 trace = f"at '{source}:{line}'" 78 79 m = re.search(r"(.*)\s*<(.*)>", text) 80 if m: 81 link_text = m.group(1) 82 link = m.group(2) 83 else: 84 link_text = text 85 link = text 86 87 line_ref = "" 88 line_match = re.search(r"(.+?)(?:#(L\d+(?:-L\d+)?))?$", link) 89 if line_match and line_match.group(2): 90 link = line_match.group(1) 91 line_ref = f"?plain=1#{line_match.group(2)}" 92 93 module_match = re.search(r"(.+?):\s*(.+)", link) 94 if module_match: 95 module = module_match.group(1).strip() 96 link = module_match.group(2).strip() 97 98 # Try to get a module repository's GitHub URL from the manifest. 99 # 100 # This allows e.g. building the docs in downstream Zephyr-based 101 # software with forks of the zephyr repository, and getting 102 # :zephyr_file: / :zephyr_raw: output that links to the fork, 103 # instead of mainline zephyr. 104 projects = [p.name for p in west_manifest.projects] if west_manifest else [] 105 if module in projects: 106 project = west_manifest.get_projects([module])[0] 107 baseurl = project.url 108 rev = project.revision 109 # No module provided 110 elif module is None: 111 raise ValueError( 112 f"Role 'module_file' must take a module as an argument\n\t{trace}" 113 ) 114 # Invalid module provided 115 elif module != config.link_roles_manifest_project: 116 logger.debug(f"Module {module} not found in the west manifest") 117 # Baseurl for manifest project not set 118 elif baseurl is None: 119 raise ValueError( 120 f"Configuration value `link_roles_manifest_baseurl` not set\n\t{trace}" 121 ) 122 123 if module == config.link_roles_manifest_project: 124 p = Path(source).relative_to(inliner.document.settings.env.srcdir) 125 if not any( 126 p.match(glob) 127 for glob in config.link_roles_manifest_project_broken_links_ignore_globs 128 ) and not Path(ZEPHYR_BASE, link).exists(): 129 logger.warning( 130 f"{link} not found in {config.link_roles_manifest_project} {trace}" 131 ) 132 133 url = f"{baseurl}/{format}/{rev}/{link}{line_ref}" 134 node = nodes.reference(rawtext, link_text, refuri=url, **options) 135 return [node], [] 136 137 return role 138