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