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