diff --git a/gp2040ce_bintools/builder.py b/gp2040ce_bintools/builder.py index 1ae45b0..bee2a2e 100644 --- a/gp2040ce_bintools/builder.py +++ b/gp2040ce_bintools/builder.py @@ -6,6 +6,7 @@ SPDX-License-Identifier: GPL-3.0-or-later import argparse import copy import logging +import os import re from typing import Optional @@ -55,13 +56,14 @@ def combine_firmware_and_config(firmware_binary: bytearray, board_config_binary: return combined -def concatenate_firmware_and_storage_files(firmware_filename: str, +def concatenate_firmware_and_storage_files(firmware_filename: str, # noqa: C901 binary_board_config_filename: Optional[str] = None, json_board_config_filename: Optional[str] = None, binary_user_config_filename: Optional[str] = None, json_user_config_filename: Optional[str] = None, combined_filename: str = '', usb: bool = False, - replace_extra: bool = False) -> None: + replace_extra: bool = False, + backup: bool = False) -> None: """Open the provided binary files and combine them into one combined GP2040-CE with config file. Args: @@ -72,6 +74,7 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, json_user_config_filename: filename of the user config section to read, in JSON format combined_filename: filename of where to write the combine binary replace_extra: if larger than normal firmware files should have their overage replaced + backup: if the output filename exists, move it to foo.ext.old before writing foo.ext """ new_binary = bytearray([]) board_config_binary = bytearray([]) @@ -112,6 +115,8 @@ def concatenate_firmware_and_storage_files(firmware_filename: str, new_binary = storage.convert_binary_to_uf2(binary_list) if combined_filename: + if backup and os.path.exists(combined_filename): + os.rename(combined_filename, f'{combined_filename}.old') with open(combined_filename, 'wb') as combined: combined.write(new_binary) @@ -304,6 +309,8 @@ def concatenate(): output_group = parser.add_mutually_exclusive_group(required=True) output_group.add_argument('--usb', action='store_true', help="write the resulting firmware + storage to USB") output_group.add_argument('--new-filename', help="output .bin or .uf2 file of the resulting firmware + storage") + parser.add_argument('--backup', action='store_true', default=False, + help="if the output file exists, move it to .old before writing") args, _ = parser.parse_known_args() concatenate_firmware_and_storage_files(args.firmware_filename, @@ -312,7 +319,7 @@ def concatenate(): binary_user_config_filename=args.binary_user_config_filename, json_user_config_filename=args.json_user_config_filename, combined_filename=args.new_filename, usb=args.usb, - replace_extra=args.replace_extra) + replace_extra=args.replace_extra, backup=args.backup) def dump_gp2040ce(): diff --git a/tests/test_builder.py b/tests/test_builder.py index d55d100..1857be3 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -137,6 +137,31 @@ def test_concatenate_to_uf2_board_only(tmp_path, firmware_binary, config_binary) math.ceil(STORAGE_SIZE/256) * 512) +def test_concatenate_with_backup(tmp_path, firmware_binary, config_binary): + """Test that we write a UF2 file as expected.""" + tmp_file = os.path.join(tmp_path, 'concat.uf2') + firmware_file = os.path.join(HERE, 'test-files', 'test-firmware.bin') + config_file = os.path.join(HERE, 'test-files', 'test-config.bin') + # create the file we are going to try to overwrite and want backed up + builder.concatenate_firmware_and_storage_files(firmware_file, binary_board_config_filename=config_file, + combined_filename=tmp_file) + # second file, expecting an overwrite of the target with a backup made + builder.concatenate_firmware_and_storage_files(firmware_file, binary_board_config_filename=config_file, + binary_user_config_filename=config_file, + combined_filename=tmp_file, + backup=True) + # size of the file should be 2x the padded firmware + 2x the board config space + 2x the user config space + with open(tmp_file, 'rb') as file: + content = file.read() + assert len(content) == (math.ceil(len(firmware_binary)/256) * 512 + + math.ceil(STORAGE_SIZE/256) * 512 * 2) + # size of the backup file should be 2x the padded firmware + 2x the board config space + with open(f'{tmp_file}.old', 'rb') as file: + content = file.read() + assert len(content) == (math.ceil(len(firmware_binary)/256) * 512 + + math.ceil(STORAGE_SIZE/256) * 512) + + def test_find_version_string(firmware_binary): """Test that we can find a version string in a binary.""" assert builder.find_version_string_in_binary(firmware_binary) == 'v0.7.5'