• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Checks that source files are listed in build files, such as BUILD.bazel."""
15
16import logging
17from typing import Callable, Sequence
18
19from pw_cli.file_filter import FileFilter
20from pw_presubmit import build, format_code, git_repo, presubmit_context
21from pw_presubmit.presubmit import Check, filter_paths
22from pw_presubmit.presubmit_context import (
23    PresubmitContext,
24    PresubmitFailure,
25)
26
27_LOG: logging.Logger = logging.getLogger(__name__)
28
29# The filter is used twice for each source_is_in_* check.  First to decide
30# whether the check should be run. Once it's running, we use ctx.all_paths
31# instead of ctx.paths since we want to check that all files are in the build,
32# not just changed files, but we need to run ctx.all_paths through the same
33# filter within the check or we won't properly ignore files that the caller
34# asked to be ignored.
35
36_DEFAULT_BAZEL_EXTENSIONS = (*format_code.C_FORMAT.extensions,)
37
38
39def bazel(
40    source_filter: FileFilter,
41    files_and_extensions_to_check: Sequence[str] = _DEFAULT_BAZEL_EXTENSIONS,
42) -> Check:
43    """Create a presubmit check that ensures source files are in Bazel files.
44
45    Args:
46        source_filter: filter that selects files that must be in the Bazel build
47        files_and_extensions_to_check: files and extensions to look for (the
48            source_filter might match build files that won't be in the build but
49            this should only match source files)
50    """
51
52    @filter_paths(file_filter=source_filter)
53    def source_is_in_bazel_build(ctx: PresubmitContext):
54        """Checks that source files are in the Bazel build."""
55
56        paths = source_filter.filter(ctx.all_paths)
57        paths = presubmit_context.apply_exclusions(ctx, paths)
58
59        missing = build.check_bazel_build_for_files(
60            files_and_extensions_to_check,
61            paths,
62            bazel_dirs=[ctx.root],
63        )
64
65        if missing:
66            with ctx.failure_summary_log.open('w') as outs:
67                print('Missing files:', file=outs)
68                for miss in missing:
69                    print(miss, file=outs)
70
71            _LOG.warning('All source files must appear in BUILD.bazel files')
72            raise PresubmitFailure
73
74    return source_is_in_bazel_build
75
76
77_DEFAULT_GN_EXTENSIONS = (
78    'setup.cfg',
79    '.toml',
80    '.rst',
81    '.py',
82    *format_code.C_FORMAT.extensions,
83)
84
85
86def gn(  # pylint: disable=invalid-name
87    source_filter: FileFilter,
88    files_and_extensions_to_check: Sequence[str] = _DEFAULT_GN_EXTENSIONS,
89) -> Check:
90    """Create a presubmit check that ensures source files are in GN files.
91
92    Args:
93        source_filter: filter that selects files that must be in the GN build
94        files_and_extensions_to_check: files and extensions to look for (the
95            source_filter might match build files that won't be in the build but
96            this should only match source files)
97    """
98
99    @filter_paths(file_filter=source_filter)
100    def source_is_in_gn_build(ctx: PresubmitContext):
101        """Checks that source files are in the GN build."""
102
103        paths = source_filter.filter(ctx.all_paths)
104        paths = presubmit_context.apply_exclusions(ctx, paths)
105
106        missing = build.check_gn_build_for_files(
107            files_and_extensions_to_check,
108            paths,
109            gn_build_files=git_repo.list_files(
110                pathspecs=['BUILD.gn', '*BUILD.gn'], repo_path=ctx.root
111            ),
112        )
113
114        if missing:
115            with ctx.failure_summary_log.open('w') as outs:
116                print('Missing files:', file=outs)
117                for miss in missing:
118                    print(miss, file=outs)
119
120            _LOG.warning('All source files must appear in BUILD.gn files')
121            raise PresubmitFailure
122
123    return source_is_in_gn_build
124
125
126_DEFAULT_CMAKE_EXTENSIONS = (*format_code.C_FORMAT.extensions,)
127
128
129def cmake(
130    source_filter: FileFilter,
131    run_cmake: Callable[[PresubmitContext], None],
132    files_and_extensions_to_check: Sequence[str] = _DEFAULT_CMAKE_EXTENSIONS,
133) -> Check:
134    """Create a presubmit check that ensures source files are in CMake files.
135
136    Args:
137        source_filter: filter that selects files that must be in the CMake build
138        run_cmake: callable that takes a PresubmitContext and invokes CMake
139        files_and_extensions_to_check: files and extensions to look for (the
140            source_filter might match build files that won't be in the build but
141            this should only match source files)
142    """
143
144    to_check = tuple(files_and_extensions_to_check)
145
146    @filter_paths(file_filter=source_filter)
147    def source_is_in_cmake_build(ctx: PresubmitContext):
148        """Checks that source files are in the CMake build."""
149
150        paths = source_filter.filter(ctx.all_paths)
151        paths = presubmit_context.apply_exclusions(ctx, paths)
152
153        run_cmake(ctx)
154        missing = build.check_compile_commands_for_files(
155            ctx.output_dir / 'compile_commands.json',
156            (f for f in paths if str(f).endswith(to_check)),
157        )
158
159        if missing:
160            with ctx.failure_summary_log.open('w') as outs:
161                print('Missing files:', file=outs)
162                for miss in missing:
163                    print(miss, file=outs)
164
165            _LOG.warning(
166                'Files missing from CMake:\n%s',
167                '\n'.join(str(f) for f in missing),
168            )
169            raise PresubmitFailure
170
171    return source_is_in_cmake_build
172