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_presubmit import build, format_code, git_repo 20from pw_presubmit.presubmit import ( 21 Check, 22 FileFilter, 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 @source_filter.apply_to_check() 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 58 missing = build.check_bazel_build_for_files( 59 files_and_extensions_to_check, 60 paths, 61 bazel_dirs=[ctx.root], 62 ) 63 64 if missing: 65 with ctx.failure_summary_log.open('w') as outs: 66 print('Missing files:', file=outs) 67 for miss in missing: 68 print(miss, file=outs) 69 70 _LOG.warning('All source files must appear in BUILD.bazel files') 71 raise PresubmitFailure 72 73 return source_is_in_bazel_build 74 75 76_DEFAULT_GN_EXTENSIONS = ( 77 'setup.cfg', 78 '.toml', 79 '.rst', 80 '.py', 81 *format_code.C_FORMAT.extensions, 82) 83 84 85def gn( # pylint: disable=invalid-name 86 source_filter: FileFilter, 87 files_and_extensions_to_check: Sequence[str] = _DEFAULT_GN_EXTENSIONS, 88) -> Check: 89 """Create a presubmit check that ensures source files are in GN files. 90 91 Args: 92 source_filter: filter that selects files that must be in the GN build 93 files_and_extensions_to_check: files and extensions to look for (the 94 source_filter might match build files that won't be in the build but 95 this should only match source files) 96 """ 97 98 @source_filter.apply_to_check() 99 def source_is_in_gn_build(ctx: PresubmitContext): 100 """Checks that source files are in the GN build.""" 101 102 paths = source_filter.filter(ctx.all_paths) 103 104 missing = build.check_gn_build_for_files( 105 files_and_extensions_to_check, 106 paths, 107 gn_build_files=git_repo.list_files( 108 pathspecs=['BUILD.gn', '*BUILD.gn'], repo_path=ctx.root 109 ), 110 ) 111 112 if missing: 113 with ctx.failure_summary_log.open('w') as outs: 114 print('Missing files:', file=outs) 115 for miss in missing: 116 print(miss, file=outs) 117 118 _LOG.warning('All source files must appear in BUILD.gn files') 119 raise PresubmitFailure 120 121 return source_is_in_gn_build 122 123 124_DEFAULT_CMAKE_EXTENSIONS = (*format_code.C_FORMAT.extensions,) 125 126 127def cmake( 128 source_filter: FileFilter, 129 run_cmake: Callable[[PresubmitContext], None], 130 files_and_extensions_to_check: Sequence[str] = _DEFAULT_CMAKE_EXTENSIONS, 131) -> Check: 132 """Create a presubmit check that ensures source files are in CMake files. 133 134 Args: 135 source_filter: filter that selects files that must be in the CMake build 136 run_cmake: callable that takes a PresubmitContext and invokes CMake 137 files_and_extensions_to_check: files and extensions to look for (the 138 source_filter might match build files that won't be in the build but 139 this should only match source files) 140 """ 141 142 to_check = tuple(files_and_extensions_to_check) 143 144 @source_filter.apply_to_check() 145 def source_is_in_cmake_build(ctx: PresubmitContext): 146 """Checks that source files are in the CMake build.""" 147 148 paths = source_filter.filter(ctx.all_paths) 149 150 run_cmake(ctx) 151 missing = build.check_compile_commands_for_files( 152 ctx.output_dir / 'compile_commands.json', 153 (f for f in paths if str(f).endswith(to_check)), 154 ) 155 156 if missing: 157 with ctx.failure_summary_log.open('w') as outs: 158 print('Missing files:', file=outs) 159 for miss in missing: 160 print(miss, file=outs) 161 162 _LOG.warning( 163 'Files missing from CMake:\n%s', 164 '\n'.join(str(f) for f in missing), 165 ) 166 raise PresubmitFailure 167 168 return source_is_in_cmake_build 169