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