blob: 5aa5abfc6f8a16eb905d86196c4cc21b677f22db [file] [log] [blame]
# 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 <file_name>.c
# and <file_name>.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
}
}
}