• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2023 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_build/evaluate_path_expressions.gni")
18import("$dir_pw_build/python_action.gni")
19
20declare_args() {
21  # Path to the Bloaty configuration file that defines the memory layout and
22  # capacities for the target binaries.
23  pw_bloat_BLOATY_CONFIG = ""
24
25  # List of toolchains to use in pw_toolchain_size_diff templates.
26  #
27  # Each entry is a scope containing the following variables:
28  #
29  #   name: Human-readable toolchain name.
30  #   target: GN target that defines the toolchain.
31  #   linker_script: Optional path to a linker script file to build for the
32  #     toolchain's target.
33  #   bloaty_config: Optional Bloaty confirugation file defining the memory
34  #     layout of the binaries as specified in the linker script.
35  #
36  # If this list is empty, pw_toolchain_size_diff targets become no-ops.
37  pw_bloat_TOOLCHAINS = []
38
39  # Controls whether to display size reports in the build output.
40  pw_bloat_SHOW_SIZE_REPORTS = false
41}
42
43# Creates a size report for a single binary.
44#
45# Args:
46#   target: Build target for executable. Required.
47#   data_sources: List of datasources from bloaty config file
48#     or built-in datasources. Order of sources determines hierarchical
49#     output. Optional.
50#     github.com/google/bloaty/blob/a1bbc93f5f6f969242046dffd9deb379f6735020/doc/using.md
51#   source_filter: Regex to filter data source names in Bloaty. Optional.
52#
53# Example:
54#   pw_size_report("foo_bloat") {
55#      target = ":foo_static"
56#      datasources = "symbols,segment_names"
57#      source_filter = "foo"
58#   }
59#
60template("pw_size_report") {
61  if (pw_bloat_BLOATY_CONFIG != "") {
62    assert(defined(invoker.target),
63           "Size report must defined a 'target' variable")
64    _all_target_dependencies = [ invoker.target ]
65    _binary_args = []
66
67    if (defined(invoker.source_filter)) {
68      curr_source_filter = invoker.source_filter
69    } else {
70      curr_source_filter = ""
71    }
72
73    if (defined(invoker.data_sources)) {
74      curr_data_sources = string_split(invoker.data_sources, ",")
75    } else {
76      curr_data_sources = ""
77    }
78    _binary_args = [
79      {
80        bloaty_config = rebase_path(pw_bloat_BLOATY_CONFIG, root_build_dir)
81        out_dir = rebase_path(target_gen_dir, root_build_dir)
82        target = "<TARGET_FILE(${invoker.target})>"
83        source_filter = curr_source_filter
84        data_sources = curr_data_sources
85      },
86    ]
87
88    _file_name = "${target_name}_single_binary.json"
89
90    _args_src = "$target_gen_dir/${_file_name}.in"
91    _args_path = "$target_gen_dir/${_file_name}"
92
93    write_file(_args_src,
94               {
95                 binaries = _binary_args
96                 target_name = target_name
97                 out_dir = rebase_path(target_gen_dir, root_build_dir)
98                 root = rebase_path("//", root_build_dir)
99                 toolchain = current_toolchain
100                 default_toolchain = default_toolchain
101                 cwd = rebase_path(".", root_build_dir)
102               },
103               "json")
104
105    pw_evaluate_path_expressions("${target_name}.evaluate") {
106      files = [
107        {
108          source = _args_src
109          dest = _args_path
110        },
111      ]
112    }
113
114    _bloat_script_args = [
115      "--gn-arg-path",
116      rebase_path(_args_path, root_build_dir),
117      "--single-report",
118    ]
119
120    _doc_rst_output = "$target_gen_dir/${target_name}"
121    _binary_sizes_output = "$target_gen_dir/${target_name}.binary_sizes.json"
122
123    if (host_os == "win") {
124      # Bloaty is not yet packaged for Windows systems; display a message
125      # indicating this.
126      not_needed("*")
127      not_needed(invoker, "*")
128
129      pw_python_action(target_name) {
130        metadata = {
131          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
132        }
133        script = "$dir_pw_bloat/py/pw_bloat/no_bloaty.py"
134        python_deps = [ "$dir_pw_bloat/py" ]
135        args = [ rebase_path(_doc_rst_output, root_build_dir) ]
136        outputs = [ _doc_rst_output ]
137      }
138
139      group(target_name + "_UNUSED_DEPS") {
140        deps = _all_target_dependencies
141      }
142    } else {
143      # Create an action which runs the size report script on the provided
144      # targets.
145      pw_python_action(target_name) {
146        metadata = {
147          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
148        }
149        script = "$dir_pw_bloat/py/pw_bloat/bloat.py"
150        python_deps = [ "$dir_pw_bloat/py" ]
151        inputs = [
152          pw_bloat_BLOATY_CONFIG,
153          _args_path,
154        ]
155        outputs = [
156          "${_doc_rst_output}.txt",
157          _binary_sizes_output,
158          _doc_rst_output,
159        ]
160        deps = _all_target_dependencies + [ ":${target_name}.evaluate" ]
161        args = _bloat_script_args
162
163        # Print size reports to stdout when they are generated, if requested.
164        capture_output = !pw_bloat_SHOW_SIZE_REPORTS
165      }
166    }
167  } else {
168    not_needed(invoker, "*")
169    group(target_name) {
170    }
171  }
172}
173
174# Aggregates JSON size report data from several pw_size_report targets into a
175# single output file.
176#
177# Args:
178#   deps: List of pw_size_report targets whose data to collect.
179#   output: Path to the output JSON file.
180#
181# Example:
182#   pw_size_report_aggregation("image_sizes") {
183#      deps = [
184#        ":app_image_size_report",
185#        ":bootloader_image_size_report",
186#      ]
187#      output = "$root_gen_dir/artifacts/image_sizes.json"
188#   }
189#
190template("pw_size_report_aggregation") {
191  assert(defined(invoker.deps) && invoker.deps != [],
192         "pw_size_report_aggregation requires size report dependencies")
193  assert(defined(invoker.output),
194         "pw_size_report_aggregation requires an output file path")
195
196  _input_json_files = []
197
198  foreach(_dep, invoker.deps) {
199    _gen_dir = get_label_info(_dep, "target_gen_dir")
200    _dep_name = get_label_info(_dep, "name")
201    _input_json_files +=
202        [ rebase_path("$_gen_dir/${_dep_name}.binary_sizes.json",
203                      root_build_dir) ]
204  }
205
206  pw_python_action(target_name) {
207    script = "$dir_pw_bloat/py/pw_bloat/binary_size_aggregator.py"
208    python_deps = [ "$dir_pw_bloat/py" ]
209    args = [
210             "--output",
211             rebase_path(invoker.output, root_build_dir),
212           ] + _input_json_files
213    outputs = [ invoker.output ]
214    deps = invoker.deps
215    forward_variables_from(invoker, [ "visibility" ])
216  }
217}
218
219# Creates a target which runs a size report diff on a set of executables.
220#
221# Args:
222#   base: The default base executable target to run the diff against. May be
223#     omitted if all binaries provide their own base.
224#   source_filter: Optional global regex to filter data source names in Bloaty.
225#   data_sources: List of datasources from bloaty config file
226#     or built-in datasources. Order of sources determines hierarchical
227#     output. Optional.
228#     github.com/google/bloaty/blob/a1bbc93f5f6f969242046dffd9deb379f6735020/doc/using.md
229#   binaries: List of executables to compare in the diff.
230#     Each binary in the list is a scope containing up to three variables:
231#       label: Descriptive name for the executable. Required.
232#       target: Build target for the executable. Required.
233#       base: Optional base diff target. Overrides global base argument.
234#       source_filter: Optional regex to filter data source names.
235#         Overrides global source_filter argument.
236#       data_sources: Optional List of datasources from bloaty config file
237#         Overrides global data_sources argument.
238#
239#
240# Example:
241#   pw_size_diff("foo_bloat") {
242#     base = ":foo_base"
243#     data_sources = "segment,symbols"
244#     binaries = [
245#       {
246#         target = ":foo_static"
247#         label = "Static"
248#       },
249#       {
250#         target = ":foo_dynamic"
251#         label = "Dynamic"
252#         data_sources = "segment_names"
253#       },
254#     ]
255#   }
256#
257template("pw_size_diff") {
258  if (pw_bloat_BLOATY_CONFIG != "") {
259    if (defined(invoker.base)) {
260      _global_base = invoker.base
261      _all_target_dependencies = [ _global_base ]
262    } else {
263      _all_target_dependencies = []
264    }
265
266    if (defined(invoker.source_filter)) {
267      _global_source_filter = invoker.source_filter
268    }
269
270    if (defined(invoker.data_sources)) {
271      _global_data_sources = string_split(invoker.data_sources, ",")
272    }
273
274    # TODO(brandonvu): Remove once all downstream projects are updated
275    if (defined(invoker.title)) {
276      not_needed(invoker, [ "title" ])
277    }
278
279    # This template creates an action which invokes a Python script to run a
280    # size report on each of the provided targets. Each of the targets is listed
281    # as a dependency of the action so that the report gets updated when
282    # anything is changed. Most of the code below builds the command-line
283    # arguments to pass each of the targets into the script.
284
285    # Process each of the binaries, creating an object and storing all the
286    # needed variables into a json. Json is parsed in bloat.py
287    _binaries_args = []
288    _bloaty_configs = []
289
290    foreach(binary, invoker.binaries) {
291      assert(defined(binary.label) && defined(binary.target),
292             "Size report binaries must define 'label' and 'target' variables")
293      _all_target_dependencies += [ binary.target ]
294
295      # If the binary defines its own base, use that instead of the global base.
296      if (defined(binary.base)) {
297        _binary_base = binary.base
298        _all_target_dependencies += [ _binary_base ]
299      } else if (defined(_global_base)) {
300        _binary_base = _global_base
301      } else {
302        assert(false, "pw_size_diff requires a 'base' file")
303      }
304
305      if (defined(binary.source_filter)) {
306        _binary_source_filter = binary.source_filter
307      } else if (defined(_global_source_filter)) {
308        _binary_source_filter = _global_source_filter
309      } else {
310        _binary_source_filter = ""
311      }
312
313      _binary_data_sources = []
314      if (defined(binary.data_sources)) {
315        _binary_data_sources = string_split(binary.data_sources, ",")
316      } else if (defined(_global_data_sources)) {
317        _binary_data_sources = _global_data_sources
318      } else {
319        _binary_data_sources = ""
320      }
321
322      # Allow each binary to override the global bloaty config.
323      if (defined(binary.bloaty_config)) {
324        _binary_bloaty_config = binary.bloaty_config
325        _bloaty_configs += [ binary.bloaty_config ]
326      } else {
327        _binary_bloaty_config = pw_bloat_BLOATY_CONFIG
328        _bloaty_configs += [ pw_bloat_BLOATY_CONFIG ]
329      }
330
331      _binaries_args += [
332        {
333          bloaty_config = rebase_path(_binary_bloaty_config, root_build_dir)
334          target = "<TARGET_FILE(${binary.target})>"
335          base = "<TARGET_FILE($_binary_base)>"
336          source_filter = _binary_source_filter
337          label = binary.label
338          data_sources = _binary_data_sources
339        },
340      ]
341    }
342
343    _file_name = "${target_name}_binaries.json"
344    _diff_source = "$target_gen_dir/${_file_name}.in"
345    _diff_path = "$target_gen_dir/${_file_name}"
346    write_file(_diff_source,
347               {
348                 binaries = _binaries_args
349                 target_name = target_name
350                 out_dir = rebase_path(target_gen_dir, root_build_dir)
351                 root = rebase_path("//", root_build_dir)
352                 toolchain = current_toolchain
353                 default_toolchain = default_toolchain
354                 cwd = rebase_path(".", root_build_dir)
355               },
356               "json")
357
358    pw_evaluate_path_expressions("${target_name}.evaluate") {
359      files = [
360        {
361          source = _diff_source
362          dest = _diff_path
363        },
364      ]
365    }
366
367    _bloat_script_args = [
368      "--gn-arg-path",
369      rebase_path(_diff_path, root_build_dir),
370    ]
371
372    # TODO(brandonvu): Remove once all downstream projects are updated
373    if (defined(invoker.full_report)) {
374      not_needed(invoker, [ "full_report" ])
375    }
376
377    _doc_rst_output = "$target_gen_dir/${target_name}"
378
379    if (host_os == "win") {
380      # Bloaty is not yet packaged for Windows systems; display a message
381      # indicating this.
382      not_needed("*")
383      not_needed(invoker, "*")
384
385      pw_python_action(target_name) {
386        metadata = {
387          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
388        }
389        script = "$dir_pw_bloat/py/pw_bloat/no_bloaty.py"
390        python_deps = [ "$dir_pw_bloat/py" ]
391        args = [ rebase_path(_doc_rst_output, root_build_dir) ]
392        outputs = [ _doc_rst_output ]
393      }
394
395      group(target_name + "_UNUSED_DEPS") {
396        deps = _all_target_dependencies
397      }
398    } else {
399      # Create an action which runs the size report script on the provided
400      # targets.
401      pw_python_action(target_name) {
402        metadata = {
403          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
404        }
405        script = "$dir_pw_bloat/py/pw_bloat/bloat.py"
406        python_deps = [ "$dir_pw_bloat/py" ]
407        inputs = _bloaty_configs + [ _diff_path ]
408        outputs = [
409          "${_doc_rst_output}.txt",
410          _doc_rst_output,
411        ]
412        deps = _all_target_dependencies + [ ":${target_name}.evaluate" ]
413        args = _bloat_script_args
414
415        # Print size reports to stdout when they are generated, if requested.
416        capture_output = !pw_bloat_SHOW_SIZE_REPORTS
417      }
418    }
419  } else {
420    not_needed(invoker, "*")
421    group(target_name) {
422    }
423  }
424}
425
426# Creates a report card comparing the sizes of the same binary compiled with
427# different toolchains. The toolchains to use are listed in the build variable
428# pw_bloat_TOOLCHAINS.
429#
430# Args:
431#   base_executable: Scope containing a list of variables defining an executable
432#     target for the size report base.
433#   diff_executable: Scope containing a list of variables defining an executable
434#     target for the size report comparison.
435#
436# Outputs:
437#   $target_gen_dir/$target_name.txt
438#   $target_gen_dir/$target_name.rst
439#
440# Example:
441#
442#   pw_toolchain_size_diff("my_size_report") {
443#     base_executable = {
444#       sources = [ "base.cc" ]
445#     }
446#
447#     diff_executable = {
448#       sources = [ "base_with_libfoo.cc" ]
449#       deps = [ ":libfoo" ]
450#     }
451#   }
452#
453template("pw_toolchain_size_diff") {
454  assert(defined(invoker.base_executable),
455         "pw_toolchain_size_diff requires a base_executable")
456  assert(defined(invoker.diff_executable),
457         "pw_toolchain_size_diff requires a diff_executable")
458
459  _size_report_binaries = []
460
461  # Multiple build targets are created for each toolchain, which all need unique
462  # target names, so throw a counter in there.
463  i = 0
464
465  # Create a base and diff executable for each toolchain, adding the toolchain's
466  # linker script to the link flags for the executable, and add them all to a
467  # list of binaries for the pw_size_diff template.
468  foreach(_toolchain, pw_bloat_TOOLCHAINS) {
469    _prefix = "_${target_name}_${i}_pw_size"
470
471    # Create a config which adds the toolchain's linker script as a linker flag
472    # if the toolchain provides one.
473    _linker_script_target_name = "${_prefix}_linker_script"
474    config(_linker_script_target_name) {
475      if (defined(_toolchain.linker_script)) {
476        ldflags =
477            [ "-T" + rebase_path(_toolchain.linker_script, root_build_dir) ]
478        inputs = [ _toolchain.linker_script ]
479      } else {
480        ldflags = []
481      }
482    }
483
484    # Create a group which forces the linker script config its dependents.
485    _linker_group_target_name = "${_prefix}_linker_group"
486    group(_linker_group_target_name) {
487      public_configs = [ ":$_linker_script_target_name" ]
488    }
489
490    # Define the size report base executable with the toolchain's linker script.
491    _base_target_name = "${_prefix}_base"
492    executable(_base_target_name) {
493      forward_variables_from(invoker.base_executable, "*")
494      if (!defined(deps)) {
495        deps = []
496      }
497      deps += [ ":$_linker_group_target_name" ]
498    }
499
500    # Define the size report diff executable with the toolchain's linker script.
501    _diff_target_name = "${_prefix}_diff"
502    executable(_diff_target_name) {
503      forward_variables_from(invoker.diff_executable, "*")
504      if (!defined(deps)) {
505        deps = []
506      }
507      deps += [ ":$_linker_group_target_name" ]
508    }
509
510    # Force compilation with the toolchain.
511    _base_label = get_label_info(":$_base_target_name", "label_no_toolchain")
512    _base_with_toolchain = "$_base_label(${_toolchain.target})"
513    _diff_label = get_label_info(":$_diff_target_name", "label_no_toolchain")
514    _diff_with_toolchain = "$_diff_label(${_toolchain.target})"
515
516    # Append a pw_size_diff binary scope to the list comparing the toolchain's
517    # diff and base executables.
518    _size_report_binaries += [
519      {
520        base = _base_with_toolchain
521        target = _diff_with_toolchain
522        label = _toolchain.name
523
524        if (defined(_toolchain.bloaty_config)) {
525          bloaty_config = _toolchain.bloaty_config
526        }
527      },
528    ]
529
530    i += 1
531  }
532
533  # TODO(frolv): Have a way of indicating that a toolchain should build docs.
534  if (current_toolchain == default_toolchain && _size_report_binaries != []) {
535    # Create the size report which runs on the binaries.
536    pw_size_diff(target_name) {
537      forward_variables_from(invoker, [ "title" ])
538      binaries = _size_report_binaries
539    }
540  } else {
541    # If no toolchains are listed in pw_bloat_TOOLCHAINS, prevent GN from
542    # complaining about unused variables and run a script that outputs a ReST
543    # warning to the size report file.
544    not_needed("*")
545    not_needed(invoker, "*")
546
547    _doc_rst_output = "$target_gen_dir/$target_name"
548    pw_python_action(target_name) {
549      metadata = {
550        pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
551      }
552      script = "$dir_pw_bloat/py/pw_bloat/no_toolchains.py"
553      python_deps = [ "$dir_pw_bloat/py" ]
554      args = [ rebase_path(_doc_rst_output, root_build_dir) ]
555      outputs = [ _doc_rst_output ]
556    }
557  }
558}
559
560# A base_executable for the pw_toolchain_size_diff template which contains a
561# main() function that loads the bloat_this_binary library and does nothing
562# else.
563pw_bloat_empty_base = {
564  deps = [
565    "$dir_pw_bloat:base_main",
566    "$dir_pw_bloat:bloat_this_binary",
567  ]
568}
569