1# Copyright 2022 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 15import("//build_overrides/pigweed.gni") 16 17import("$dir_pw_compilation_testing/negative_compilation_test.gni") 18import("$dir_pw_third_party/boringssl/boringssl.gni") 19import("$dir_pw_third_party/chre/chre.gni") 20import("$dir_pw_third_party/googletest/googletest.gni") 21import("$dir_pw_third_party/mbedtls/mbedtls.gni") 22import("$dir_pw_toolchain/universal_tools.gni") 23 24declare_args() { 25 # Regular expressions matching the paths of the source files to be excluded 26 # from the analysis. clang-tidy provides no alternative option. 27 # 28 # For example, the following disables clang-tidy on all source files in the 29 # third_party directory: 30 # 31 # pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = ["third_party/.*"] 32 # 33 pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = [] 34 35 # Disable clang-tidy for specific include paths. In the clang-tidy command, 36 # include paths that end with one of these, or match as a regular expression, 37 # are switched from -I to -isystem, which causes clang-tidy to ignore them. 38 # Unfortunately, clang-tidy provides no other way to filter header files. 39 # 40 # For example, the following ignores header files in "repo/include": 41 # 42 # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = ["repo/include"] 43 # 44 # While the following ignores all third-party header files: 45 # 46 # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [".*/third_party/.*"] 47 # 48 pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [] 49} 50 51# Third-party software with Pigweed-supported build files that do not pass all 52# clang-tidy checks. 53_excluded_third_party_dirs = [ 54 dir_pw_third_party_mbedtls, 55 dir_pw_third_party_boringssl, 56 dir_pw_third_party_googletest, 57 dir_pw_third_party_chre, 58] 59 60# Creates a toolchain target for static analysis. 61# 62# The generated toolchain runs clang-tidy on all source files that are not 63# excluded by pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES or 64# pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS. 65# 66# Args: 67# cc: (required) String indicating the C compiler to use. 68# cxx: (required) String indicating the C++ compiler to use. 69# static_analysis: (required) A scope defining args to apply to the 70# static_analysis toolchain. 71# static_analysis.enabled: (required) Bool used to indicate whether 72# static_analysis should be enabled for the toolchain where scope is 73# applied to. Note that static_analysis.enabled must be set in order to 74# use this toolchain. 75# static_analysis.clang_tidy_path: (optional) String indicating clang-tidy bin 76# to use. 77# static_analysis.cc_post: (optional) String defining additional commands to 78# append to cc tool's command list (i.e command(s) to run after cc command 79# chain). 80# static_analysis.cxx_post: (optional) String defining additional commands to 81# append to cxx tool's command list (i.e command(s) to run after cxx 82# command chain). 83template("pw_static_analysis_toolchain") { 84 invoker_toolchain_args = invoker.defaults 85 assert(defined(invoker.static_analysis), "static_analysis scope missing.") 86 _static_analysis_args = invoker.static_analysis 87 assert(defined(_static_analysis_args.enabled), 88 "static_analysis.enabled is missing") 89 assert(_static_analysis_args.enabled, 90 "static_analysis.enabled must be true to use this toolchain.") 91 92 _skipped_regexps = [] 93 _skipped_include_paths = [] 94 foreach(third_party_dir, _excluded_third_party_dirs) { 95 if (third_party_dir != "") { 96 _skipped_include_paths += [ 97 third_party_dir + "/include", 98 third_party_dir, 99 ] 100 } 101 } 102 103 _skipped_regexps += pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES 104 _skipped_include_paths += pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS 105 106 # Clang tidy is invoked by a wrapper script which implements the missing 107 # option --source-filter. 108 _clang_tidy_py_path = 109 rebase_path("$dir_pw_toolchain/py/pw_toolchain/clang_tidy.py", 110 root_build_dir) 111 _clang_tidy_py = "${python_path} ${_clang_tidy_py_path}" 112 _source_root = rebase_path("//", root_build_dir) 113 _source_exclude = "" 114 foreach(pattern, _skipped_regexps) { 115 _source_exclude = _source_exclude + " --source-exclude '${pattern}'" 116 } 117 _skip_include_path = "" 118 foreach(pattern, _skipped_include_paths) { 119 _skip_include_path = 120 _skip_include_path + " --skip-include-path '${pattern}'" 121 } 122 _clang_tidy_path = "" 123 if (defined(_static_analysis_args.clang_tidy_path)) { 124 _clang_tidy_path = 125 "--clang-tidy " + 126 rebase_path(_static_analysis_args.clang_tidy_path, root_build_dir) 127 } 128 129 toolchain(target_name) { 130 # Uncomment this line to see which toolchains generate other toolchains. 131 # print("Generating toolchain: ${target_name} by ${current_toolchain}") 132 133 tool("asm") { 134 depfile = "{{output}}.d" 135 command = pw_universal_stamp.command 136 depsformat = "gcc" 137 description = "as {{output}}" 138 outputs = [ 139 # Use {{source_file_part}}, which includes the extension, instead of 140 # {{source_name_part}} so that object files created from <file_name>.c 141 # and <file_name>.cc sources are unique. 142 "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", 143 ] 144 } 145 146 assert(defined(invoker.cc), "toolchain is missing 'cc'") 147 tool("cc") { 148 _post_command_hook = "" 149 if (defined(_static_analysis_args.cc_post) && 150 _static_analysis_args.cc_post != "") { 151 _post_command_hook += " && " + _static_analysis_args.cc_post 152 } 153 154 depfile = "{{output}}.d" 155 command = string_join(" ", 156 [ 157 _clang_tidy_py, 158 _source_exclude, 159 _skip_include_path, 160 _clang_tidy_path, 161 "--source-file {{source}}", 162 "--source-root '${_source_root}'", 163 "--export-fixes {{output}}.yaml", 164 "--", 165 invoker.cc, 166 "END_OF_INVOKER", 167 "-MMD -MF $depfile", # Write out dependencies. 168 "{{cflags}}", 169 "{{cflags_c}}", # Must come after {{cflags}}. 170 "{{defines}}", 171 "{{include_dirs}}", 172 "-c {{source}}", 173 "-o {{output}}", 174 ]) + " && touch {{output}}" + _post_command_hook 175 depsformat = "gcc" 176 description = "clang-tidy {{source}}" 177 outputs = 178 [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] 179 } 180 181 assert(defined(invoker.cxx), "toolchain is missing 'cxx'") 182 tool("cxx") { 183 _post_command_hook = "" 184 if (defined(_static_analysis_args.cxx_post) && 185 _static_analysis_args.cxx_post != "") { 186 _post_command_hook += " && " + _static_analysis_args.cxx_post 187 } 188 189 depfile = "{{output}}.d" 190 command = string_join(" ", 191 [ 192 _clang_tidy_py, 193 _source_exclude, 194 _skip_include_path, 195 _clang_tidy_path, 196 "--source-file {{source}}", 197 "--source-root '${_source_root}'", 198 "--export-fixes {{output}}.yaml", 199 "--", 200 invoker.cxx, 201 "END_OF_INVOKER", 202 "-MMD -MF $depfile", # Write out dependencies. 203 "{{cflags}}", 204 "{{cflags_cc}}", # Must come after {{cflags}}. 205 "{{defines}}", 206 "{{include_dirs}}", 207 "-c {{source}}", 208 "-o {{output}}", 209 ]) + " && touch {{output}}" + _post_command_hook 210 depsformat = "gcc" 211 description = "clang-tidy {{source}}" 212 outputs = 213 [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] 214 } 215 216 tool("objc") { 217 depfile = "{{output}}.d" 218 command = pw_universal_stamp.command 219 depsformat = "gcc" 220 description = "objc {{source}}" 221 outputs = 222 [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] 223 } 224 225 tool("objcxx") { 226 depfile = "{{output}}.d" 227 command = pw_universal_stamp.command 228 depsformat = "gcc" 229 description = "objc++ {{output}}" 230 outputs = 231 [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] 232 } 233 234 tool("alink") { 235 command = "rm -f {{output}} && touch {{output}}" 236 description = "ar {{target_output_name}}{{output_extension}}" 237 outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ] 238 default_output_extension = ".a" 239 default_output_dir = "{{target_out_dir}}/lib" 240 } 241 242 tool("link") { 243 if (host_os == "win") { 244 # Force the extension to '.bat', empty bat scripts are still 245 # executable and will not raise errors. 246 _output = "{{output_dir}}/{{target_output_name}}.bat" 247 command = pw_universal_stamp.command 248 default_output_extension = ".bat" 249 } else { 250 default_output_extension = "" 251 _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" 252 command = "touch {{output}} && chmod +x {{output}}" 253 } 254 description = "ld $_output" 255 outputs = [ _output ] 256 default_output_dir = "{{target_out_dir}}/bin" 257 } 258 259 tool("solink") { 260 _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" 261 command = pw_universal_stamp.command 262 description = "ld -shared $_output" 263 outputs = [ _output ] 264 default_output_dir = "{{target_out_dir}}/lib" 265 default_output_extension = ".so" 266 } 267 268 tool("stamp") { 269 # GN-ism: GN gets mad if you directly forward the contents of 270 # pw_universal_stamp. 271 _stamp = pw_universal_stamp 272 forward_variables_from(_stamp, "*") 273 } 274 275 tool("copy") { 276 # GN-ism: GN gets mad if you directly forward the contents of 277 # pw_universal_copy. 278 _copy = pw_universal_copy 279 forward_variables_from(_copy, "*") 280 } 281 282 tool("rust_bin") { 283 _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" 284 command = pw_universal_stamp.command 285 description = "rustc {{output}}" 286 outputs = [ _output ] 287 default_output_dir = "{{target_out_dir}}/bin" 288 } 289 290 # Build arguments to be overridden when compiling cross-toolchain: 291 # 292 # pw_toolchain_defaults: A scope setting defaults to apply to GN targets 293 # in this toolchain. It is analogous to $pw_target_defaults in 294 # $dir_pigweed/pw_vars_default.gni. 295 # 296 # pw_toolchain_SCOPE: A copy of the invoker scope that defines the 297 # toolchain. Used for generating derivative toolchains. 298 # 299 toolchain_args = { 300 pw_toolchain_SCOPE = { 301 } 302 pw_toolchain_SCOPE = { 303 forward_variables_from(invoker, "*") 304 name = target_name 305 } 306 forward_variables_from(invoker_toolchain_args, "*") 307 308 # Disable compilation testing for static analysis toolchains. 309 pw_compilation_testing_NEGATIVE_COMPILATION_ENABLED = false 310 311 # Always disable coverage generation since we will not actually run the 312 # instrumented binaries to produce a profraw file. 313 pw_toolchain_COVERAGE_ENABLED = false 314 } 315 } 316} 317