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 # Build arguments to be overridden when compiling cross-toolchain: 283 # 284 # pw_toolchain_defaults: A scope setting defaults to apply to GN targets 285 # in this toolchain. It is analogous to $pw_target_defaults in 286 # $dir_pigweed/pw_vars_default.gni. 287 # 288 # pw_toolchain_SCOPE: A copy of the invoker scope that defines the 289 # toolchain. Used for generating derivative toolchains. 290 # 291 toolchain_args = { 292 pw_toolchain_SCOPE = { 293 } 294 pw_toolchain_SCOPE = { 295 forward_variables_from(invoker, "*") 296 name = target_name 297 } 298 forward_variables_from(invoker_toolchain_args, "*") 299 300 # Disable compilation testing for static analysis toolchains. 301 pw_compilation_testing_NEGATIVE_COMPILATION_ENABLED = false 302 303 # Always disable coverage generation since we will not actually run the 304 # instrumented binaries to produce a profraw file. 305 pw_toolchain_COVERAGE_ENABLED = false 306 } 307 308 _generate_rust_tools = defined(invoker.rustc) 309 if (_generate_rust_tools) { 310 if (defined(invoker.ld)) { 311 _rustc_linker = "-Clinker=${invoker.ld}" 312 } else { 313 _rustc_linker = "" 314 } 315 316 _rustc_command = string_join( 317 " ", 318 [ 319 # TODO: b/234872510 - Ensure this works with Windows. 320 "RUST_BACKTRACE=1", 321 "{{rustenv}}", 322 invoker.rustc, 323 "{{source}}", 324 "--crate-name {{crate_name}}", 325 "--crate-type {{crate_type}}", 326 _rustc_linker, 327 "{{externs}}", 328 "{{rustdeps}}", 329 "{{rustflags}}", 330 "-D warnings", 331 "--color always", 332 "--emit=dep-info={{output}}.d,link", 333 "-o {{output_dir}}/{{target_output_name}}{{output_extension}}", 334 ]) 335 336 _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" 337 338 tool("rust_bin") { 339 description = "rustc {{output}}" 340 default_output_dir = "{{target_out_dir}}/bin" 341 depfile = "{{output}}.d" 342 command = _rustc_command 343 outputs = [ _output ] 344 } 345 346 tool("rust_rlib") { 347 description = "rustc {{output}}" 348 default_output_dir = "{{target_out_dir}}/lib" 349 depfile = "{{output}}.d" 350 output_prefix = "lib" 351 default_output_extension = ".rlib" 352 command = _rustc_command 353 outputs = [ _output ] 354 } 355 356 tool("rust_staticlib") { 357 description = "rustc {{output}}" 358 default_output_dir = "{{target_out_dir}}/lib" 359 depfile = "{{output}}.d" 360 output_prefix = "lib" 361 default_output_extension = ".a" 362 command = _rustc_command 363 outputs = [ _output ] 364 } 365 366 if (defined(invoker.is_host_toolchain) && invoker.is_host_toolchain) { 367 if (!defined(invoker_toolchain_args.current_os)) { 368 toolchain_os = "" 369 } else { 370 # Determine OS of toolchain, which is the builtin argument "current_os". 371 toolchain_os = invoker_toolchain_args.current_os 372 } 373 374 if (toolchain_os == "mac") { 375 _dylib_extension = ".dylib" 376 } else if (toolchain_os == "win") { 377 _dylib_extension = ".dll" 378 } else { 379 _dylib_extension = ".so" 380 } 381 382 tool("rust_macro") { 383 description = "rustc {{output}}" 384 default_output_dir = "{{target_out_dir}}/lib" 385 depfile = "{{output}}.d" 386 output_prefix = "lib" 387 default_output_extension = _dylib_extension 388 command = _rustc_command 389 outputs = [ _output ] 390 } 391 } 392 } 393 } 394} 395