mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-03 03:31:41 +03:00
fix(partition_table): normalize CLI output with esp-pylib
This commit is contained in:
@@ -7,15 +7,12 @@
|
||||
#
|
||||
# (Can also check if the bootloader binary fits before the partition table.)
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2020-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import io # noqa: F401 # pylint: disable=unused-import
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
from typing import IO # noqa: F401 # pylint: disable=unused-import
|
||||
@@ -23,12 +20,17 @@ except ImportError:
|
||||
pass # used for type hinting only
|
||||
|
||||
import gen_esp32part
|
||||
from gen_esp32part import PartitionTable, get_ptype_as_int, get_subtype_as_int
|
||||
import rich_click as click
|
||||
from esp_pylib.cli_types import AnyIntType
|
||||
from esp_pylib.logger import log
|
||||
from gen_esp32part import PartitionTable
|
||||
from gen_esp32part import get_ptype_as_int
|
||||
from gen_esp32part import get_subtype_as_int
|
||||
|
||||
allow_failures = False
|
||||
|
||||
|
||||
def _file_size(f): # type: (IO) -> int
|
||||
def _file_size(f): # type: (IO) -> int
|
||||
before = f.tell()
|
||||
f.seek(0, 2) # seek to end
|
||||
result = f.tell()
|
||||
@@ -36,25 +38,27 @@ def _file_size(f): # type: (IO) -> int
|
||||
return result
|
||||
|
||||
|
||||
def _fail(msg): # type: (str) -> None
|
||||
def _fail(msg): # type: (str) -> None
|
||||
if allow_failures:
|
||||
print('Warning: {}'.format(msg))
|
||||
log.warn(msg)
|
||||
else:
|
||||
raise SystemExit('Error: {}'.format(msg))
|
||||
log.err(msg)
|
||||
raise SystemExit(msg)
|
||||
|
||||
|
||||
def check_bootloader(partition_table_offset, bootloader_offset, binary_file): # type: (int, int, IO) -> None
|
||||
max_size = partition_table_offset - bootloader_offset
|
||||
bootloader_size = _file_size(binary_file)
|
||||
if bootloader_size > max_size:
|
||||
msg = ('Bootloader binary size {:#x} bytes is too large for partition table offset {:#02x}. ' +
|
||||
'Bootloader binary can be maximum {:#x} ({}) bytes unless the partition table offset ' +
|
||||
'is increased in the Partition Table section of the project configuration menu.').format(
|
||||
bootloader_size, partition_table_offset, max_size, max_size)
|
||||
msg = (
|
||||
'Bootloader binary size {:#x} bytes is too large for partition table offset {:#02x}. '
|
||||
+ 'Bootloader binary can be maximum {:#x} ({}) bytes unless the partition table offset '
|
||||
+ 'is increased in the Partition Table section of the project configuration menu.'
|
||||
).format(bootloader_size, partition_table_offset, max_size, max_size)
|
||||
_fail(msg)
|
||||
free_size = max_size - bootloader_size
|
||||
print('Bootloader binary size {:#x} bytes. {:#x} bytes ({}%) free.'.format(
|
||||
bootloader_size, free_size, round(free_size * 100 / max_size)))
|
||||
pct = round(free_size * 100 / max_size)
|
||||
log.print(f'Bootloader binary size {bootloader_size:#x} bytes. {free_size:#x} bytes ({pct}%) free.')
|
||||
|
||||
|
||||
def check_partition(ptype, subtype, partition_table_file, bin_file): # type: (str, str, io.IOBase, IO) -> None
|
||||
@@ -64,12 +68,12 @@ def check_partition(ptype, subtype, partition_table_file, bin_file): # type: (s
|
||||
partitions = [p for p in table if p.type == ptype]
|
||||
|
||||
if subtype is not None:
|
||||
ptype_str += ' ({})'.format(subtype)
|
||||
ptype_str += f' ({subtype})'
|
||||
subtype = get_subtype_as_int(ptype, subtype)
|
||||
partitions = [p for p in partitions if p.subtype == subtype]
|
||||
|
||||
if len(partitions) == 0:
|
||||
print('WARNING: Partition table does not contain any partitions matching {}'.format(ptype_str))
|
||||
log.warn(f'Partition table does not contain any partitions matching {ptype_str}')
|
||||
return
|
||||
|
||||
bin_name = os.path.basename(bin_file.name)
|
||||
@@ -78,65 +82,87 @@ def check_partition(ptype, subtype, partition_table_file, bin_file): # type: (s
|
||||
if smallest_size >= bin_size:
|
||||
free_size = smallest_size - bin_size
|
||||
free_size_relative = free_size / smallest_size
|
||||
print('{} binary size {:#x} bytes. Smallest {} partition is {:#x} bytes. {:#x} bytes ({:.0%}) free.'.format(
|
||||
bin_name, bin_size, ptype_str, smallest_size, free_size, free_size_relative))
|
||||
log.print(
|
||||
f'{bin_name} binary size {bin_size:#x} bytes. Smallest {ptype_str} partition is '
|
||||
f'{smallest_size:#x} bytes. {free_size:#x} bytes ({free_size_relative:.0%}) free.'
|
||||
)
|
||||
free_size_relative_critical = 0.05
|
||||
if free_size_relative < free_size_relative_critical:
|
||||
print('Warning: The smallest {} partition is nearly full ({:.0%} free space left)!'.format(ptype_str, free_size_relative))
|
||||
log.warn(f'The smallest {ptype_str} partition is nearly full ({free_size_relative:.0%} free space left)!')
|
||||
return
|
||||
|
||||
too_small_partitions = [p for p in partitions if p.size < bin_size]
|
||||
if len(partitions) == 1:
|
||||
msg = '{} partition is'.format(ptype_str)
|
||||
msg = f'{ptype_str} partition is'
|
||||
elif len(partitions) == len(too_small_partitions):
|
||||
msg = 'All {} partitions are'.format(ptype_str)
|
||||
msg = f'All {ptype_str} partitions are'
|
||||
else:
|
||||
msg = '{}/{} {} partitions are'.format(len(too_small_partitions), len(partitions), ptype_str)
|
||||
msg += ' too small for binary {} size {:#x}:'.format(bin_name, bin_size)
|
||||
msg = f'{len(too_small_partitions)}/{len(partitions)} {ptype_str} partitions are'
|
||||
msg += f' too small for binary {bin_name} size {bin_size:#x}:'
|
||||
for p in too_small_partitions:
|
||||
msg += '\n - {} (overflow {:#x})'.format(p, bin_size - p.size)
|
||||
msg += f'\n - {p} (overflow {bin_size - p.size:#x})'
|
||||
if not allow_failures and len(partitions) == len(too_small_partitions):
|
||||
# if some partitions can fit the binary then just print a warning
|
||||
raise SystemExit('Error: ' + msg)
|
||||
_fail(msg)
|
||||
else:
|
||||
print('Warning: ' + msg)
|
||||
log.warn(msg)
|
||||
|
||||
|
||||
def _configure_globals(offset: int, allow_failures_flag: bool) -> None:
|
||||
global allow_failures # pylint: disable=global-statement
|
||||
|
||||
gen_esp32part.quiet = True
|
||||
gen_esp32part.offset_part_table = offset
|
||||
allow_failures = allow_failures_flag
|
||||
|
||||
|
||||
@click.group(
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
help='Check binary sizes against partition table entries',
|
||||
)
|
||||
@click.option('--target', type=click.Choice(['esp32', 'esp32s2']))
|
||||
@click.option(
|
||||
'--allow-failures',
|
||||
'allow_failures_flag',
|
||||
is_flag=True,
|
||||
help='If true, failures will print warnings but not exit with an error',
|
||||
)
|
||||
@click.option('--offset', '-o', type=AnyIntType(), default=0x8000, show_default=True, help='Set partition table offset')
|
||||
@click.pass_context
|
||||
def cli(ctx, target, allow_failures_flag, offset): # type: ignore[no-untyped-def]
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj['offset'] = offset
|
||||
ctx.obj['allow_failures'] = allow_failures_flag
|
||||
|
||||
|
||||
@cli.command('bootloader', help='Check bootloader binary size against partition table offset')
|
||||
@click.argument('bootloader_offset', type=AnyIntType())
|
||||
@click.argument('bootloader_binary', type=click.File('rb'))
|
||||
@click.pass_context
|
||||
def bootloader_cmd(ctx, bootloader_offset, bootloader_binary): # type: ignore[no-untyped-def]
|
||||
_configure_globals(ctx.obj['offset'], ctx.obj['allow_failures'])
|
||||
check_bootloader(ctx.obj['offset'], bootloader_offset, bootloader_binary)
|
||||
|
||||
|
||||
@cli.command('partition', help='Check binary size against partition table entries')
|
||||
@click.option(
|
||||
'--type', 'partition_type', required=True, help='Check the file size against all partitions of this type.'
|
||||
)
|
||||
@click.option('--subtype', help='Optional, only check the file size against all partitions of this subtype.')
|
||||
@click.argument('partition_table', type=click.File('rb'))
|
||||
@click.argument('binary', type=click.File('rb'))
|
||||
@click.pass_context
|
||||
def partition_cmd(ctx, partition_type, subtype, partition_table, binary): # type: ignore[no-untyped-def]
|
||||
_configure_globals(ctx.obj['offset'], ctx.obj['allow_failures'])
|
||||
check_partition(partition_type, subtype, partition_table, binary)
|
||||
|
||||
|
||||
def main(): # type: () -> None
|
||||
global allow_failures # pylint: disable=global-statement
|
||||
|
||||
parser = argparse.ArgumentParser(description='Check binary sizes against partition table entries')
|
||||
parser.add_argument('--target', choices=['esp32', 'esp32s2'])
|
||||
parser.add_argument('--allow_failures', action='store_true', help='If true, failures will print warnings but not exit with an error')
|
||||
parser.add_argument('--offset', '-o', help='Set partition table offset', default='0x8000')
|
||||
|
||||
subparsers = parser.add_subparsers(dest='check_target',
|
||||
help='Type of binary to check against partition table layout')
|
||||
sp_bootloader = subparsers.add_parser('bootloader')
|
||||
sp_bootloader.add_argument('bootloader_offset', help='Hex offset of bootloader in flash')
|
||||
sp_bootloader.add_argument('bootloader_binary', type=argparse.FileType('rb'), help='Bootloader binary (.bin) file from build output')
|
||||
|
||||
sp_part = subparsers.add_parser('partition')
|
||||
sp_part.add_argument('--type', type=str, help='Check the file size against all partitions of this type.', required=True)
|
||||
sp_part.add_argument('--subtype', type=str, help='Optional, only check the file size against all partitions of this subtype.')
|
||||
sp_part.add_argument('partition_table', type=argparse.FileType('rb'), help='Partition table file')
|
||||
sp_part.add_argument('binary', type=argparse.FileType('rb'), help='Binary file which will have the size checked')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
gen_esp32part.quiet = True
|
||||
|
||||
args.offset = int(args.offset, 0)
|
||||
gen_esp32part.offset_part_table = args.offset
|
||||
|
||||
if args.check_target is None: # add_subparsers only has a 'required' argument since Python 3
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
if args.check_target == 'bootloader':
|
||||
check_bootloader(args.offset, int(args.bootloader_offset, 0), args.bootloader_binary)
|
||||
else:
|
||||
check_partition(args.type, args.subtype, args.partition_table, args.binary)
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
|
||||
install_exception_reporting()
|
||||
main()
|
||||
|
||||
@@ -4,21 +4,20 @@
|
||||
#
|
||||
# This tool generates an empty binary file of the required size.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2018-2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
from __future__ import division, print_function, unicode_literals
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
import rich_click as click
|
||||
from esp_pylib.cli_types import AnyIntType
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
quiet = False
|
||||
|
||||
|
||||
def generate_blanked_file(size, output_path):
|
||||
output = b'\xFF' * size
|
||||
output = b'\xff' * size
|
||||
try:
|
||||
stdout_binary = sys.stdout.buffer # Python 3
|
||||
except AttributeError:
|
||||
@@ -27,28 +26,23 @@ def generate_blanked_file(size, output_path):
|
||||
f.write(output)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='Generates an empty binary file of the required size.')
|
||||
parser.add_argument('size', help='Size of generated the file', type=str)
|
||||
|
||||
parser.add_argument('output', help='Path for binary file.', nargs='?', default='-')
|
||||
args = parser.parse_args()
|
||||
|
||||
size = int(args.size, 0)
|
||||
@click.command(
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
help='Generates an empty binary file of the required size.',
|
||||
)
|
||||
@click.argument('size', type=AnyIntType(), metavar='SIZE')
|
||||
@click.argument('output', type=click.Path(), required=False, default='-')
|
||||
def cli(size, output):
|
||||
if size > 0:
|
||||
generate_blanked_file(size, args.output)
|
||||
return 0
|
||||
generate_blanked_file(size, output)
|
||||
|
||||
|
||||
class InputError(RuntimeError):
|
||||
def __init__(self, e):
|
||||
super(InputError, self).__init__(e)
|
||||
def main():
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
r = main()
|
||||
sys.exit(r)
|
||||
except InputError as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(2)
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
|
||||
install_exception_reporting()
|
||||
main()
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
# See https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/partition-tables.html
|
||||
# for explanation of partition table structure and uses.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
# ruff: noqa: UP031, UP008
|
||||
import binascii
|
||||
import codecs
|
||||
import errno
|
||||
@@ -19,6 +19,10 @@ import re
|
||||
import struct
|
||||
import sys
|
||||
|
||||
import rich_click as click
|
||||
from esp_pylib.cli_types import AnyIntType
|
||||
from esp_pylib.logger import log
|
||||
|
||||
MAX_PARTITION_LENGTH = 0xC00 # 3K for partition data (96 entries) leaves 1K in a 4K sector for signature
|
||||
MD5_PARTITION_BEGIN = b'\xeb\xeb' + b'\xff' * 14 # The first 2 bytes are like magic numbers for MD5 sum
|
||||
PARTITION_TABLE_SIZE = 0x1000 # Size of partition table
|
||||
@@ -167,7 +171,8 @@ recovery_bootloader_offset = None
|
||||
def status(msg):
|
||||
"""Print status message to stderr"""
|
||||
if not quiet:
|
||||
critical(msg)
|
||||
sys.stderr.write(msg)
|
||||
sys.stderr.write('\n')
|
||||
|
||||
|
||||
def critical(msg):
|
||||
@@ -420,7 +425,7 @@ class PartitionTable(list):
|
||||
return '\n'.join(rows) + '\n'
|
||||
|
||||
|
||||
class PartitionDefinition(object):
|
||||
class PartitionDefinition:
|
||||
MAGIC_BYTES = b'\xaa\x50'
|
||||
|
||||
# dictionary maps flag name (as used in CSV flags list, property name)
|
||||
@@ -689,82 +694,54 @@ def parse_int(v, keywords={}):
|
||||
raise InputError("Value '%s' is not valid. Known keywords: %s" % (v, ', '.join(keywords)))
|
||||
|
||||
|
||||
def main():
|
||||
def run_gen(
|
||||
input_file,
|
||||
output='-',
|
||||
flash_size=None,
|
||||
disable_md5sum=False,
|
||||
no_verify=False,
|
||||
quiet_flag=False,
|
||||
offset=0x8000,
|
||||
primary_bootloader_offset_arg=None,
|
||||
recovery_bootloader_offset_arg=None,
|
||||
secure_arg=None,
|
||||
extra_partition_subtypes=None,
|
||||
):
|
||||
global quiet
|
||||
global md5sum
|
||||
global offset_part_table
|
||||
global secure
|
||||
global primary_bootloader_offset
|
||||
global recovery_bootloader_offset
|
||||
parser = argparse.ArgumentParser(description='ESP32 partition table utility')
|
||||
|
||||
parser.add_argument(
|
||||
'--flash-size',
|
||||
help='Optional flash size limit, checks partition table fits in flash',
|
||||
nargs='?',
|
||||
choices=['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB'],
|
||||
)
|
||||
parser.add_argument(
|
||||
'--disable-md5sum', help='Disable md5 checksum for the partition table', default=False, action='store_true'
|
||||
)
|
||||
parser.add_argument('--no-verify', help="Don't verify partition table fields", action='store_true')
|
||||
parser.add_argument(
|
||||
'--verify',
|
||||
'-v',
|
||||
help='Verify partition table fields (deprecated, this behaviour is '
|
||||
'enabled by default and this flag does nothing.',
|
||||
action='store_true',
|
||||
)
|
||||
parser.add_argument('--quiet', '-q', help="Don't print non-critical status messages to stderr", action='store_true')
|
||||
parser.add_argument('--offset', '-o', help='Set offset partition table', default='0x8000')
|
||||
parser.add_argument('--primary-bootloader-offset', help='Set primary bootloader offset', default=None)
|
||||
parser.add_argument('--recovery-bootloader-offset', help='Set recovery bootloader offset', default=None)
|
||||
parser.add_argument(
|
||||
'--secure',
|
||||
help='Require app partitions to be suitable for secure boot',
|
||||
nargs='?',
|
||||
const=SECURE_V1,
|
||||
choices=[SECURE_V1, SECURE_V2],
|
||||
)
|
||||
parser.add_argument('--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*')
|
||||
parser.add_argument('input', help='Path to CSV or binary file to parse.', type=argparse.FileType('rb'))
|
||||
parser.add_argument(
|
||||
'output',
|
||||
help='Path to output converted binary or CSV file. Will use stdout if omitted.',
|
||||
nargs='?',
|
||||
default='-',
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
quiet = args.quiet
|
||||
md5sum = not args.disable_md5sum
|
||||
secure = args.secure
|
||||
offset_part_table = int(args.offset, 0)
|
||||
if args.primary_bootloader_offset is not None:
|
||||
primary_bootloader_offset = int(args.primary_bootloader_offset, 0)
|
||||
quiet = quiet_flag
|
||||
md5sum = not disable_md5sum
|
||||
secure = secure_arg
|
||||
offset_part_table = offset
|
||||
if primary_bootloader_offset_arg is not None:
|
||||
primary_bootloader_offset = primary_bootloader_offset_arg
|
||||
if primary_bootloader_offset >= offset_part_table:
|
||||
raise InputError(
|
||||
f'Unsupported configuration. Primary bootloader must be below partition table. '
|
||||
f'Check --primary-bootloader-offset={primary_bootloader_offset:#x} and --offset={offset_part_table:#x}'
|
||||
)
|
||||
if args.recovery_bootloader_offset is not None:
|
||||
recovery_bootloader_offset = int(args.recovery_bootloader_offset, 0)
|
||||
if args.extra_partition_subtypes:
|
||||
add_extra_subtypes(args.extra_partition_subtypes)
|
||||
if recovery_bootloader_offset_arg is not None:
|
||||
recovery_bootloader_offset = recovery_bootloader_offset_arg
|
||||
if extra_partition_subtypes:
|
||||
add_extra_subtypes(extra_partition_subtypes)
|
||||
|
||||
table, input_is_binary = PartitionTable.from_file(args.input)
|
||||
table, input_is_binary = PartitionTable.from_file(input_file)
|
||||
|
||||
if not args.no_verify:
|
||||
if not no_verify:
|
||||
status('Verifying table...')
|
||||
table.verify()
|
||||
|
||||
if args.flash_size:
|
||||
size_mb = int(args.flash_size.replace('MB', ''))
|
||||
if flash_size:
|
||||
size_mb = int(flash_size.replace('MB', ''))
|
||||
table.verify_size_fits(size_mb * 1024 * 1024)
|
||||
|
||||
# Make sure that the output directory is created
|
||||
output_dir = os.path.abspath(os.path.dirname(args.output))
|
||||
output_dir = os.path.abspath(os.path.dirname(output))
|
||||
|
||||
if not os.path.exists(output_dir):
|
||||
try:
|
||||
@@ -774,17 +751,82 @@ def main():
|
||||
raise
|
||||
|
||||
if input_is_binary:
|
||||
output = table.to_csv()
|
||||
with sys.stdout if args.output == '-' else open(args.output, 'w', encoding='utf-8') as f:
|
||||
f.write(output)
|
||||
output_data = table.to_csv()
|
||||
with sys.stdout if output == '-' else open(output, 'w', encoding='utf-8') as f:
|
||||
f.write(output_data)
|
||||
else:
|
||||
output = table.to_binary()
|
||||
output_data = table.to_binary()
|
||||
try:
|
||||
stdout_binary = sys.stdout.buffer # Python 3
|
||||
except AttributeError:
|
||||
stdout_binary = sys.stdout
|
||||
with stdout_binary if args.output == '-' else open(args.output, 'wb') as f:
|
||||
f.write(output)
|
||||
with stdout_binary if output == '-' else open(output, 'wb') as f:
|
||||
f.write(output_data)
|
||||
|
||||
|
||||
@click.command(
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
help='ESP32 partition table utility',
|
||||
)
|
||||
@click.option(
|
||||
'--flash-size',
|
||||
type=click.Choice(['1MB', '2MB', '4MB', '8MB', '16MB', '32MB', '64MB', '128MB']),
|
||||
help='Optional flash size limit, checks partition table fits in flash',
|
||||
)
|
||||
@click.option('--disable-md5sum', is_flag=True, help='Disable md5 checksum for the partition table')
|
||||
@click.option('--no-verify', is_flag=True, help="Don't verify partition table fields")
|
||||
@click.option(
|
||||
'--verify',
|
||||
'-v',
|
||||
is_flag=True,
|
||||
help='Verify partition table fields (deprecated, this behaviour is enabled by default and this flag does nothing).',
|
||||
)
|
||||
@click.option('--quiet', '-q', is_flag=True, help="Don't print non-critical status messages to stderr")
|
||||
@click.option('--offset', '-o', type=AnyIntType(), default=0x8000, show_default=True, help='Set offset partition table')
|
||||
@click.option('--primary-bootloader-offset', type=AnyIntType(), default=None, help='Set primary bootloader offset')
|
||||
@click.option('--recovery-bootloader-offset', type=AnyIntType(), default=None, help='Set recovery bootloader offset')
|
||||
@click.option(
|
||||
'--secure',
|
||||
is_flag=False,
|
||||
flag_value=SECURE_V1,
|
||||
type=click.Choice([SECURE_V1, SECURE_V2], case_sensitive=False),
|
||||
default=None,
|
||||
help='Require app partitions to be suitable for secure boot',
|
||||
)
|
||||
@click.option('--extra-partition-subtypes', multiple=True, help='Extra partition subtype entries')
|
||||
@click.argument('input_file', type=click.File('rb'), metavar='INPUT')
|
||||
@click.argument('output', type=click.Path(), required=False, default='-')
|
||||
def cli(
|
||||
input_file,
|
||||
output,
|
||||
flash_size,
|
||||
disable_md5sum,
|
||||
no_verify,
|
||||
verify,
|
||||
quiet,
|
||||
offset,
|
||||
primary_bootloader_offset,
|
||||
recovery_bootloader_offset,
|
||||
secure,
|
||||
extra_partition_subtypes,
|
||||
):
|
||||
run_gen(
|
||||
input_file=input_file,
|
||||
output=output,
|
||||
flash_size=flash_size,
|
||||
disable_md5sum=disable_md5sum,
|
||||
no_verify=no_verify,
|
||||
quiet_flag=quiet,
|
||||
offset=offset,
|
||||
primary_bootloader_offset_arg=primary_bootloader_offset,
|
||||
recovery_bootloader_offset_arg=recovery_bootloader_offset,
|
||||
secure_arg=secure,
|
||||
extra_partition_subtypes=extra_partition_subtypes or None,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
cli()
|
||||
|
||||
|
||||
class InputError(RuntimeError):
|
||||
@@ -798,8 +840,12 @@ class ValidationError(InputError):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
|
||||
install_exception_reporting()
|
||||
try:
|
||||
main()
|
||||
except InputError as e:
|
||||
print(e, file=sys.stderr)
|
||||
# soft_wrap avoids Rich line-breaking long errors (build-system tests grep substrings).
|
||||
log.print(str(e), file=sys.stderr, soft_wrap=True, markup=False)
|
||||
sys.exit(2)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python
|
||||
# SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import rich_click as click
|
||||
from esp_pylib.logger import log
|
||||
|
||||
|
||||
def gen_header_file(path: str, subtypes: str) -> None:
|
||||
@@ -22,15 +23,27 @@ def gen_header_file(path: str, subtypes: str) -> None:
|
||||
fields[0] = fields[0].strip()
|
||||
fields[1] = fields[1].strip()
|
||||
fields[2] = fields[2].strip()
|
||||
f.write('ESP_PARTITION_SUBTYPE_%s_%s = %s,\n' % (fields[0].upper(), fields[1].upper(), fields[2]))
|
||||
f.write(f'ESP_PARTITION_SUBTYPE_{fields[0].upper()}_{fields[1].upper()} = {fields[2]},\n')
|
||||
except ValueError as err:
|
||||
raise ValueError('Error parsing custom subtypes: %s' % err)
|
||||
log.die(f'Error parsing custom subtypes: {err}')
|
||||
|
||||
|
||||
@click.command(
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
help='ESP32 extra partitions utility',
|
||||
)
|
||||
@click.argument('config_dir', type=click.Path())
|
||||
@click.argument('extra_partition_subtypes', nargs=-1)
|
||||
def cli(config_dir, extra_partition_subtypes): # type: ignore[no-untyped-def]
|
||||
gen_header_file(config_dir, extra_partition_subtypes)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description='ESP32 extra partitions utility')
|
||||
parser.add_argument('config_dir', help='Path to config directory')
|
||||
parser.add_argument('extra_partition_subtypes', help='Extra partition subtype entries', nargs='*')
|
||||
args = parser.parse_args()
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
|
||||
gen_header_file(args.config_dir, args.extra_partition_subtypes)
|
||||
install_exception_reporting()
|
||||
main()
|
||||
|
||||
@@ -3,9 +3,8 @@
|
||||
# parttool is used to perform partition level operations - reading,
|
||||
# writing, erasing and getting info about the partition.
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2018-2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-FileCopyrightText: 2018-2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
@@ -13,6 +12,12 @@ import sys
|
||||
import tempfile
|
||||
|
||||
import gen_esp32part as gen
|
||||
import rich_click as click
|
||||
from esp_pylib.cli_options import EspRichGroup
|
||||
from esp_pylib.cli_options import MutuallyExclusiveOption
|
||||
from esp_pylib.cli_options import OptionEatAll
|
||||
from esp_pylib.cli_types import AnyIntType
|
||||
from esp_pylib.logger import log
|
||||
|
||||
__version__ = '2.2'
|
||||
|
||||
@@ -21,12 +26,28 @@ COMPONENTS_PATH = os.path.expandvars(os.path.join('$IDF_PATH', 'components'))
|
||||
PARTITION_TABLE_OFFSET = 0x8000
|
||||
|
||||
|
||||
class _StringListParamType(click.ParamType):
|
||||
"""ParamType for `OptionEatAll` without ``multiple=True`` (argparse ``nargs='+'``)."""
|
||||
|
||||
name = 'text_list'
|
||||
|
||||
def convert(self, value, param, ctx):
|
||||
if not value:
|
||||
return ()
|
||||
if isinstance(value, (list, tuple)): # noqa: UP038
|
||||
return tuple(value)
|
||||
return (value,)
|
||||
|
||||
|
||||
_ESPTOOL_ARGS_TYPE = _StringListParamType()
|
||||
|
||||
|
||||
quiet = False
|
||||
|
||||
|
||||
def status(msg):
|
||||
if not quiet:
|
||||
print(msg)
|
||||
log.print(msg, file=sys.stderr)
|
||||
|
||||
|
||||
class _PartitionId:
|
||||
@@ -122,11 +143,11 @@ class ParttoolTarget:
|
||||
|
||||
esptool_args += args
|
||||
|
||||
print(f'Running {" ".join(esptool_args)}...')
|
||||
log.print(f'Running {" ".join(esptool_args)}...', file=sys.stderr)
|
||||
try:
|
||||
subprocess.check_call(esptool_args, stdout=out, stderr=subprocess.STDOUT)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print('An exception: **', str(e), '** occurred in _call_esptool.', file=out)
|
||||
log.print(f'An exception: ** {e} ** occurred in _call_esptool.', file=out or sys.stderr)
|
||||
raise e
|
||||
|
||||
def get_partition_info(self, partition_id):
|
||||
@@ -162,7 +183,7 @@ class ParttoolTarget:
|
||||
partition = self.get_partition_info(partition_id)
|
||||
|
||||
if partition.readonly and not ignore_readonly:
|
||||
raise SystemExit(f'"{partition.name}" partition is read-only, (use the --ignore-readonly flag to skip it)')
|
||||
log.die(f'"{partition.name}" partition is read-only, (use the --ignore-readonly flag to skip it)')
|
||||
|
||||
self.erase_partition(partition_id)
|
||||
|
||||
@@ -219,165 +240,84 @@ def _get_partition_info(target, partition_id, info):
|
||||
for i in info:
|
||||
infos += [info_dict[i]]
|
||||
except KeyError:
|
||||
raise RuntimeError(f'Request for unknown partition info {i}')
|
||||
log.die(f'Request for unknown partition info {i}')
|
||||
|
||||
print(' '.join(infos))
|
||||
|
||||
|
||||
def main():
|
||||
def _partition_selection_options(func):
|
||||
decorators = [
|
||||
click.option(
|
||||
'--partition-name',
|
||||
'-n',
|
||||
cls=MutuallyExclusiveOption,
|
||||
exclusive_with=['partition_type', 'partition_boot_default'],
|
||||
help='name of the partition',
|
||||
),
|
||||
click.option(
|
||||
'--partition-type',
|
||||
'-t',
|
||||
cls=MutuallyExclusiveOption,
|
||||
exclusive_with=['partition_name', 'partition_boot_default'],
|
||||
help='type of the partition',
|
||||
),
|
||||
click.option(
|
||||
'--partition-boot-default',
|
||||
'-d',
|
||||
is_flag=True,
|
||||
cls=MutuallyExclusiveOption,
|
||||
exclusive_with=['partition_name', 'partition_type'],
|
||||
help='select the default boot partition using the same fallback logic as the IDF bootloader',
|
||||
),
|
||||
click.option('--partition-subtype', '-s', help='subtype of the partition'),
|
||||
click.option('--extra-partition-subtypes', multiple=True, help='Extra partition subtype entries'),
|
||||
]
|
||||
for decorator in reversed(decorators):
|
||||
func = decorator(func)
|
||||
return func
|
||||
|
||||
|
||||
def _build_partition_id(partition_name, partition_type, partition_subtype, partition_boot_default, part_list=False):
|
||||
if partition_name:
|
||||
return PartitionName(partition_name)
|
||||
if partition_type:
|
||||
if not partition_subtype:
|
||||
log.die('--partition-subtype should be defined when --partition-type is defined')
|
||||
return PartitionType(partition_type, partition_subtype, part_list)
|
||||
if partition_boot_default:
|
||||
return PARTITION_BOOT_DEFAULT
|
||||
log.die(
|
||||
'Partition to operate on should be defined using --partition-name OR '
|
||||
'partition-type,--partition-subtype OR partition-boot-default'
|
||||
)
|
||||
|
||||
|
||||
def _target_kwargs_from_ctx(ctx_obj, extra_partition_subtypes=None):
|
||||
kwargs = {}
|
||||
for key, value in (
|
||||
('port', ctx_obj.get('port')),
|
||||
('baud', ctx_obj.get('baud')),
|
||||
('partition_table_offset', ctx_obj.get('partition_table_offset')),
|
||||
('primary_bootloader_offset', ctx_obj.get('primary_bootloader_offset')),
|
||||
('recovery_bootloader_offset', ctx_obj.get('recovery_bootloader_offset')),
|
||||
('partition_table_file', ctx_obj.get('partition_table_file')),
|
||||
('esptool_args', ctx_obj.get('esptool_args')),
|
||||
('esptool_write_args', ctx_obj.get('esptool_write_args')),
|
||||
('esptool_read_args', ctx_obj.get('esptool_read_args')),
|
||||
('esptool_erase_args', ctx_obj.get('esptool_erase_args')),
|
||||
):
|
||||
if value is not None and value != ():
|
||||
kwargs[key] = value
|
||||
if extra_partition_subtypes:
|
||||
gen.add_extra_subtypes(extra_partition_subtypes)
|
||||
return kwargs
|
||||
|
||||
|
||||
def _run_operation(operation, quiet_flag, target, partition_id, **op_kwargs):
|
||||
global quiet
|
||||
|
||||
parser = argparse.ArgumentParser('ESP-IDF Partitions Tool')
|
||||
quiet = quiet_flag
|
||||
|
||||
parser.add_argument('--quiet', '-q', help='suppress stderr messages', action='store_true')
|
||||
parser.add_argument('--esptool-args', help='additional main arguments for esptool', nargs='+')
|
||||
parser.add_argument('--esptool-write-args', help='additional subcommand arguments when writing to flash', nargs='+')
|
||||
parser.add_argument('--esptool-read-args', help='additional subcommand arguments when reading flash', nargs='+')
|
||||
parser.add_argument(
|
||||
'--esptool-erase-args', help='additional subcommand arguments when erasing regions of flash', nargs='+'
|
||||
)
|
||||
|
||||
# By default the device attached to the specified port is queried for the partition table. If a partition table file
|
||||
# is specified, that is used instead.
|
||||
parser.add_argument(
|
||||
'--port',
|
||||
'-p',
|
||||
help='port where the target device of the command is connected to; the partition table is sourced from '
|
||||
'this device when the partition table file is not defined',
|
||||
)
|
||||
parser.add_argument('--baud', '-b', help='baudrate to use', type=int)
|
||||
|
||||
parser.add_argument('--partition-table-offset', '-o', help='offset to read the partition table from', type=str)
|
||||
parser.add_argument('--primary-bootloader-offset', help='offset for primary bootloader', type=str)
|
||||
parser.add_argument('--recovery-bootloader-offset', help='offset for recovery bootloader', type=str)
|
||||
parser.add_argument(
|
||||
'--partition-table-file',
|
||||
'-f',
|
||||
help='file (CSV/binary) to read the partition table from; '
|
||||
'overrides device attached to specified port as the partition table source when defined',
|
||||
)
|
||||
|
||||
partition_selection_parser = argparse.ArgumentParser(add_help=False)
|
||||
|
||||
# Specify what partition to perform the operation on. This can either be specified using the
|
||||
# partition name or the first partition that matches the specified type/subtype
|
||||
partition_selection_args = partition_selection_parser.add_mutually_exclusive_group()
|
||||
|
||||
partition_selection_args.add_argument('--partition-name', '-n', help='name of the partition')
|
||||
partition_selection_args.add_argument('--partition-type', '-t', help='type of the partition')
|
||||
partition_selection_args.add_argument(
|
||||
'--partition-boot-default',
|
||||
'-d',
|
||||
help='select the default boot partition \
|
||||
using the same fallback logic as the IDF bootloader',
|
||||
action='store_true',
|
||||
)
|
||||
|
||||
partition_selection_parser.add_argument('--partition-subtype', '-s', help='subtype of the partition')
|
||||
partition_selection_parser.add_argument(
|
||||
'--extra-partition-subtypes', help='Extra partition subtype entries', nargs='*'
|
||||
)
|
||||
|
||||
subparsers = parser.add_subparsers(dest='operation', help='run parttool -h for additional help')
|
||||
|
||||
# Specify the supported operations
|
||||
read_part_subparser = subparsers.add_parser(
|
||||
'read_partition',
|
||||
help='read partition from device and dump contents into a file',
|
||||
parents=[partition_selection_parser],
|
||||
)
|
||||
read_part_subparser.add_argument('--output', help='file to dump the read partition contents to')
|
||||
|
||||
write_part_subparser = subparsers.add_parser(
|
||||
'write_partition',
|
||||
help='write contents of a binary file to partition on device',
|
||||
parents=[partition_selection_parser],
|
||||
)
|
||||
write_part_subparser.add_argument('--input', help='file whose contents are to be written to the partition offset')
|
||||
write_part_subparser.add_argument('--ignore-readonly', help='Ignore read-only attribute', action='store_true')
|
||||
|
||||
subparsers.add_parser(
|
||||
'erase_partition', help='erase the contents of a partition on the device', parents=[partition_selection_parser]
|
||||
)
|
||||
|
||||
print_partition_info_subparser = subparsers.add_parser(
|
||||
'get_partition_info', help='get partition information', parents=[partition_selection_parser]
|
||||
)
|
||||
print_partition_info_subparser.add_argument(
|
||||
'--info',
|
||||
help='type of partition information to get',
|
||||
choices=['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly'],
|
||||
default=['offset', 'size'],
|
||||
nargs='+',
|
||||
)
|
||||
print_partition_info_subparser.add_argument(
|
||||
'--part_list', help='Get a list of partitions suitable for a given type', action='store_true'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
quiet = args.quiet
|
||||
|
||||
# No operation specified, display help and exit
|
||||
if args.operation is None:
|
||||
if not quiet:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
# Prepare the partition to perform operation on
|
||||
if args.partition_name:
|
||||
partition_id = PartitionName(args.partition_name)
|
||||
elif args.partition_type:
|
||||
if not args.partition_subtype:
|
||||
raise RuntimeError('--partition-subtype should be defined when --partition-type is defined')
|
||||
partition_id = PartitionType(args.partition_type, args.partition_subtype, getattr(args, 'part_list', None))
|
||||
elif args.partition_boot_default:
|
||||
partition_id = PARTITION_BOOT_DEFAULT
|
||||
else:
|
||||
raise RuntimeError(
|
||||
'Partition to operate on should be defined using --partition-name OR \
|
||||
partition-type,--partition-subtype OR partition-boot-default'
|
||||
)
|
||||
|
||||
# Prepare the device to perform operation on
|
||||
target_args = {}
|
||||
|
||||
if args.port:
|
||||
target_args['port'] = args.port
|
||||
|
||||
if args.baud:
|
||||
target_args['baud'] = args.baud
|
||||
|
||||
if args.partition_table_file:
|
||||
target_args['partition_table_file'] = args.partition_table_file
|
||||
|
||||
if args.partition_table_offset:
|
||||
target_args['partition_table_offset'] = int(args.partition_table_offset, 0)
|
||||
|
||||
if args.primary_bootloader_offset:
|
||||
target_args['primary_bootloader_offset'] = int(args.primary_bootloader_offset, 0)
|
||||
|
||||
if args.recovery_bootloader_offset:
|
||||
target_args['recovery_bootloader_offset'] = int(args.recovery_bootloader_offset, 0)
|
||||
|
||||
if args.esptool_args:
|
||||
target_args['esptool_args'] = args.esptool_args
|
||||
|
||||
if args.esptool_write_args:
|
||||
target_args['esptool_write_args'] = args.esptool_write_args
|
||||
|
||||
if args.esptool_read_args:
|
||||
target_args['esptool_read_args'] = args.esptool_read_args
|
||||
|
||||
if args.esptool_erase_args:
|
||||
target_args['esptool_erase_args'] = args.esptool_erase_args
|
||||
|
||||
if args.extra_partition_subtypes:
|
||||
gen.add_extra_subtypes(args.extra_partition_subtypes)
|
||||
|
||||
target = ParttoolTarget(**target_args)
|
||||
|
||||
# Create the operation table and execute the operation
|
||||
common_args = {'target': target, 'partition_id': partition_id}
|
||||
parttool_ops = {
|
||||
'erase_partition': (_erase_partition, []),
|
||||
'read_partition': (_read_partition, ['output']),
|
||||
@@ -385,13 +325,12 @@ def main():
|
||||
'get_partition_info': (_get_partition_info, ['info']),
|
||||
}
|
||||
|
||||
(op, op_args) = parttool_ops[args.operation]
|
||||
|
||||
for op_arg in op_args:
|
||||
common_args.update({op_arg: vars(args)[op_arg]})
|
||||
op, op_arg_names = parttool_ops[operation]
|
||||
common_args = {'target': target, 'partition_id': partition_id}
|
||||
for op_arg in op_arg_names:
|
||||
common_args[op_arg] = op_kwargs[op_arg]
|
||||
|
||||
if quiet:
|
||||
# If exceptions occur, suppress and exit quietly
|
||||
try:
|
||||
op(**common_args)
|
||||
except Exception:
|
||||
@@ -400,9 +339,196 @@ def main():
|
||||
try:
|
||||
op(**common_args)
|
||||
except gen.InputError as e:
|
||||
print(e, file=sys.stderr)
|
||||
log.print(str(e), file=sys.stderr)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
@click.group(
|
||||
cls=EspRichGroup,
|
||||
context_settings={'help_option_names': ['-h', '--help']},
|
||||
help='ESP-IDF Partitions Tool',
|
||||
)
|
||||
@click.option('--quiet', '-q', is_flag=True, help='suppress stderr messages')
|
||||
@click.option(
|
||||
'--esptool-args',
|
||||
cls=OptionEatAll,
|
||||
type=_ESPTOOL_ARGS_TYPE,
|
||||
default=(),
|
||||
help='additional main arguments for esptool',
|
||||
)
|
||||
@click.option(
|
||||
'--esptool-write-args',
|
||||
cls=OptionEatAll,
|
||||
type=_ESPTOOL_ARGS_TYPE,
|
||||
default=(),
|
||||
help='additional subcommand arguments when writing to flash',
|
||||
)
|
||||
@click.option(
|
||||
'--esptool-read-args',
|
||||
cls=OptionEatAll,
|
||||
type=_ESPTOOL_ARGS_TYPE,
|
||||
default=(),
|
||||
help='additional subcommand arguments when reading flash',
|
||||
)
|
||||
@click.option(
|
||||
'--esptool-erase-args',
|
||||
cls=OptionEatAll,
|
||||
type=_ESPTOOL_ARGS_TYPE,
|
||||
default=(),
|
||||
help='additional subcommand arguments when erasing regions of flash',
|
||||
)
|
||||
@click.option(
|
||||
'--port',
|
||||
'-p',
|
||||
help='port where the target device of the command is connected to; the partition table is sourced from '
|
||||
'this device when the partition table file is not defined',
|
||||
)
|
||||
@click.option('--baud', '-b', type=int, help='baudrate to use')
|
||||
@click.option('--partition-table-offset', '-o', type=AnyIntType(), help='offset to read the partition table from')
|
||||
@click.option('--primary-bootloader-offset', type=AnyIntType(), help='offset for primary bootloader')
|
||||
@click.option('--recovery-bootloader-offset', type=AnyIntType(), help='offset for recovery bootloader')
|
||||
@click.option(
|
||||
'--partition-table-file',
|
||||
'-f',
|
||||
type=click.Path(),
|
||||
help='file (CSV/binary) to read the partition table from; '
|
||||
'overrides device attached to specified port as the partition table source when defined',
|
||||
)
|
||||
@click.pass_context
|
||||
def cli(
|
||||
ctx,
|
||||
quiet,
|
||||
esptool_args,
|
||||
esptool_write_args,
|
||||
esptool_read_args,
|
||||
esptool_erase_args,
|
||||
port,
|
||||
baud,
|
||||
partition_table_offset,
|
||||
primary_bootloader_offset,
|
||||
recovery_bootloader_offset,
|
||||
partition_table_file,
|
||||
):
|
||||
ctx.ensure_object(dict)
|
||||
ctx.obj.update(
|
||||
{
|
||||
'quiet': quiet,
|
||||
'esptool_args': esptool_args,
|
||||
'esptool_write_args': esptool_write_args,
|
||||
'esptool_read_args': esptool_read_args,
|
||||
'esptool_erase_args': esptool_erase_args,
|
||||
'port': port,
|
||||
'baud': baud,
|
||||
'partition_table_offset': partition_table_offset,
|
||||
'primary_bootloader_offset': primary_bootloader_offset,
|
||||
'recovery_bootloader_offset': recovery_bootloader_offset,
|
||||
'partition_table_file': partition_table_file,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _shared_subcommand_options(func):
|
||||
func = click.pass_context(func)
|
||||
return func
|
||||
|
||||
|
||||
@cli.command('read_partition', help='read partition from device and dump contents into a file')
|
||||
@_partition_selection_options
|
||||
@click.option('--output', help='file to dump the read partition contents to')
|
||||
@_shared_subcommand_options
|
||||
def read_partition_cmd(
|
||||
ctx,
|
||||
partition_name,
|
||||
partition_type,
|
||||
partition_boot_default,
|
||||
partition_subtype,
|
||||
extra_partition_subtypes,
|
||||
output,
|
||||
):
|
||||
partition_id = _build_partition_id(partition_name, partition_type, partition_subtype, partition_boot_default)
|
||||
target = ParttoolTarget(**_target_kwargs_from_ctx(ctx.obj, extra_partition_subtypes))
|
||||
_run_operation('read_partition', ctx.obj['quiet'], target, partition_id, output=output)
|
||||
|
||||
|
||||
@cli.command('write_partition', help='write contents of a binary file to partition on device')
|
||||
@_partition_selection_options
|
||||
@click.option('--input', 'input_file', help='file whose contents are to be written to the partition offset')
|
||||
@click.option('--ignore-readonly', is_flag=True, help='Ignore read-only attribute')
|
||||
@_shared_subcommand_options
|
||||
def write_partition_cmd(
|
||||
ctx,
|
||||
partition_name,
|
||||
partition_type,
|
||||
partition_boot_default,
|
||||
partition_subtype,
|
||||
extra_partition_subtypes,
|
||||
input_file,
|
||||
ignore_readonly,
|
||||
):
|
||||
partition_id = _build_partition_id(partition_name, partition_type, partition_subtype, partition_boot_default)
|
||||
target = ParttoolTarget(**_target_kwargs_from_ctx(ctx.obj, extra_partition_subtypes))
|
||||
_run_operation(
|
||||
'write_partition',
|
||||
ctx.obj['quiet'],
|
||||
target,
|
||||
partition_id,
|
||||
input=input_file,
|
||||
ignore_readonly=ignore_readonly,
|
||||
)
|
||||
|
||||
|
||||
@cli.command('erase_partition', help='erase the contents of a partition on the device')
|
||||
@_partition_selection_options
|
||||
@_shared_subcommand_options
|
||||
def erase_partition_cmd(
|
||||
ctx,
|
||||
partition_name,
|
||||
partition_type,
|
||||
partition_boot_default,
|
||||
partition_subtype,
|
||||
extra_partition_subtypes,
|
||||
):
|
||||
partition_id = _build_partition_id(partition_name, partition_type, partition_subtype, partition_boot_default)
|
||||
target = ParttoolTarget(**_target_kwargs_from_ctx(ctx.obj, extra_partition_subtypes))
|
||||
_run_operation('erase_partition', ctx.obj['quiet'], target, partition_id)
|
||||
|
||||
|
||||
@cli.command('get_partition_info', help='get partition information')
|
||||
@_partition_selection_options
|
||||
@click.option(
|
||||
'--info',
|
||||
type=click.Choice(['name', 'type', 'subtype', 'offset', 'size', 'encrypted', 'readonly']),
|
||||
multiple=True,
|
||||
cls=OptionEatAll,
|
||||
default=['offset', 'size'],
|
||||
show_default=True,
|
||||
help='type of partition information to get',
|
||||
)
|
||||
@click.option('--part_list', 'part_list', is_flag=True, help='Get a list of partitions suitable for a given type')
|
||||
@_shared_subcommand_options
|
||||
def get_partition_info_cmd(
|
||||
ctx,
|
||||
partition_name,
|
||||
partition_type,
|
||||
partition_boot_default,
|
||||
partition_subtype,
|
||||
extra_partition_subtypes,
|
||||
info,
|
||||
part_list,
|
||||
):
|
||||
partition_id = _build_partition_id(
|
||||
partition_name, partition_type, partition_subtype, partition_boot_default, part_list=part_list
|
||||
)
|
||||
target = ParttoolTarget(**_target_kwargs_from_ctx(ctx.obj, extra_partition_subtypes))
|
||||
_run_operation('get_partition_info', ctx.obj['quiet'], target, partition_id, info=info)
|
||||
|
||||
|
||||
def main():
|
||||
cli()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from esp_pylib.excepthook import install_exception_reporting
|
||||
|
||||
install_exception_reporting()
|
||||
main()
|
||||
|
||||
Reference in New Issue
Block a user