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