check_sizes.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. #!/usr/bin/env python
  2. #
  3. # check_sizes.py is a tool run by the ESP-IDF build system
  4. # to check a particular binary fits in the available partitions of
  5. # a particular type/subtype. Can be used to check if the app binary fits in
  6. # all available app partitions, for example.
  7. #
  8. # (Can also check if the bootloader binary fits before the partition table.)
  9. #
  10. # SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
  11. # SPDX-License-Identifier: Apache-2.0
  12. from __future__ import division, print_function, unicode_literals
  13. import argparse
  14. import io # noqa: F401 # pylint: disable=unused-import
  15. import os
  16. import sys
  17. try:
  18. from typing import IO # noqa: F401 # pylint: disable=unused-import
  19. except ImportError:
  20. pass # used for type hinting only
  21. import gen_esp32part
  22. from gen_esp32part import PartitionTable, get_ptype_as_int, get_subtype_as_int
  23. allow_failures = False
  24. def _file_size(f): # type: (IO) -> int
  25. before = f.tell()
  26. f.seek(0, 2) # seek to end
  27. result = f.tell()
  28. f.seek(before)
  29. return result
  30. def _fail(msg): # type: (str) -> None
  31. if allow_failures:
  32. print('Warning: {}'.format(msg))
  33. else:
  34. raise SystemExit('Error: {}'.format(msg))
  35. def check_bootloader(partition_table_offset, bootloader_offset, binary_file): # type: (int, int, IO) -> None
  36. max_size = partition_table_offset - bootloader_offset
  37. bootloader_size = _file_size(binary_file)
  38. if bootloader_size > max_size:
  39. msg = ('Bootloader binary size {:#x} bytes is too large for partition table offset {:#02x}. ' +
  40. 'Bootloader binary can be maximum {:#x} ({}) bytes unless the partition table offset ' +
  41. 'is increased in the Partition Table section of the project configuration menu.').format(
  42. bootloader_size, partition_table_offset, max_size, max_size)
  43. _fail(msg)
  44. free_size = max_size - bootloader_size
  45. print('Bootloader binary size {:#x} bytes. {:#x} bytes ({}%) free.'.format(
  46. bootloader_size, free_size, round(free_size * 100 / max_size)))
  47. def check_partition(ptype, subtype, partition_table_file, bin_file): # type: (str, str, io.IOBase, IO) -> None
  48. table, _ = PartitionTable.from_file(partition_table_file)
  49. ptype_str = str(ptype)
  50. ptype = get_ptype_as_int(ptype)
  51. partitions = [p for p in table if p.type == ptype]
  52. if subtype is not None:
  53. ptype_str += ' ({})'.format(subtype)
  54. subtype = get_subtype_as_int(ptype, subtype)
  55. partitions = [p for p in partitions if p.subtype == subtype]
  56. if len(partitions) == 0:
  57. print('WARNING: Partition table does not contain any partitions matching {}'.format(ptype_str))
  58. return
  59. bin_name = os.path.basename(bin_file.name)
  60. bin_size = _file_size(bin_file)
  61. smallest_size = min(p.size for p in partitions)
  62. if smallest_size >= bin_size:
  63. free_size = smallest_size - bin_size
  64. free_size_relative = free_size / smallest_size
  65. print('{} binary size {:#x} bytes. Smallest {} partition is {:#x} bytes. {:#x} bytes ({:.0%}) free.'.format(
  66. bin_name, bin_size, ptype_str, smallest_size, free_size, free_size_relative))
  67. free_size_relative_critical = 0.05
  68. if free_size_relative < free_size_relative_critical:
  69. print('Warning: The smallest {} partition is nearly full ({:.0%} free space left)!'.format(ptype_str, free_size_relative))
  70. return
  71. too_small_partitions = [p for p in partitions if p.size < bin_size]
  72. if len(partitions) == 1:
  73. msg = '{} partition is'.format(ptype_str)
  74. elif len(partitions) == len(too_small_partitions):
  75. msg = 'All {} partitions are'.format(ptype_str)
  76. else:
  77. msg = '{}/{} {} partitions are'.format(len(too_small_partitions), len(partitions), ptype_str)
  78. msg += ' too small for binary {} size {:#x}:'.format(bin_name, bin_size)
  79. for p in too_small_partitions:
  80. msg += '\n - {} (overflow {:#x})'.format(p, bin_size - p.size)
  81. if not allow_failures and len(partitions) == len(too_small_partitions):
  82. # if some partitions can fit the binary then just print a warning
  83. raise SystemExit('Error: ' + msg)
  84. else:
  85. print('Warning: ' + msg)
  86. def main(): # type: () -> None
  87. global allow_failures # pylint: disable=global-statement
  88. parser = argparse.ArgumentParser(description='Check binary sizes against partition table entries')
  89. parser.add_argument('--target', choices=['esp32', 'esp32s2'])
  90. parser.add_argument('--allow_failures', action='store_true', help='If true, failures will print warnings but not exit with an error')
  91. parser.add_argument('--offset', '-o', help='Set partition table offset', default='0x8000')
  92. subparsers = parser.add_subparsers(dest='check_target',
  93. help='Type of binary to check against partition table layout')
  94. sp_bootloader = subparsers.add_parser('bootloader')
  95. sp_bootloader.add_argument('bootloader_offset', help='Hex offset of bootloader in flash')
  96. sp_bootloader.add_argument('bootloader_binary', type=argparse.FileType('rb'), help='Bootloader binary (.bin) file from build output')
  97. sp_part = subparsers.add_parser('partition')
  98. sp_part.add_argument('--type', type=str, help='Check the file size against all partitions of this type.', required=True)
  99. sp_part.add_argument('--subtype', type=str, help='Optional, only check the file size against all partitions of this subtype.')
  100. sp_part.add_argument('partition_table', type=argparse.FileType('rb'), help='Partition table file')
  101. sp_part.add_argument('binary', type=argparse.FileType('rb'), help='Binary file which will have the size checked')
  102. args = parser.parse_args()
  103. gen_esp32part.quiet = True
  104. args.offset = int(args.offset, 0)
  105. gen_esp32part.offset_part_table = args.offset
  106. if args.check_target is None: # add_subparsers only has a 'required' argument since Python 3
  107. parser.print_help()
  108. sys.exit(1)
  109. if args.check_target == 'bootloader':
  110. check_bootloader(args.offset, int(args.bootloader_offset, 0), args.bootloader_binary)
  111. else:
  112. check_partition(args.type, args.subtype, args.partition_table, args.binary)
  113. if __name__ == '__main__':
  114. main()