• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 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_toolchain/static_analysis_toolchain.gni")
18import("$dir_pw_toolchain/universal_tools.gni")
19
20declare_args() {
21  # Scope defining the current toolchain. Contains all of the arguments required
22  # by the generate_toolchain template. This should NOT be manually modified.
23  pw_toolchain_SCOPE = {
24  }
25
26  # Prefix for compilation commands (e.g. the path to a Goma or CCache compiler
27  # launcher). Example for ccache:
28  #   gn gen out --args='pw_command_launcher="ccache"'
29  pw_command_launcher = ""
30}
31
32# Creates a toolchain target.
33#
34# Args:
35#   ar: (required) String indicating the archive tool to use.
36#   cc: (required) String indicating the C compiler to use.
37#   cxx: (required) String indicating the C++ compiler to use.
38#   ld: (optional) String indicating the linking binary to use.
39#   is_host_toolchain: (optional) Boolean indicating if the outputs are meant
40#     for the $host_os.
41#   final_binary_extension: (optional) The extension to apply to final linked
42#     binaries.
43#   link_whole_archive: (optional) Boolean indicating if the linker should load
44#     all object files when resolving symbols.
45#   link_group: (optional) Boolean indicating if the linker should use
46#     a group to resolve circular dependencies between artifacts.
47#   link_generate_map_file: (optional) Boolean indicating if to add linker
48#     flags to generate a mapfile. Defaults to true.
49#   generate_from: (optional) The full target name of the toolchain that can
50#     trigger this toolchain to be generated. GN only allows one toolchain to
51#     be generated at a given target path, so if multiple toolchains parse the
52#     same generate_toolchain target only one should declare a toolchain. This
53#     is primarily to allow generating sub-toolchains. Defaults to
54#     default_toolchain.
55#   defaults: (required) A scope setting GN build arg values to apply to GN
56#     targets in this toolchain. These take precedence over args.gni settings.
57#   static_analysis: (optional) A scope defining args to apply to the
58#     static_analysis toolchain. If the scope is not defined, static analysis
59#     will be disabled. If provided, static_analysis will be enabled iff
60#     required enabled field in scope is declared true. See
61#     static_analysis_toolchain.gni for more information on scope members.
62#
63# The defaults scope should contain values for builtin GN arguments:
64#   current_cpu: The CPU of the toolchain.
65#     Well known values include "arm", "arm64", "x64", "x86", and "mips".
66#   current_os: The OS of the toolchain. Defaults to "".
67#     Well known values include "win", "mac", "linux", "android", and "ios".
68#
69# TODO: b/234891809 - This should be renamed to pw_generate_toolchain.
70template("generate_toolchain") {
71  assert(defined(invoker.defaults), "toolchain is missing 'defaults'")
72
73  # On the default toolchain invocation, you typically need to generate all
74  # toolchains you encounter. For sub-toolchains, they must be generated from
75  # the context of their parent.
76  if (defined(invoker.generate_from)) {
77    _generate_toolchain =
78        get_label_info(invoker.generate_from, "label_no_toolchain") ==
79        current_toolchain
80  } else {
81    _generate_toolchain = default_toolchain == current_toolchain
82  }
83
84  if (_generate_toolchain) {
85    # TODO(amontanez): This should be renamed to build_args as "defaults" isn't
86    # sufficiently descriptive.
87    invoker_toolchain_args = invoker.defaults
88
89    # These values should always be set as they influence toolchain
90    # behavior, but allow them to be unset as a transitional measure.
91    if (!defined(invoker_toolchain_args.current_cpu)) {
92      invoker_toolchain_args.current_cpu = ""
93    }
94    if (!defined(invoker_toolchain_args.current_os)) {
95      invoker_toolchain_args.current_os = ""
96    }
97
98    # Determine OS of toolchain, which is the builtin argument "current_os".
99    toolchain_os = invoker_toolchain_args.current_os
100
101    toolchain(target_name) {
102      # Uncomment this line to see which toolchains generate other toolchains.
103      # print("Generating toolchain: ${target_name} by ${current_toolchain}")
104
105      assert(defined(invoker.cc), "toolchain is missing 'cc'")
106      tool("asm") {
107        if (pw_command_launcher != "") {
108          command_launcher = pw_command_launcher
109        }
110        depfile = "{{output}}.d"
111        command = string_join(" ",
112                              [
113                                invoker.cc,
114                                "-MMD -MF $depfile",  # Write out dependencies.
115                                "{{asmflags}}",
116                                "{{cflags}}",
117                                "{{defines}}",
118                                "{{include_dirs}}",
119                                "-c {{source}}",
120                                "-o {{output}}",
121                              ])
122        depsformat = "gcc"
123        description = "as {{output}}"
124        outputs = [
125          # Use {{source_file_part}}, which includes the extension, instead of
126          # {{source_name_part}} so that object files created from <file_name>.c
127          # and <file_name>.cc sources are unique.
128          "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
129        ]
130      }
131
132      tool("cc") {
133        if (pw_command_launcher != "") {
134          command_launcher = pw_command_launcher
135        }
136        depfile = "{{output}}.d"
137        command = string_join(" ",
138                              [
139                                invoker.cc,
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                              ])
148        depsformat = "gcc"
149        description = "cc {{output}}"
150        outputs = [
151          "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
152        ]
153      }
154
155      assert(defined(invoker.cxx), "toolchain is missing 'cxx'")
156      tool("cxx") {
157        if (pw_command_launcher != "") {
158          command_launcher = pw_command_launcher
159        }
160        depfile = "{{output}}.d"
161        command = string_join(" ",
162                              [
163                                invoker.cxx,
164                                "-MMD -MF $depfile",  # Write out dependencies.
165                                "{{cflags}}",
166                                "{{cflags_cc}}",  # Must come after {{cflags}}.
167                                "{{defines}}",
168                                "{{include_dirs}}",
169                                "-c {{source}}",
170                                "-o {{output}}",
171                              ])
172        depsformat = "gcc"
173        description = "c++ {{output}}"
174        outputs = [
175          "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
176        ]
177      }
178
179      tool("objc") {
180        if (pw_command_launcher != "") {
181          command_launcher = pw_command_launcher
182        }
183        depfile = "{{output}}.d"
184        command =
185            string_join(" ",
186                        [
187                          invoker.cc,
188                          "-MMD -MF $depfile",  # Write out dependencies.
189                          "{{cflags}}",
190                          "{{cflags_objc}}",  # Must come after {{cflags}}.
191                          "{{defines}}",
192                          "{{include_dirs}}",
193                          "{{framework_dirs}}",
194                          "-c {{source}}",
195                          "-o {{output}}",
196                        ])
197        depsformat = "gcc"
198        description = "objc {{output}}"
199        outputs = [
200          "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
201        ]
202      }
203
204      tool("objcxx") {
205        if (pw_command_launcher != "") {
206          command_launcher = pw_command_launcher
207        }
208        depfile = "{{output}}.d"
209        command =
210            string_join(" ",
211                        [
212                          invoker.cxx,
213                          "-MMD -MF $depfile",  # Write out dependencies.
214                          "{{cflags}}",
215                          "{{cflags_objcc}}",  # Must come after {{cflags}}.
216                          "{{defines}}",
217                          "{{include_dirs}}",
218                          "{{framework_dirs}}",
219                          "-c {{source}}",
220                          "-o {{output}}",
221                        ])
222        depsformat = "gcc"
223        description = "objc++ {{output}}"
224        outputs = [
225          "{{source_out_dir}}/{{target_output_name}}.{{source_file_part}}.o",
226        ]
227      }
228
229      assert(defined(invoker.ar), "toolchain is missing 'ar'")
230      tool("alink") {
231        if (host_os == "win") {
232          rspfile = "{{output}}.rsp"
233          rspfile_content = "{{inputs}}"
234          rm_command = "del /F /Q \"{{output}}\" 2> NUL"
235          command = "cmd /c \"($rm_command) & ${invoker.ar} {{arflags}} rcs {{output}} @$rspfile\""
236        } else {
237          command = "rm -f {{output}} && ${invoker.ar} {{arflags}} rcs {{output}} {{inputs}}"
238        }
239
240        description = "ar {{target_output_name}}{{output_extension}}"
241        outputs =
242            [ "{{output_dir}}/{{target_output_name}}{{output_extension}}" ]
243        default_output_extension = ".a"
244        default_output_dir = "{{target_out_dir}}/lib"
245      }
246
247      lib_switch = "-l"
248      lib_dir_switch = "-L"
249
250      _link_outfile =
251          "{{output_dir}}/{{target_output_name}}{{output_extension}}"
252      if (defined(invoker.ld)) {
253        _link_flags = [
254          invoker.ld,
255          "{{ldflags}}",
256        ]
257      } else {
258        _link_flags = [
259          invoker.cxx,
260          "{{ldflags}}",
261        ]
262      }
263
264      if (defined(invoker.link_generate_map_file)) {
265        _link_generate_map_file = invoker.link_generate_map_file
266      } else {
267        _link_generate_map_file = true
268      }
269
270      _link_outputs = [ _link_outfile ]
271
272      if (_link_generate_map_file) {
273        _link_mapfile = "{{output_dir}}/{{target_output_name}}.map"
274
275        if (toolchain_os == "mac" || toolchain_os == "ios") {
276          _link_flags += [
277            # Output a map file that shows symbols and their location.
278            "-Wl,-map,$_link_mapfile",
279          ]
280        } else {
281          _link_flags += [
282            # Output a map file that shows symbols and their location.
283            "-Wl,-Map,$_link_mapfile",
284            "-Wl,--cref",
285          ]
286        }
287        _link_outputs += [ _link_mapfile ]
288      }
289
290      _rsp_file = "$_link_outfile.rsp"
291      _rsp_contents = []
292
293      _link_group = defined(invoker.link_group) && invoker.link_group
294      if (_link_group) {
295        _rsp_contents += [ "-Wl,--start-group" ]
296      }
297      _rsp_contents += [ "{{inputs}}" ]
298      _rsp_contents += [ "{{frameworks}}" ]
299
300      if (defined(invoker.link_whole_archive) && invoker.link_whole_archive) {
301        # Load all object files from all libraries to resolve symbols.
302        # Short of living in the ideal world where all dependency graphs
303        # among static libs are acyclic and all developers diligently
304        # express such graphs in terms that GN understands, this is the
305        # safest option.
306        # Make sure you use this with --gc-sections, otherwise the
307        # resulting binary will contain every symbol defined in every
308        # input file and every static library. That could be quite a lot.
309        _rsp_contents += [
310          "-Wl,--whole-archive",
311          "{{libs}}",
312          "-Wl,--no-whole-archive",
313        ]
314      } else {
315        _rsp_contents += [ "{{libs}}" ]
316      }
317
318      if (_link_group) {
319        _rsp_contents += [ "-Wl,--end-group" ]
320      }
321      _rsp_command = string_join(" ", _rsp_contents)
322
323      _link_flags += [ "@$_rsp_file" ]
324      _link_flags += [ "-o $_link_outfile" ]
325
326      _link_command = string_join(" ", _link_flags)
327
328      tool("link") {
329        command = _link_command
330        rspfile = _rsp_file
331        rspfile_content = _rsp_command
332        description = "ld $_link_outfile"
333        outputs = _link_outputs
334        default_output_dir = "{{target_out_dir}}/bin"
335
336        if (defined(invoker.final_binary_extension)) {
337          default_output_extension = invoker.final_binary_extension
338        } else if (toolchain_os == "win") {
339          default_output_extension = ".exe"
340        } else {
341          default_output_extension = ""
342        }
343      }
344
345      tool("solink") {
346        command = _link_command + " -shared"
347        rspfile = _rsp_file
348        rspfile_content = _rsp_command
349        description = "ld -shared $_link_outfile"
350        outputs = _link_outputs
351        default_output_dir = "{{target_out_dir}}/lib"
352        default_output_extension = ".so"
353      }
354
355      tool("stamp") {
356        # GN-ism: GN gets mad if you directly forward the contents of
357        # pw_universal_stamp.
358        _stamp = pw_universal_stamp
359        forward_variables_from(_stamp, "*")
360      }
361
362      tool("copy") {
363        # GN-ism: GN gets mad if you directly forward the contents of
364        # pw_universal_copy.
365        _copy = pw_universal_copy
366        forward_variables_from(_copy, "*")
367      }
368
369      # Build arguments to be overridden when compiling cross-toolchain:
370      #
371      #   pw_toolchain_defaults: A scope setting defaults to apply to GN targets
372      #     in this toolchain. It is analogous to $pw_target_defaults in
373      #     $dir_pigweed/pw_vars_default.gni.
374      #
375      #   pw_toolchain_SCOPE: A copy of the invoker scope that defines the
376      #     toolchain. Used for generating derivative toolchains.
377      #
378      toolchain_args = {
379        pw_toolchain_SCOPE = {
380        }
381        pw_toolchain_SCOPE = {
382          forward_variables_from(invoker, "*")
383          name = target_name
384        }
385        forward_variables_from(invoker_toolchain_args, "*")
386      }
387
388      _generate_rust_tools = defined(invoker.rustc)
389      if (_generate_rust_tools) {
390        if (toolchain_os == "mac") {
391          _dylib_extension = ".dylib"
392        } else if (toolchain_os == "win") {
393          _dylib_extension = ".dll"
394        } else {
395          _dylib_extension = ".so"
396        }
397
398        _rustc_command = string_join(
399                " ",
400                [
401                  # TODO: b/234872510 - Ensure this works with Windows.
402                  "RUST_BACKTRACE=1",
403                  "{{rustenv}}",
404                  invoker.rustc,
405                  "{{source}}",
406                  "--crate-name {{crate_name}}",
407                  "--crate-type {{crate_type}}",
408                  "{{externs}}",
409                  "{{rustdeps}}",
410                  "{{rustflags}}",
411                  "-D warnings",
412                  "--color always",
413                  "--emit=dep-info={{output}}.d,link",
414                  "-o {{output_dir}}/{{target_output_name}}{{output_extension}}",
415                ])
416
417        _output = "{{output_dir}}/{{target_output_name}}{{output_extension}}"
418
419        tool("rust_bin") {
420          description = "rustc {{output}}"
421          default_output_dir = "{{target_out_dir}}/bin"
422          depfile = "{{output}}.d"
423          command = _rustc_command
424          outputs = [ _output ]
425        }
426
427        tool("rust_rlib") {
428          description = "rustc {{output}}"
429          default_output_dir = "{{target_out_dir}}/lib"
430          depfile = "{{output}}.d"
431          output_prefix = "lib"
432          default_output_extension = ".rlib"
433          command = _rustc_command
434          outputs = [ _output ]
435        }
436
437        tool("rust_macro") {
438          description = "rustc {{output}}"
439          default_output_dir = "{{target_out_dir}}/lib"
440          depfile = "{{output}}.d"
441          output_prefix = "lib"
442          default_output_extension = _dylib_extension
443          command = _rustc_command
444          outputs = [ _output ]
445        }
446      }
447    }
448
449    _generate_static_analysis_toolchain = false
450    if (defined(invoker.static_analysis)) {
451      _static_analysis_args = invoker.static_analysis
452      assert(defined(_static_analysis_args.enabled),
453             "static_analysis.enabled missing from scope.")
454      _generate_static_analysis_toolchain = _static_analysis_args.enabled
455    }
456    if (_generate_static_analysis_toolchain) {
457      pw_static_analysis_toolchain(target_name + ".static_analysis") {
458        forward_variables_from(invoker, "*")
459      }
460    }
461  } else {
462    not_needed(invoker, "*")
463    group(target_name) {
464    }
465  }
466}
467
468# Creates a series of toolchain targets with common compiler options.
469#
470# Args:
471#   toolchains: List of scopes defining each of the desired toolchains.
472#     The scope must contain a "name" variable; other variables are forwarded to
473#     $generate_toolchain.
474template("generate_toolchains") {
475  not_needed([ "target_name" ])
476  assert(defined(invoker.toolchains),
477         "generate_toolchains must be called with a list of toolchains")
478
479  # Create a target for each of the desired toolchains, appending its own cflags
480  # and ldflags to the common ones.
481  foreach(_toolchain, invoker.toolchains) {
482    generate_toolchain(_toolchain.name) {
483      forward_variables_from(_toolchain, "*", [ "name" ])
484    }
485  }
486}
487