1# Copyright 2019 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/python_action.gni")
18
19declare_args() {
20  # Path to the Bloaty configuration file that defines the memory layout and
21  # capacities for the target binaries.
22  pw_bloat_BLOATY_CONFIG = ""
23
24  # List of toolchains to use in pw_toolchain_size_report templates.
25  #
26  # Each entry is a scope containing the following variables:
27  #
28  #   name: Human-readable toolchain name.
29  #   target: GN target that defines the toolchain.
30  #   linker_script: Optional path to a linker script file to build for the
31  #     toolchain's target.
32  #   bloaty_config: Optional Bloaty confirugation file defining the memory
33  #     layout of the binaries as specified in the linker script.
34  #
35  # If this list is empty, pw_toolchain_size_report targets become no-ops.
36  pw_bloat_TOOLCHAINS = []
37}
38
39# Creates a target which runs a size report diff on a set of executables.
40#
41# Args:
42#   base: The default base executable target to run the diff against. May be
43#     omitted if all binaries provide their own base.
44#   binaries: List of executables to compare in the diff.
45#     Each binary in the list is a scope containing up to three variables:
46#       label: Descriptive name for the executable. Required.
47#       target: Build target for the executable. Required.
48#       base: Optional base diff target. Overrides global base argument.
49#   source_filter: Optional regex to filter data source names in Bloaty.
50#   title: Optional title string to display with the size report.
51#   full_report: Optional boolean flag indicating whether to produce a full
52#     symbol size breakdown or a summary.
53#
54# Example:
55#   pw_size_report("foo_bloat") {
56#     base = ":foo_base"
57#     binaries = [
58#       {
59#         target = ":foo_static"
60#         label = "Static"
61#       },
62#       {
63#         target = ":foo_dynamic"
64#         label = "Dynamic"
65#       },
66#     ]
67#     title = "static vs. dynamic foo"
68#   }
69#
70template("pw_size_report") {
71  if (pw_bloat_BLOATY_CONFIG != "") {
72    if (defined(invoker.base)) {
73      _global_base = invoker.base
74      _all_target_dependencies = [ _global_base ]
75    } else {
76      _all_target_dependencies = []
77    }
78
79    if (defined(invoker.title)) {
80      _title = invoker.title
81    } else {
82      _title = target_name
83    }
84
85    # This template creates an action which invokes a Python script to run a
86    # size report on each of the provided targets. Each of the targets is listed
87    # as a dependency of the action so that the report gets updated when
88    # anything is changed. Most of the code below builds the command-line
89    # arguments to pass each of the targets into the script.
90
91    _binary_paths = []
92    _binary_labels = []
93    _bloaty_configs = []
94
95    # Process each of the binaries, resolving their full output paths and
96    # building them into a list of command-line arguments to the bloat script.
97    foreach(binary, invoker.binaries) {
98      assert(defined(binary.label) && defined(binary.target),
99             "Size report binaries must define 'label' and 'target' variables")
100      _all_target_dependencies += [ binary.target ]
101
102      _binary_path = "<TARGET_FILE(${binary.target})>"
103
104      # If the binary defines its own base, use that instead of the global base.
105      if (defined(binary.base)) {
106        _binary_base = binary.base
107        _all_target_dependencies += [ _binary_base ]
108      } else if (defined(_global_base)) {
109        _binary_base = _global_base
110      } else {
111        assert(false, "pw_size_report requires a 'base' file")
112      }
113
114      # Allow each binary to override the global bloaty config.
115      if (defined(binary.bloaty_config)) {
116        _bloaty_configs += [ rebase_path(binary.bloaty_config) ]
117      } else {
118        _bloaty_configs += [ rebase_path(pw_bloat_BLOATY_CONFIG) ]
119      }
120
121      _binary_path += ";" + "<TARGET_FILE($_binary_base)>"
122
123      _binary_paths += [ _binary_path ]
124      _binary_labels += [ binary.label ]
125    }
126
127    _bloat_script_args = [
128      "--bloaty-config",
129      string_join(";", _bloaty_configs),
130      "--out-dir",
131      rebase_path(target_gen_dir),
132      "--target",
133      target_name,
134      "--title",
135      _title,
136      "--labels",
137      string_join(";", _binary_labels),
138    ]
139
140    if (defined(invoker.full_report) && invoker.full_report) {
141      _bloat_script_args += [ "--full" ]
142    }
143
144    if (defined(invoker.source_filter)) {
145      _bloat_script_args += [
146        "--source-filter",
147        invoker.source_filter,
148      ]
149    }
150
151    _doc_rst_output = "$target_gen_dir/${target_name}"
152
153    if (host_os == "win") {
154      # Bloaty is not yet packaged for Windows systems; display a message
155      # indicating this.
156      not_needed("*")
157      not_needed(invoker, "*")
158
159      pw_python_action(target_name) {
160        metadata = {
161          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
162        }
163        script = "$dir_pw_bloat/py/pw_bloat/no_bloaty.py"
164        python_deps = [ "$dir_pw_bloat/py" ]
165        args = [ rebase_path(_doc_rst_output) ]
166        outputs = [ _doc_rst_output ]
167      }
168
169      group(target_name + "_UNUSED_DEPS") {
170        deps = _all_target_dependencies
171      }
172    } else {
173      # Create an action which runs the size report script on the provided
174      # targets.
175      pw_python_action(target_name) {
176        metadata = {
177          pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
178        }
179        script = "$dir_pw_bloat/py/pw_bloat/bloat.py"
180        python_deps = [ "$dir_pw_bloat/py" ]
181        inputs = _bloaty_configs
182        outputs = [
183          "$target_gen_dir/${target_name}.txt",
184          _doc_rst_output,
185        ]
186        deps = _all_target_dependencies
187        args = _bloat_script_args + _binary_paths
188
189        # Print size reports to stdout when they are generated.
190        capture_output = false
191      }
192    }
193  } else {
194    not_needed(invoker, "*")
195    group(target_name) {
196    }
197  }
198}
199
200# Creates a report card comparing the sizes of the same binary compiled with
201# different toolchains. The toolchains to use are listed in the build variable
202# pw_bloat_TOOLCHAINS.
203#
204# Args:
205#   base_executable: Scope containing a list of variables defining an executable
206#     target for the size report base.
207#   diff_executable: Scope containing a list of variables defining an executable
208#     target for the size report comparison.
209#
210# Outputs:
211#   $target_gen_dir/$target_name.txt
212#   $target_gen_dir/$target_name.rst
213#
214# Example:
215#
216#   pw_toolchain_size_report("my_size_report") {
217#     base_executable = {
218#       sources = [ "base.cc" ]
219#     }
220#
221#     diff_executable = {
222#       sources = [ "base_with_libfoo.cc" ]
223#       deps = [ ":libfoo" ]
224#     }
225#   }
226#
227template("pw_toolchain_size_report") {
228  assert(defined(invoker.base_executable),
229         "pw_toolchain_size_report requires a base_executable")
230  assert(defined(invoker.diff_executable),
231         "pw_toolchain_size_report requires a diff_executable")
232
233  _size_report_binaries = []
234
235  # Multiple build targets are created for each toolchain, which all need unique
236  # target names, so throw a counter in there.
237  i = 0
238
239  # Create a base and diff executable for each toolchain, adding the toolchain's
240  # linker script to the link flags for the executable, and add them all to a
241  # list of binaries for the pw_size_report template.
242  foreach(_toolchain, pw_bloat_TOOLCHAINS) {
243    _prefix = "_${target_name}_${i}_pw_size"
244
245    # Create a config which adds the toolchain's linker script as a linker flag
246    # if the toolchain provides one.
247    _linker_script_target_name = "${_prefix}_linker_script"
248    config(_linker_script_target_name) {
249      if (defined(_toolchain.linker_script)) {
250        ldflags = [ "-T" + rebase_path(_toolchain.linker_script) ]
251        inputs = [ _toolchain.linker_script ]
252      } else {
253        ldflags = []
254      }
255    }
256
257    # Create a group which forces the linker script config its dependents.
258    _linker_group_target_name = "${_prefix}_linker_group"
259    group(_linker_group_target_name) {
260      public_configs = [ ":$_linker_script_target_name" ]
261    }
262
263    # Define the size report base executable with the toolchain's linker script.
264    _base_target_name = "${_prefix}_base"
265    executable(_base_target_name) {
266      forward_variables_from(invoker.base_executable, "*")
267      if (!defined(deps)) {
268        deps = []
269      }
270      deps += [ ":$_linker_group_target_name" ]
271    }
272
273    # Define the size report diff executable with the toolchain's linker script.
274    _diff_target_name = "${_prefix}_diff"
275    executable(_diff_target_name) {
276      forward_variables_from(invoker.diff_executable, "*")
277      if (!defined(deps)) {
278        deps = []
279      }
280      deps += [ ":$_linker_group_target_name" ]
281    }
282
283    # Force compilation with the toolchain.
284    _base_label = get_label_info(":$_base_target_name", "label_no_toolchain")
285    _base_with_toolchain = "$_base_label(${_toolchain.target})"
286    _diff_label = get_label_info(":$_diff_target_name", "label_no_toolchain")
287    _diff_with_toolchain = "$_diff_label(${_toolchain.target})"
288
289    # Append a pw_size_report binary scope to the list comparing the toolchain's
290    # diff and base executables.
291    _size_report_binaries += [
292      {
293        base = _base_with_toolchain
294        target = _diff_with_toolchain
295        label = _toolchain.name
296
297        if (defined(_toolchain.bloaty_config)) {
298          bloaty_config = _toolchain.bloaty_config
299        }
300      },
301    ]
302
303    i += 1
304  }
305
306  # TODO(frolv): Have a way of indicating that a toolchain should build docs.
307  if (current_toolchain == default_toolchain && _size_report_binaries != []) {
308    # Create the size report which runs on the binaries.
309    pw_size_report(target_name) {
310      forward_variables_from(invoker, [ "title" ])
311      binaries = _size_report_binaries
312    }
313  } else {
314    # If no toolchains are listed in pw_bloat_TOOLCHAINS, prevent GN from
315    # complaining about unused variables and run a script that outputs a ReST
316    # warning to the size report file.
317    not_needed("*")
318    not_needed(invoker, "*")
319
320    _doc_rst_output = "$target_gen_dir/$target_name"
321    pw_python_action(target_name) {
322      metadata = {
323        pw_doc_sources = rebase_path([ _doc_rst_output ], root_build_dir)
324      }
325      script = "$dir_pw_bloat/py/pw_bloat/no_toolchains.py"
326      python_deps = [ "$dir_pw_bloat/py" ]
327      args = [ rebase_path(_doc_rst_output) ]
328      outputs = [ _doc_rst_output ]
329    }
330  }
331}
332
333# A base_executable for the pw_toolchain_size_report template which contains a
334# main() function that loads the bloat_this_binary library and does nothing
335# else.
336pw_bloat_empty_base = {
337  deps = [
338    "$dir_pw_bloat:base_main",
339    "$dir_pw_bloat:bloat_this_binary",
340  ]
341}
342