Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_bloat / py / pw_bloat / bloat.py
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 """
15 bloat is a script which generates a size report card for binary files.
16 """
17
18 import argparse
19 import logging
20 import os
21 import subprocess
22 import sys
23 from typing import List, Iterable, Optional
24
25 import pw_cli.log
26
27 from pw_bloat.binary_diff import BinaryDiff
28 from pw_bloat import bloat_output
29
30 _LOG = logging.getLogger(__name__)
31
32
33 def parse_args() -> argparse.Namespace:
34     """Parses the script's arguments."""
35     def delimited_list(delimiter: str, items: Optional[int] = None):
36         def _parser(arg: str):
37             args = arg.split(delimiter)
38
39             if items and len(args) != items:
40                 raise argparse.ArgumentTypeError(
41                     'Argument must be a '
42                     f'{delimiter}-delimited list with {items} items: "{arg}"')
43
44             return args
45
46         return _parser
47
48     parser = argparse.ArgumentParser(
49         'Generate a size report card for binaries')
50     parser.add_argument('--bloaty-config',
51                         type=delimited_list(';'),
52                         required=True,
53                         help='Data source configuration for Bloaty')
54     parser.add_argument('--full',
55                         action='store_true',
56                         help='Display full bloat breakdown by symbol')
57     parser.add_argument('--labels',
58                         type=delimited_list(';'),
59                         default='',
60                         help='Labels for output binaries')
61     parser.add_argument('--out-dir',
62                         type=str,
63                         required=True,
64                         help='Directory in which to write output files')
65     parser.add_argument('--target',
66                         type=str,
67                         required=True,
68                         help='Build target name')
69     parser.add_argument('--title',
70                         type=str,
71                         default='pw_bloat',
72                         help='Report title')
73     parser.add_argument('--source-filter',
74                         type=str,
75                         help='Bloaty data source filter')
76     parser.add_argument('diff_targets',
77                         type=delimited_list(';', 2),
78                         nargs='+',
79                         metavar='DIFF_TARGET',
80                         help='Binary;base pairs to process')
81
82     return parser.parse_args()
83
84
85 def run_bloaty(
86     filename: str,
87     config: str,
88     base_file: Optional[str] = None,
89     data_sources: Iterable[str] = (),
90     extra_args: Iterable[str] = ()
91 ) -> bytes:
92     """Executes a Bloaty size report on some binary file(s).
93
94     Args:
95         filename: Path to the binary.
96         config: Path to Bloaty config file.
97         base_file: Path to a base binary. If provided, a size diff is performed.
98         data_sources: List of Bloaty data sources for the report.
99         extra_args: Additional command-line arguments to pass to Bloaty.
100
101     Returns:
102         Binary output of the Bloaty invocation.
103
104     Raises:
105         subprocess.CalledProcessError: The Bloaty invocation failed.
106     """
107
108     # TODO(frolv): Point the default bloaty path to a prebuilt in Pigweed.
109     default_bloaty = 'bloaty'
110     bloaty_path = os.getenv('BLOATY_PATH', default_bloaty)
111
112     # yapf: disable
113     cmd = [
114         bloaty_path,
115         '-c', config,
116         '-d', ','.join(data_sources),
117         '--domain', 'vm',
118         filename,
119         *extra_args
120     ]
121     # yapf: enable
122
123     if base_file is not None:
124         cmd.extend(['--', base_file])
125
126     return subprocess.check_output(cmd)
127
128
129 def main() -> int:
130     """Program entry point."""
131
132     args = parse_args()
133
134     base_binaries: List[str] = []
135     diff_binaries: List[str] = []
136
137     try:
138         for binary, base in args.diff_targets:
139             diff_binaries.append(binary)
140             base_binaries.append(base)
141     except RuntimeError as err:
142         _LOG.error('%s: %s', sys.argv[0], err)
143         return 1
144
145     data_sources = ['segment_names']
146     if args.full:
147         data_sources.append('fullsymbols')
148
149     # TODO(frolv): CSV output is disabled for full reports as the default Bloaty
150     # breakdown is printed. This script should be modified to print a custom
151     # symbol breakdown in full reports.
152     extra_args = [] if args.full else ['--csv']
153     if args.source_filter:
154         extra_args.extend(['--source-filter', args.source_filter])
155
156     diffs: List[BinaryDiff] = []
157     report = []
158
159     for i, binary in enumerate(diff_binaries):
160         binary_name = (args.labels[i]
161                        if i < len(args.labels) else os.path.basename(binary))
162         try:
163             output = run_bloaty(binary, args.bloaty_config[i],
164                                 base_binaries[i], data_sources, extra_args)
165             if not output:
166                 continue
167
168             # TODO(frolv): Remove when custom output for full mode is added.
169             if args.full:
170                 report.append(binary_name)
171                 report.append('-' * len(binary_name))
172                 report.append(output.decode())
173                 continue
174
175             # Ignore the first row as it displays column names.
176             bloaty_csv = output.decode().splitlines()[1:]
177             diffs.append(BinaryDiff.from_csv(binary_name, bloaty_csv))
178         except subprocess.CalledProcessError:
179             _LOG.error('%s: failed to run diff on %s', sys.argv[0], binary)
180             return 1
181
182     def write_file(filename: str, contents: str) -> None:
183         path = os.path.join(args.out_dir, filename)
184         with open(path, 'w') as output_file:
185             output_file.write(contents)
186         _LOG.debug('Output written to %s', path)
187
188     # TODO(frolv): Remove when custom output for full mode is added.
189     if not args.full:
190         out = bloat_output.TableOutput(args.title,
191                                        diffs,
192                                        charset=bloat_output.LineCharset)
193         report.append(out.diff())
194
195         rst = bloat_output.RstOutput(diffs)
196         write_file(f'{args.target}', rst.diff())
197
198     complete_output = '\n'.join(report) + '\n'
199     write_file(f'{args.target}.txt', complete_output)
200     print(complete_output)
201
202     return 0
203
204
205 if __name__ == '__main__':
206     pw_cli.log.install()
207     sys.exit(main())