1 # Copyright 2019 The Pigweed Authors
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
7 # https://www.apache.org/licenses/LICENSE-2.0
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
15 bloat is a script which generates a size report card for binary files.
24 from typing import List, Iterable, Optional
26 from binary_diff import BinaryDiff
31 _LOG = logging.getLogger(__name__)
34 def parse_args() -> argparse.Namespace:
35 """Parses the script's arguments."""
36 def delimited_list(delimiter: str, items: Optional[int] = None):
37 def _parser(arg: str):
38 args = arg.split(delimiter)
40 if items and len(args) != items:
41 raise argparse.ArgumentTypeError(
43 f'{delimiter}-delimited list with {items} items: "{arg}"')
49 parser = argparse.ArgumentParser(
50 'Generate a size report card for binaries')
51 parser.add_argument('--bloaty-config',
52 type=delimited_list(';'),
54 help='Data source configuration for Bloaty')
55 parser.add_argument('--full',
57 help='Display full bloat breakdown by symbol')
58 parser.add_argument('--labels',
59 type=delimited_list(';'),
61 help='Labels for output binaries')
62 parser.add_argument('--out-dir',
65 help='Directory in which to write output files')
66 parser.add_argument('--target',
69 help='Build target name')
70 parser.add_argument('--title',
74 parser.add_argument('--source-filter',
76 help='Bloaty data source filter')
77 parser.add_argument('diff_targets',
78 type=delimited_list(';', 2),
80 metavar='DIFF_TARGET',
81 help='Binary;base pairs to process')
83 return parser.parse_args()
89 base_file: Optional[str] = None,
90 data_sources: Iterable[str] = (),
91 extra_args: Iterable[str] = ()
93 """Executes a Bloaty size report on some binary file(s).
96 filename: Path to the binary.
97 config: Path to Bloaty config file.
98 base_file: Path to a base binary. If provided, a size diff is performed.
99 data_sources: List of Bloaty data sources for the report.
100 extra_args: Additional command-line arguments to pass to Bloaty.
103 Binary output of the Bloaty invocation.
106 subprocess.CalledProcessError: The Bloaty invocation failed.
109 # TODO(frolv): Point the default bloaty path to a prebuilt in Pigweed.
110 default_bloaty = 'bloaty'
111 bloaty_path = os.getenv('BLOATY_PATH', default_bloaty)
117 '-d', ','.join(data_sources),
124 if base_file is not None:
125 cmd.extend(['--', base_file])
127 return subprocess.check_output(cmd)
131 """Program entry point."""
135 base_binaries: List[str] = []
136 diff_binaries: List[str] = []
139 for binary, base in args.diff_targets:
140 diff_binaries.append(binary)
141 base_binaries.append(base)
142 except RuntimeError as err:
143 _LOG.error('%s: %s', sys.argv[0], err)
146 data_sources = ['segment_names']
148 data_sources.append('fullsymbols')
150 # TODO(frolv): CSV output is disabled for full reports as the default Bloaty
151 # breakdown is printed. This script should be modified to print a custom
152 # symbol breakdown in full reports.
153 extra_args = [] if args.full else ['--csv']
154 if args.source_filter:
155 extra_args.extend(['--source-filter', args.source_filter])
157 diffs: List[BinaryDiff] = []
160 for i, binary in enumerate(diff_binaries):
161 binary_name = (args.labels[i]
162 if i < len(args.labels) else os.path.basename(binary))
164 output = run_bloaty(binary, args.bloaty_config[i],
165 base_binaries[i], data_sources, extra_args)
169 # TODO(frolv): Remove when custom output for full mode is added.
171 report.append(binary_name)
172 report.append('-' * len(binary_name))
173 report.append(output.decode())
176 # Ignore the first row as it displays column names.
177 bloaty_csv = output.decode().splitlines()[1:]
178 diffs.append(BinaryDiff.from_csv(binary_name, bloaty_csv))
179 except subprocess.CalledProcessError:
180 _LOG.error('%s: failed to run diff on %s', sys.argv[0], binary)
183 def write_file(filename: str, contents: str) -> None:
184 path = os.path.join(args.out_dir, filename)
185 with open(path, 'w') as output_file:
186 output_file.write(contents)
187 _LOG.debug('Output written to %s', path)
189 # TODO(frolv): Remove when custom output for full mode is added.
191 out = bloat_output.TableOutput(args.title,
193 charset=bloat_output.LineCharset)
194 report.append(out.diff())
196 rst = bloat_output.RstOutput(diffs)
197 write_file(f'{args.target}', rst.diff())
199 complete_output = '\n'.join(report) + '\n'
200 write_file(f'{args.target}.txt', complete_output)
201 print(complete_output)
206 if __name__ == '__main__':