# Copyright 2022 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import("//build_overrides/pigweed.gni") import("$dir_pw_compilation_testing/negative_compilation_test.gni") import("$dir_pw_third_party/boringssl/boringssl.gni") import("$dir_pw_third_party/chre/chre.gni") import("$dir_pw_third_party/googletest/googletest.gni") import("$dir_pw_third_party/mbedtls/mbedtls.gni") import("$dir_pw_toolchain/universal_tools.gni") declare_args() { # Regular expressions matching the paths of the source files to be excluded # from the analysis. clang-tidy provides no alternative option. # # For example, the following disables clang-tidy on all source files in the # third_party directory: # # pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = ["third_party/.*"] # pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES = [] # Disable clang-tidy for specific include paths. In the clang-tidy command, # include paths that end with one of these, or match as a regular expression, # are switched from -I to -isystem, which causes clang-tidy to ignore them. # Unfortunately, clang-tidy provides no other way to filter header files. # # For example, the following ignores header files in "repo/include": # # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = ["repo/include"] # # While the following ignores all third-party header files: # # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [".*/third_party/.*"] # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS = [] } # Third-party software with Pigweed-supported build files that do not pass all # clang-tidy checks. _excluded_third_party_dirs = [ dir_pw_third_party_mbedtls, dir_pw_third_party_boringssl, dir_pw_third_party_googletest, dir_pw_third_party_chre, ] # Creates a toolchain target for static analysis. # # The generated toolchain runs clang-tidy on all source files that are not # excluded by pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES or # pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS. # # Args: # cc: (required) String indicating the C compiler to use. # cxx: (required) String indicating the C++ compiler to use. # static_analysis: (required) A scope defining args to apply to the # static_analysis toolchain. # static_analysis.enabled: (required) Bool used to indicate whether # static_analysis should be enabled for the toolchain where scope is # applied to. Note that static_analysis.enabled must be set in order to # use this toolchain. # static_analysis.clang_tidy_path: (optional) String indicating clang-tidy bin # to use. # static_analysis.cc_post: (optional) String defining additional commands to # append to cc tool's command list (i.e command(s) to run after cc command # chain). # static_analysis.cxx_post: (optional) String defining additional commands to # append to cxx tool's command list (i.e command(s) to run after cxx # command chain). template("pw_static_analysis_toolchain") { invoker_toolchain_args = invoker.defaults assert(defined(invoker.static_analysis), "static_analysis scope missing.") _static_analysis_args = invoker.static_analysis assert(defined(_static_analysis_args.enabled), "static_analysis.enabled is missing") assert(_static_analysis_args.enabled, "static_analysis.enabled must be true to use this toolchain.") _skipped_regexps = [] _skipped_include_paths = [] foreach(third_party_dir, _excluded_third_party_dirs) { if (third_party_dir != "") { _skipped_include_paths += [ third_party_dir + "/include", third_party_dir, ] } } _skipped_regexps += pw_toolchain_STATIC_ANALYSIS_SKIP_SOURCES_RES _skipped_include_paths += pw_toolchain_STATIC_ANALYSIS_SKIP_INCLUDE_PATHS # Clang tidy is invoked by a wrapper script which implements the missing # option --source-filter. _clang_tidy_py_path = rebase_path("$dir_pw_toolchain/py/pw_toolchain/clang_tidy.py", root_build_dir) _clang_tidy_py = "${python_path} ${_clang_tidy_py_path}" _source_root = rebase_path("//", root_build_dir) _source_exclude = "" foreach(pattern, _skipped_regexps) { _source_exclude = _source_exclude + " --source-exclude '${pattern}'" } _skip_include_path = "" foreach(pattern, _skipped_include_paths) { _skip_include_path = _skip_include_path + " --skip-include-path '${pattern}'" } _clang_tidy_path = "" if (defined(_static_analysis_args.clang_tidy_path)) { _clang_tidy_path = "--clang-tidy " + rebase_path(_static_analysis_args.clang_tidy_path, root_build_dir) } toolchain(target_name) { # Uncomment this line to see which toolchains generate other toolchains. # print("Generating toolchain: ${target_name} by ${current_toolchain}") tool("asm") { depfile = "{{output}}.d" command = pw_universal_stamp.command depsformat = "gcc" description = "as {{output}}" outputs = [ # Use {{source_file_part}}, which includes the extension, instead of # {{source_name_part}} so that object files created from .c # and .cc sources are unique. "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o", ] } assert(defined(invoker.cc), "toolchain is missing 'cc'") tool("cc") { _post_command_hook = "" if (defined(_static_analysis_args.cc_post) && _static_analysis_args.cc_post != "") { _post_command_hook += " && " + _static_analysis_args.cc_post } depfile = "{{output}}.d" command = string_join(" ", [ _clang_tidy_py, _source_exclude, _skip_include_path, _clang_tidy_path, "--source-file {{source}}", "--source-root '${_source_root}'", "--export-fixes {{output}}.yaml", "--", invoker.cc, "END_OF_INVOKER", "-MMD -MF $depfile", # Write out dependencies. "{{cflags}}", "{{cflags_c}}", # Must come after {{cflags}}. "{{defines}}", "{{include_dirs}}", "-c {{source}}", "-o {{output}}", ]) + " && touch {{output}}" + _post_command_hook depsformat = "gcc" description = "clang-tidy {{source}}" outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] } assert(defined(invoker.cxx), "toolchain is missing 'cxx'") tool("cxx") { _post_command_hook = "" if (defined(_static_analysis_args.cxx_post) && _static_analysis_args.cxx_post != "") { _post_command_hook += " && " + _static_analysis_args.cxx_post } depfile = "{{output}}.d" command = string_join(" ", [ _clang_tidy_py, _source_exclude, _skip_include_path, _clang_tidy_path, "--source-file {{source}}", "--source-root '${_source_root}'", "--export-fixes {{output}}.yaml", "--", invoker.cxx, "END_OF_INVOKER", "-MMD -MF $depfile", # Write out dependencies. "{{cflags}}", "{{cflags_cc}}", # Must come after {{cflags}}. "{{defines}}", "{{include_dirs}}", "-c {{source}}", "-o {{output}}", ]) + " && touch {{output}}" + _post_command_hook depsformat = "gcc" description = "clang-tidy {{source}}" outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] } tool("objc") { depfile = "{{output}}.d" command = pw_universal_stamp.command depsformat = "gcc" description = "objc {{source}}" outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] } tool("objcxx") { depfile = "{{output}}.d" command = pw_universal_stamp.command depsformat = "gcc" description = "objc++ {{output}}" outputs = [ "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o" ] } tool("alink") { command = "rm -f {{output}} && touch {{output}}" description = "ar {{target_output_name}}{{output_extension}}" outputs = [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ] default_output_extension = ".a" default_output_dir = "{{target_out_dir}}/lib" } tool("link") { if (host_os == "win") { # Force the extension to '.bat', empty bat scripts are still # executable and will not raise errors. _output = "{{output_dir}}/{{target_output_name}}.bat" command = pw_universal_stamp.command default_output_extension = ".bat" } else { default_output_extension = "" _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" command = "touch {{output}} && chmod +x {{output}}" } description = "ld $_output" outputs = [ _output ] default_output_dir = "{{target_out_dir}}/bin" } tool("solink") { _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" command = pw_universal_stamp.command description = "ld -shared $_output" outputs = [ _output ] default_output_dir = "{{target_out_dir}}/lib" default_output_extension = ".so" } tool("stamp") { # GN-ism: GN gets mad if you directly forward the contents of # pw_universal_stamp. _stamp = pw_universal_stamp forward_variables_from(_stamp, "*") } tool("copy") { # GN-ism: GN gets mad if you directly forward the contents of # pw_universal_copy. _copy = pw_universal_copy forward_variables_from(_copy, "*") } tool("rust_bin") { _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}" command = pw_universal_stamp.command description = "rustc {{output}}" outputs = [ _output ] default_output_dir = "{{target_out_dir}}/bin" } # Build arguments to be overridden when compiling cross-toolchain: # # pw_toolchain_defaults: A scope setting defaults to apply to GN targets # in this toolchain. It is analogous to $pw_target_defaults in # $dir_pigweed/pw_vars_default.gni. # # pw_toolchain_SCOPE: A copy of the invoker scope that defines the # toolchain. Used for generating derivative toolchains. # toolchain_args = { pw_toolchain_SCOPE = { } pw_toolchain_SCOPE = { forward_variables_from(invoker, "*") name = target_name } forward_variables_from(invoker_toolchain_args, "*") # Disable compilation testing for static analysis toolchains. pw_compilation_testing_NEGATIVE_COMPILATION_ENABLED = false # Always disable coverage generation since we will not actually run the # instrumented binaries to produce a profraw file. pw_toolchain_COVERAGE_ENABLED = false } } }