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