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.
23 from typing import List, Iterable, Optional
27 from pw_bloat.binary_diff import BinaryDiff
28 from pw_bloat import bloat_output
30 _LOG = logging.getLogger(__name__)
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)
39 if items and len(args) != items:
40 raise argparse.ArgumentTypeError(
42 f'{delimiter}-delimited list with {items} items: "{arg}"')
48 parser = argparse.ArgumentParser(
49 'Generate a size report card for binaries')
50 parser.add_argument('--bloaty-config',
51 type=delimited_list(';'),
53 help='Data source configuration for Bloaty')
54 parser.add_argument('--full',
56 help='Display full bloat breakdown by symbol')
57 parser.add_argument('--labels',
58 type=delimited_list(';'),
60 help='Labels for output binaries')
61 parser.add_argument('--out-dir',
64 help='Directory in which to write output files')
65 parser.add_argument('--target',
68 help='Build target name')
69 parser.add_argument('--title',
73 parser.add_argument('--source-filter',
75 help='Bloaty data source filter')
76 parser.add_argument('diff_targets',
77 type=delimited_list(';', 2),
79 metavar='DIFF_TARGET',
80 help='Binary;base pairs to process')
82 return parser.parse_args()
88 base_file: Optional[str] = None,
89 data_sources: Iterable[str] = (),
90 extra_args: Iterable[str] = ()
92 """Executes a Bloaty size report on some binary file(s).
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.
102 Binary output of the Bloaty invocation.
105 subprocess.CalledProcessError: The Bloaty invocation failed.
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)
116 '-d', ','.join(data_sources),
123 if base_file is not None:
124 cmd.extend(['--', base_file])
126 return subprocess.check_output(cmd)
130 """Program entry point."""
134 base_binaries: List[str] = []
135 diff_binaries: List[str] = []
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)
145 data_sources = ['segment_names']
147 data_sources.append('fullsymbols')
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])
156 diffs: List[BinaryDiff] = []
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))
163 output = run_bloaty(binary, args.bloaty_config[i],
164 base_binaries[i], data_sources, extra_args)
168 # TODO(frolv): Remove when custom output for full mode is added.
170 report.append(binary_name)
171 report.append('-' * len(binary_name))
172 report.append(output.decode())
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)
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)
188 # TODO(frolv): Remove when custom output for full mode is added.
190 out = bloat_output.TableOutput(args.title,
192 charset=bloat_output.LineCharset)
193 report.append(out.diff())
195 rst = bloat_output.RstOutput(diffs)
196 write_file(f'{args.target}', rst.diff())
198 complete_output = '\n'.join(report) + '\n'
199 write_file(f'{args.target}.txt', complete_output)
200 print(complete_output)
205 if __name__ == '__main__':