#!/usr/bin/python3 import logging import sys import json import argparse import os import hashlib from shutil import copyfile from datetime import datetime logger = logging.getLogger() handler = logging.StreamHandler(stream=sys.stderr) formatter = logging.Formatter( '%(asctime)s %(name)-12s %(levelname)-8s %(message)s') handler.setFormatter(formatter) logger.addHandler(handler) logger.setLevel(logging.DEBUG) parser = argparse.ArgumentParser( prog="mkrelease", description="Automatically package a firmware release." ) parser.add_argument('-c', '--config', dest="config_location", action="store", help="The location of the config file.", default="./release-config.json") parser.add_argument('version', help="Version you wish to release. Use https://semver.org") parser.add_argument('--verbose', help="Enable verbose logging output.", action="store_true", default=False) def build_version_string(gsd_num : str, out_name:str, version_number:str, release_type:str, file_extension:str): return f'GSD-{gsd_num}-{out_name}-{version_number}-{release_type}.{file_extension}' def md5_hash_of(file): with open(file, 'rb') as f: file_hash = hashlib.md5() while chunk := f.read(8192): file_hash.update(chunk) return file_hash.hexdigest() if __name__ == "__main__": args = parser.parse_args() if not args.verbose: logger.disabled = True logger.debug("Attempting to read config file...") config = None try: config = json.load(open(args.config_location)) except Exception as e: logger.error(args.config_location + ": " + str(e)) assert config is not None logger.debug("Config file read.") release_dir = f'./{config["releases_directory"]}/{args.version}/' print("The following releases will be created:") assert config["releases"] is not None missingfiles = [] binaries_manifest = [] print(release_dir) for release in config["releases"]: for ext in release["copy_extensions"]: build_file_path = f'./{release["build_directory"]}/{release["build_file_name"]}.{ext}' output_file_path = build_version_string(config["gsd"] , config["product_name"] , args.version , release["release_name"] , ext) try: os.stat(build_file_path) except Exception as e: logging.error(f"Could not stat {build_file_path}:") logging.error(e) missingfiles.append(build_file_path) print('\t'+ output_file_path + f'\t({build_file_path})') binaries_manifest.append((build_file_path, release_dir + output_file_path)) if len(missingfiles) > 0: logger.error("Exiting due to missing source files.") print("The following files were not able to be found:") [print(f"\t{path}") for path in missingfiles] exit(1) print("\nThe following additional files will be created:") print(f'./{config["releases_directory"]}/{args.version}/') print("\trelease_notes.txt") if config["changelog"] is not None: logger.debug("Detected changelog field. Will also create changelog.") if not os.path.exists(config["changelog"]): logger.warning("Config file does not exist.") print("The changelog file you provided does not exist. Check your configuration file.") elif os.path.isfile(config["changelog"]): print("\tchangelog.md (from file)") elif os.path.isdir(config["changelog"]): print("\tchangelog.md (from directory of files)") print("") accept_words = ["y", "yes", ""] deny_words = ["n", "no"] while True: print("Do you wish to proceed? [Y/n]: ", end="") result = input() if result.lower() in deny_words: print("Aborted.") exit(1) elif result.lower() not in accept_words: print("Don't understand what you mean.") else: print("OK, executing file operations.") break logger.debug("Ensuring output directories exist.") os.makedirs(release_dir, exist_ok=True) for (source,destination) in binaries_manifest: copyfile(source, destination) logger.debug(f'Copied {source} to {destination}') logger.debug(f'Generating changelog') if os.path.isdir(config["changelog"]): with open(release_dir + "changelog.md", mode='+w') as out_log: for f in reversed(os.listdir(config["changelog"])): if os.path.isfile(f'{config["changelog"]}/{f}'): with open(f'{config["changelog"]}/{f}') as fi: for line in fi: out_log.write(line) out_log.write("\n") else: copyfile(config["changelog"], release_dir + "/changelog.md") logger.debug(f'Copied {config["changelog"]} to {release_dir}/changelog.txt') logger.debug("Generating release_notes.txt") with open(f'{release_dir}/release_notes.txt', mode="+w") as release_notes: release_notes.write(f'Generated using mkrelease at {datetime.now().strftime("%Y/%m/%d %H:%M:%S")} by user {os.getlogin()}\n') release_notes.write("Output file manifest (name, md5 hash):\n") for (_, file) in binaries_manifest: release_notes.write(f'{file} {md5_hash_of(file)}\n')