• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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