File: check_source_files_in_bazel_build.py

package info (click to toggle)
pico-sdk 2.1.1-1
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 26,552 kB
  • sloc: ansic: 146,841; asm: 13,423; python: 2,417; cpp: 2,171; yacc: 381; lex: 270; makefile: 32; sh: 13; javascript: 13
file content (210 lines) | stat: -rwxr-xr-x 5,659 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
#!/usr/bin/env python3
#
# Copyright (c) 2024 Raspberry Pi (Trading) Ltd.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Check Bazel build file source coverage. Reports files that:
# - Are in the repo but not included in a BUILD.bazel file.
# - Are referenced in a BUILD.bazel file but are not present.
#
# Usage:
#   python tools/check_source_files_in_bazel_build.py
#
# Run from anywhere in the pico-sdk repo.

import logging
from pathlib import Path
import shlex
import subprocess
from typing import (
    Container,
    Iterable,
    List,
    Optional,
    Set,
)
import sys

from bazel_common import (
    SDK_ROOT,
    bazel_command,
    override_picotool_arg,
    parse_common_args,
    setup_logging,
)

_LOG = logging.getLogger(__file__)

CPP_HEADER_EXTENSIONS = (
    ".h",
    ".hpp",
    ".hxx",
    ".h++",
    ".hh",
    ".H",
)
CPP_SOURCE_EXTENSIONS = (
    ".c",
    ".cpp",
    ".cxx",
    ".c++",
    ".cc",
    ".C",
    ".S",
    ".inc",
    ".inl",
)

IGNORED_FILE_PATTERNS = (
    # Doxygen only files.
    "**/index.h",
    "**/doc.h",
)


def get_paths_from_command(source_dir: Path, *args, **kwargs) -> Set[Path]:
    """Runs a command and reads Bazel //-style paths from it."""
    process = subprocess.run(
        args, check=False, capture_output=True, cwd=source_dir, **kwargs
    )

    if process.returncode:
        _LOG.error("Command invocation failed with return code %d!", process.returncode)
        _LOG.error(
            "Command: %s",
            " ".join(shlex.quote(str(arg)) for arg in args),
        )
        _LOG.error(
            "Output:\n%s",
            process.stderr.decode(),
        )
        sys.exit(1)

    files = set()

    for line in process.stdout.splitlines():
        path = line.strip().lstrip(b"/").replace(b":", b"/").decode()
        files.add(Path(path))

    return files


def check_bazel_build_for_files(
    bazel_extensions_to_check: Container[str],
    files: Iterable[Path],
    bazel_dirs: Iterable[Path],
    picotool_dir: Optional[Path],
) -> List[Path]:
    """Checks that source files are in the Bazel builds.

    Args:
        bazel_extensions_to_check: which file suffixes to look for in Bazel
        files: the files that should be checked
        bazel_dirs: directories in which to run bazel query

    Returns:
        a list of missing files; will be empty if there were no missing files
    """

    # Collect all paths in the Bazel builds files.
    bazel_build_source_files: Set[Path] = set()
    pictool_override = override_picotool_arg(picotool_dir) if picotool_dir else ""
    for directory in bazel_dirs:
        bazel_build_source_files.update(
            get_paths_from_command(
                directory, bazel_command(), "query", pictool_override, 'kind("source file", //...:*)',
            )
        )
    missing_from_bazel: List[Path] = []
    referenced_in_bazel_missing: List[Path] = []

    if not bazel_dirs:
        _LOG.error("No bazel directories to check.")
        raise RuntimeError

    for path in (p for p in files if p.suffix in bazel_extensions_to_check):
        if path not in bazel_build_source_files:
            missing_from_bazel.append(path)

    for path in (
        p for p in bazel_build_source_files if p.suffix in bazel_extensions_to_check
    ):
        if path not in files:
            referenced_in_bazel_missing.append(path)

    if missing_from_bazel:
        _LOG.warning(
            "Files not included in the Bazel build:\n\n%s\n",
            "\n".join("  " + str(x) for x in sorted(missing_from_bazel)),
        )

    if referenced_in_bazel_missing:
        _LOG.warning(
            "Files referenced in the Bazel build that are missing:\n\n%s\n",
            "\n".join("  " + str(x) for x in sorted(referenced_in_bazel_missing)),
        )

    return missing_from_bazel + referenced_in_bazel_missing


def git_ls_files_by_extension(file_suffixes: Iterable[str]) -> Iterable[Path]:
    """List git source files.

    Returns: A list of files matching the provided extensions.
    """
    git_command = ["git", "ls-files"]
    for pattern in file_suffixes:
        git_command.append("*" + pattern)

    bazel_file_list = subprocess.run(
        git_command,
        cwd=SDK_ROOT,
        text=True,
        check=True,
        capture_output=True,
    ).stdout

    bazel_files = [Path(f) for f in bazel_file_list.splitlines()]
    return bazel_files


def check_sources_in_bazel_build(picotool_dir) -> int:
    # List files using git ls-files
    all_source_files = git_ls_files_by_extension(
        CPP_HEADER_EXTENSIONS + CPP_SOURCE_EXTENSIONS
    )

    # Filter out any unwanted files.
    ignored_files = []
    for source in all_source_files:
        for pattern in IGNORED_FILE_PATTERNS:
            if source.match(pattern):
                ignored_files.append(source)
    _LOG.debug(
        "Ignoring files:\n\n%s\n", "\n".join("  " + str(f) for f in ignored_files)
    )

    source_files = list(set(all_source_files) - set(ignored_files))

    # Check for missing files.
    _LOG.info("Checking all source files are accounted for in Bazel.")
    missing_files = check_bazel_build_for_files(
        bazel_extensions_to_check=CPP_HEADER_EXTENSIONS + CPP_SOURCE_EXTENSIONS,
        files=source_files,
        bazel_dirs=[Path(SDK_ROOT)],
        picotool_dir=picotool_dir,
    )

    if missing_files:
        _LOG.error("Missing files found.")
        return 1

    _LOG.info("\x1b[32mSuccess!\x1b[0m All files accounted for in Bazel.")
    return 0


if __name__ == "__main__":
    setup_logging()
    args = parse_common_args()
    sys.exit(check_sources_in_bazel_build(args.picotool_dir))