xref: /llvm-project/libc/utils/docgen/header.py (revision 88bcf7283b35b979ace0c6be32736b13f6b771ae)
1# ====- Information about standard headers used by docgen  ----*- python -*--==#
2#
3# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4# See https://llvm.org/LICENSE.txt for license information.
5# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6#
7# ==-------------------------------------------------------------------------==#
8from pathlib import Path
9from typing import Generator
10
11
12class Header:
13    """
14    Maintains implementation information about a standard header file:
15    * where does its implementation dir live
16    * where is its macros file
17    * where is its docgen yaml file
18
19    By convention, the macro-only part of a header file is in a header-specific
20    file somewhere in the directory tree with root at
21    ``$LLVM_PROJECT_ROOT/libc/include/llvm-libc-macros``.  Docgen expects that
22    if a macro is implemented, that it appears in a string
23    ``#define MACRO_NAME`` in some ``*-macros.h`` file in the directory tree.
24    Docgen searches for this string in the file to set the implementation status
25    shown in the generated rst docs rendered as html for display at
26    <libc.llvm.org>.
27
28    By convention, each function for a header is implemented in a function-specific
29    cpp file somewhere in the directory tree with root at, e.g,
30    ``$LLVM_PROJECT_ROOT/libc/src/fenv``. Some headers have architecture-specific
31    implementations, like ``math``, and some don't, like ``fenv``. Docgen uses the
32    presence of this function-specific cpp file to set the implementation status
33    shown in the generated rst docs rendered as html for display at
34    <libc.llvm.org>.
35    """
36
37    def __init__(self, header_name: str):
38        """
39        :param header_name: e.g., ``"threads.h"`` or ``"signal.h"``
40        """
41        self.name = header_name
42        self.stem = header_name.rstrip(".h")
43        self.docgen_root = Path(__file__).parent
44        self.libc_root = self.docgen_root.parent.parent
45        self.docgen_yaml = self.docgen_root / Path(header_name).with_suffix(".yaml")
46        self.fns_dir = Path(self.libc_root, "src", self.stem)
47        self.macros_dir = Path(self.libc_root, "include", "llvm-libc-macros")
48
49    def macro_file_exists(self) -> bool:
50        for _ in self.__get_macro_files():
51            return True
52
53        return False
54
55    def fns_dir_exists(self) -> bool:
56        return self.fns_dir.exists() and self.fns_dir.is_dir()
57
58    def implements_fn(self, fn_name: str) -> bool:
59        for _ in self.fns_dir.glob(f"**/{fn_name}.cpp"):
60            return True
61
62        return False
63
64    def implements_macro(self, m_name: str) -> bool:
65        """
66        Some macro files are in, e.g.,
67        ``$LLVM_PROJECT_ROOT/libc/include/llvm-libc-macros/fenv-macros.h``,
68        but others are in subdirectories, e.g., ``signal.h`` has the macro
69        definitions in
70        ``$LLVM_PROJECT_ROOT/libc/include/llvm-libc-macros/linux/signal-macros.h``.
71
72        :param m_name: name of macro, e.g., ``FE_ALL_EXCEPT``
73        """
74        for f in self.__get_macro_files():
75            if f"#define {m_name}" in f.read_text():
76                return True
77
78        return False
79
80    def __get_macro_files(self) -> Generator[Path, None, None]:
81        """
82        This function uses a glob on, e.g., ``"**/fcntl.macros.h"`` because the
83        macro file might be located in a subdirectory:
84        libc/include/llvm-libc-macros/fcntl-macros.h
85        libc/include/llvm-libc-macros/linux/fcntl-macros.h
86
87        When a header would be nested in a dir (such as arpa/, sys/, etc) we
88        instead use a hyphen in the name.
89        libc/include/llvm-libc-macros/sys-mman-macros.h
90        """
91        stem = self.stem.replace("/", "-")
92        return self.macros_dir.glob(f"**/{stem}-macros.h")
93