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