diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2018-02-14 17:49:35 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2018-03-01 17:19:38 -0800 |
commit | 00322c0aeeba377e293dfc5c180c65ab587c9bef (patch) | |
tree | 9b9bb4926f0a3a043481b8c61c8c3174fddaea0a | |
parent | 2e9cab9cfaff356427f77156350d2657216576bf (diff) | |
download | uhd-00322c0aeeba377e293dfc5c180c65ab587c9bef.tar.gz uhd-00322c0aeeba377e293dfc5c180c65ab587c9bef.tar.bz2 uhd-00322c0aeeba377e293dfc5c180c65ab587c9bef.zip |
utils: package_images: create image packages
-package_images.py looks at files in the current directory, and builds
all image packages that it can from the available files.
-image_package_mapping.py defines a nested dictionary that maps
targets (not tied to any other target definition) to the archive name
and constituent files.
-users can provide a manifest file with the --manifest option. If
provided, the new repository and githash, as well as the new SHA256
sums calculated during the packaging process, are added to the
manifest file.
-rw-r--r-- | images/image_package_mapping.py | 216 | ||||
-rw-r--r-- | images/package_images.py | 287 |
2 files changed, 503 insertions, 0 deletions
diff --git a/images/image_package_mapping.py b/images/image_package_mapping.py new file mode 100644 index 000000000..5a1137980 --- /dev/null +++ b/images/image_package_mapping.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Container for the list of image package targets, and the information about them +""" +PACKAGE_MAPPING = { + "e310": { + "type": "e3xx", + "package_name": "e3xx_e310_fpga_default.zip", + "files": ["usrp_e310_fpga.bin", + "usrp_e310_fpga_sg3.bin", + "usrp_e3xx_fpga_idle.bin", + "usrp_e3xx_fpga_idle_sg3.bin", + "usrp_e310_fpga.bit", + "usrp_e310_fpga_sg3.bit", + "usrp_e3xx_fpga_idle.bit", + "usrp_e3xx_fpga_idle_sg3.bit", + "usrp_e310_fpga.rpt", + "usrp_e310_fpga_sg3.rpt", + "usrp_e3xx_fpga_idle.rpt", + "usrp_e3xx_fpga_idle_sg3.rpt"] + }, + "x300": { + "type": "x3xx", + "package_name": "x3xx_x300_fpga_default.zip", + "files": ["usrp_x300_fpga_HG.bin", + "usrp_x300_fpga_HG.lvbitx", + "usrp_x300_fpga_XG.bin", + "usrp_x300_fpga_XG.lvbitx", + "usrp_x300_fpga_HG.bit", + "usrp_x300_fpga_HG.rpt", + "usrp_x300_fpga_XG.bit", + "usrp_x300_fpga_XG.rpt"] + }, + "x310": { + "type": "x3xx", + "package_name": "x3xx_x310_fpga_default.zip", + "files": ["usrp_x310_fpga_HG.bin", + "usrp_x310_fpga_HG.lvbitx", + "usrp_x310_fpga_XG.bin", + "usrp_x310_fpga_XG.lvbitx", + "usrp_x310_fpga_HG.bit", + "usrp_x310_fpga_HG.rpt", + "usrp_x310_fpga_XG.bit", + "usrp_x310_fpga_XG.rpt"] + }, + "n310": { + "type": "n3xx", + "package_name": "n3xx_n310_fpga_default.zip", + "files": ['usrp_n310_fpga_HG.bit', + 'usrp_n310_fpga_HG.bit.md5', + 'usrp_n310_fpga_HG.dts', + 'usrp_n310_fpga_HG.dts.md5', + 'usrp_n310_fpga_HG.rpt', + 'usrp_n310_fpga_XG.bit', + 'usrp_n310_fpga_XG.bit.md5', + 'usrp_n310_fpga_XG.dts', + 'usrp_n310_fpga_XG.dts.md5', + 'usrp_n310_fpga_XG.rpt'] + }, + "n300": { + "type": "n3xx", + "package_name": "n3xx_n300_fpga_default.zip", + "files": ['usrp_n300_fpga_HG.bit', + 'usrp_n300_fpga_HG.bit.md5', + 'usrp_n300_fpga_HG.dts', + 'usrp_n300_fpga_HG.dts.md5', + 'usrp_n300_fpga_HG.rpt', + 'usrp_n300_fpga_XG.bit', + 'usrp_n300_fpga_XG.bit.md5', + 'usrp_n300_fpga_XG.dts', + 'usrp_n300_fpga_XG.dts.md5', + 'usrp_n300_fpga_XG.rpt'] + }, + "n310_aa": { + "type": "n3xx", + "package_name": "n3xx_n310_fpga_aurora.zip", + "files": ['usrp_n310_fpga_AA.bit', + 'usrp_n310_fpga_AA.bit.md5', + 'usrp_n310_fpga_AA.dts', + 'usrp_n310_fpga_AA.dts.md5', + 'usrp_n310_fpga_AA.rpt', + 'usrp_n310_fpga_HA.bit', + 'usrp_n310_fpga_HA.bit.md5', + 'usrp_n310_fpga_HA.dts', + 'usrp_n310_fpga_HA.dts.md5', + 'usrp_n310_fpga_HA.rpt', + 'usrp_n310_fpga_XA.bit', + 'usrp_n310_fpga_XA.bit.md5', + 'usrp_n310_fpga_XA.dts', + 'usrp_n310_fpga_XA.dts.md5', + 'usrp_n310_fpga_XA.rpt'] + }, + "n310_cpld": { + "type": "n3xx", + "package_name": "n3xx_n310_cpld_default.zip", + "files": ['usrp_n310_mg_cpld.svf'] + }, + 'n200': { + 'type': 'usrp2', + 'package_name': 'usrp2_n200_fpga_default.zip', + 'files': ["usrp_n200_r2_fpga.bin", + "usrp_n200_r3_fpga.bin", + "usrp_n200_r4_fpga.bin", + "bit/usrp_n200_r3_fpga.bit", + "bit/usrp_n200_r4_fpga.bit"], + }, + 'n210': { + 'type': 'usrp2', + 'package_name': 'usrp2_n210_fpga_default.zip', + 'files': ["usrp_n210_r2_fpga.bin", + "usrp_n210_r3_fpga.bin", + "usrp_n210_r4_fpga.bin", + "bit/usrp_n210_r3_fpga.bit", + "bit/usrp_n210_r4_fpga.bit"], + }, + 'n200_fw': { + 'type': 'usrp2', + 'package_name': 'usrp2_n200_fw_default.zip', + 'files': ["usrp_n200_fw.bin"], + }, + 'n210_fw': { + 'type': 'usrp2', + 'package_name': 'usrp2_n210_fw_default.zip', + 'files': ["usrp_n210_fw.bin"], + }, + 'usrp2': { + 'type': 'usrp2', + 'package_name': 'usrp2_usrp2_fpga_default.zip', + 'files': ["usrp2_fpga.bin"], + }, + 'usrp2_fw': { + 'type': 'usrp2', + 'package_name': 'usrp2_usrp2_fw_default.zip', + 'files': ["usrp2_fw.bin"], + }, + 'b200': { + 'type': 'b2xx', + 'package_name': 'b2xx_b200_fpga_default.zip', + 'files': ["usrp_b200_fpga.bin"], + }, + 'b200mini': { + 'type': 'b2xx', + 'package_name': 'b2xx_b200mini_fpga_default.zip', + 'files': ["usrp_b200mini_fpga.bin"], + }, + 'b205mini': { + 'type': 'b2xx', + 'package_name': 'b2xx_b205mini_fpga_default.zip', + 'files': ["usrp_b205mini_fpga.bin"], + }, + 'b210': { + 'type': 'b2xx', + 'package_name': 'b2xx_b210_fpga_default.zip', + 'files': ["usrp_b210_fpga.bin"], + }, + 'b2xx_fw': { + 'type': 'b2xx', + 'package_name': 'b2xx_common_fw_default.zip', + 'files': ["usrp_b200_fw.hex"], + }, + 'n230': { + 'type': 'n230', + 'package_name': 'n230_n230_fpga_default.zip', + 'files': ["usrp_n230_fpga.bin", + "usrp_n230_fpga.bit", + "usrp_n230_fpga.rpt"], + }, + 'b100': { + 'type': 'usrp1', + 'package_name': 'usrp1_b100_fpga_default.zip', + 'files': ["usrp_b100_fpga_2rx.bin", + "usrp_b100_fpga.bin"], + }, + 'b100_fw': { + 'type': 'usrp1', + 'package_name': 'usrp1_b100_fw_default.zip', + 'files': ["usrp_b100_fw.ihx"], + }, + 'usrp1': { + 'type': 'usrp1', + 'package_name': 'usrp1_usrp1_fpga_default.zip', + 'files': ["usrp1_fpga_4rx.rbf", + "usrp1_fpga.rbf", + "usrp1_fw.ihx"], + }, + 'octoclock': { + 'type': 'octoclock', + 'package_name': 'octoclock_octoclock_fw_default.zip', + 'files': ["octoclock_bootloader.hex", + "octoclock_r4_fw.hex"], + }, + 'winusb_drv': { + 'type': 'usb', + 'package_name': 'usb_common_windrv_default.zip', + 'files': ["winusb_driver/", + "winusb_driver/erllc_uhd_b205mini.inf", + "winusb_driver/erllc_uhd_b100.inf", + "winusb_driver/erllc_uhd_b200_reinit.inf", + "winusb_driver/erllc_uhd_b200mini.inf", + "winusb_driver/erllc_uhd_b200.inf", + "winusb_driver/amd64/", + "winusb_driver/amd64/WdfCoInstaller01009.dll", + "winusb_driver/amd64/winusbcoinstaller2.dll", + "winusb_driver/x86/", + "winusb_driver/x86/WdfCoInstaller01009.dll", + "winusb_driver/x86/winusbcoinstaller2.dll", + "winusb_driver/erllc_uhd_usrp1.inf", + "winusb_driver/erllc_uhd_makecat.cdf", + "winusb_driver/erllc_uhd.cat"], + }, +} diff --git a/images/package_images.py b/images/package_images.py new file mode 100644 index 000000000..cab51a913 --- /dev/null +++ b/images/package_images.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python3 +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +Package image files into image archive packages + +Provides functions for packaging image files into image packages. Generate the intermediate files +(like hash files), and create image archives from sets. +""" +from __future__ import print_function +import argparse +import copy +import glob +import hashlib +import itertools +import os +import re +import sys +import tempfile +import zipfile +from image_package_mapping import PACKAGE_MAPPING + + +def parse_args(): + """Setup argument parser and parse""" + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--md5', action="store_true", default=False, + help="Generate MD5 files") + parser.add_argument('--sha256', action="store_true", default=False, + help="Generate SHA256 files") + parser.add_argument('-f', '--files', type=str, default="", + help="Comma separate list of files") + parser.add_argument('-o', '--output', type=str, default="", + help="Output file to put the hashes in") + parser.add_argument('-m', '--manifest', type=str, default="", + help="Update the manifest file at this path with the new SHAs") + parser.add_argument('-t', '--targets', type=str, default="", + help="RegEx to select image sets from the manifest file.") + parser.add_argument('-g', '--githash', type=str, default="", + help="Git hash directory name (eg. fpga-abc1234)") + return parser.parse_args() + + +def gen_filelist(includes, excludes=None): + """ + Generates a list of files, first generating + :param includes: [list of] expression[s] to include + :param excludes: [list of] expression[s] to exclude + :return: flat list of filenames + """ + if isinstance(includes, str): + included = glob.glob(includes) + else: + included = list(itertools.chain(*[glob.iglob(filename) for filename in includes])) + + if excludes is None: + excluded = [] + elif isinstance(excludes, str): + excluded = glob.glob(excludes) + else: + excluded = list(itertools.chain(*[glob.iglob(filename) for filename in excludes])) + # Remove the excluded files from the include list + for filename in excluded: + if filename in included: + included.remove(filename) + return included + + +def gen_md5(files_list, hash_filename=""): + """Generate the .md5 files for all input files""" + hashes = {} + for filename in files_list: + # Read and hash the input file + with open(filename, 'rb') as img_file: + md5_sum = hashlib.md5() + md5_sum.update(img_file.read()) + # Write the hash to a *.md5 file + with open(filename + '.md5', 'w') as md5_file: + md5_hex = md5_sum.hexdigest() + newline = "{md5_hex} {filename}\n".format(filename=filename, md5_hex=md5_hex) + md5_file.write(newline) + # Also store it to write to a file of all the hashes + hashes[filename] = md5_hex + + # Write the MD5 hashes to file + with open(hash_filename, 'a') as hash_file: + for filename, md5_hex in hashes.items(): + newline = "{md5_hex} {filename}\n".format(filename=filename, md5_hex=md5_hex) + hash_file.write(newline) + + +def gen_sha256(files_list, hash_filename=None, manifest_fn="", repo_and_hash=""): + """Generate the SHA256 files for all input file""" + # Input checking + if hash_filename is None: + hash_filename = "hashes.txt" + print("Generating SHA256 sums for:\n{}".format( + "\n".join("--{}".format(sha_fn) for sha_fn in files_list))) + + # Make a dictionary to store the new SHA256 sums + sha256_dict = {} + with open(hash_filename, 'a') as hash_file: + for filename in files_list: + with open(filename, 'rb') as img_file: + sha256_sum = hashlib.sha256() + sha256_sum.update(img_file.read()) + sha256_str = sha256_sum.hexdigest() + newline = "{sha_hex} {filename}\n".format(filename=filename, + sha_hex=sha256_str) + hash_file.write(newline) + # Add the sha256 to the dictionary + basename = os.path.basename(filename) + sha256_dict[basename] = sha256_str + + # If there's a manifest file to edit, put the new information in + if os.path.isfile(manifest_fn): + edit_manifest(manifest_fn, repo_and_hash, sha256_dict) + + +def gen_zip(zip_filename, files_list): + """Generate the zip file for a set of images""" + try: + with zipfile.ZipFile(zip_filename, 'w', zipfile.ZIP_DEFLATED) as zip_file: + for filename in files_list: + zip_file.write(filename) + return True + except Exception as ex: + print("Caught exception in gen_zip: {}".format(ex)) + return False + + +def do_gen_package(pkg_target, install_dir=""): + """Generate the entire N3XX image package, from the start to the end""" + print("---Generating package for {}---".format(pkg_target)) + filelist = PACKAGE_MAPPING[pkg_target]['files'] + print("Required files:\n{}".format( + "\n".join("--{}".format(img_fn) for img_fn in filelist))) + md5_files = gen_filelist(includes=filelist, excludes=["*.rpt", "*.md5"]) + print("Files to md5sum:\n{}".format( + "\n".join("--{}".format(md5_fn) for md5_fn in md5_files))) + gen_md5(md5_files, "md5_hashes.txt") + + zip_files = gen_filelist(includes=filelist) + zip_filename = os.path.join(install_dir, PACKAGE_MAPPING[pkg_target]['package_name']) + print("Files to zip:\n{}".format( + "\n".join("--{}".format(zip_fn) for zip_fn in zip_files))) + if not gen_zip(zip_filename, zip_files): + zip_filename = "" + return zip_filename + + +def gen_package(pkg_targets=(), repo_and_hash="", manifest_fn=""): + """Generate the entire image package, and place it in the proper directory structure""" + # Make the cache/ directory if necessary + cache_path = os.path.join(os.getcwd(), "cache") + if not os.path.isdir(cache_path): + os.mkdir(cache_path) + + sha_filenames = [] + for pkg_target in pkg_targets: + if pkg_target in PACKAGE_MAPPING: + # Make the type directory + pkg_type = PACKAGE_MAPPING[pkg_target]["type"] + type_path = os.path.join(cache_path, pkg_type) + if not os.path.isdir(type_path): + os.mkdir(type_path) + # Make the 'repository-hash' directory + if not repo_and_hash: + repo_and_hash = "repo-githash" + git_path = os.path.join(type_path, repo_and_hash) + if not os.path.isdir(git_path): + os.mkdir(git_path) + + # Generate the package and add the the zip filename to the SHA list + sha_filenames.append(do_gen_package(pkg_target, install_dir=git_path)) + else: + print("Error: Specify a supported type from {}".format( + list(PACKAGE_MAPPING.keys()))) + sha_filenames[:] = [sha_fn for sha_fn in sha_filenames if os.path.exists(sha_fn)] + gen_sha256(sha_filenames, hash_filename="hashes.txt", + manifest_fn=manifest_fn, repo_and_hash=repo_and_hash) + + +def list_differences(list1, list2): + """Returns two lists containing the unique elements of each input list""" + outlist1 = [] + outlist2 = [] + outlist1[:] = [elem for elem in list1 if elem not in list2] + outlist2[:] = [elem for elem in list2 if elem not in list1] + return outlist1, outlist2 + + +def verify_package(zip_filename, pkg_target): + """Verify the contents of the image package match the expected list of files""" + expected_filelist = PACKAGE_MAPPING[pkg_target]['files'] + with zipfile.ZipFile(zip_filename, 'r') as zip_file: + actual_filelist = zip_file.namelist() + + missing, extra = list_differences(expected_filelist, actual_filelist) + if missing or extra: + print("Error: image package does not include expected files") + if missing: + print("Missing files: {}".format(missing)) + if extra: + print("Extra files: {}".format(extra)) + return False + return True + + +def edit_manifest_line(line, new_repo_and_hash, new_hashes_dict): + """Edit the line in the manifest to (maybe) include the new repo, git hash, and SHA""" + # Check each value in your dictionary of new hashes + for filename, new_hash in new_hashes_dict.items(): + # If the filename with a new hash shows up in the line + if filename in line: + # Replace the repo and git hash + repo_and_hash = re.findall(r"[\w]+-[\da-fA-F]{7}", line) + if repo_and_hash: + repo_and_hash = repo_and_hash[0] + line = line.replace(repo_and_hash, new_repo_and_hash) + + # Replace the SHA256 + sha = re.findall(r"[\da-fA-F]{64}", line) + if sha: + sha = sha[0] + line = line.replace(sha, new_hash) + + if not repo_and_hash or not sha: + print("Error: repo, hash or SHA missing in line with new file") + print("Line: {}", format(line)) + # If we found and replaced info, return the edited line + return line + # If we never edit the line, just return it + return line + + +def edit_manifest(manifest_fn, new_repo_and_hash, new_hash_dict): + """Edit the provided manifest file to update the githash and SHA256""" + with tempfile.NamedTemporaryFile(mode='w', dir='.', delete=False) as tmp_manifest, \ + open(manifest_fn, 'r') as old_manifest: + # Check each line in the manifest file + for line in old_manifest: + # If needed, put the new info in the line + line = edit_manifest_line(line, new_repo_and_hash, new_hash_dict) + # Always write the line back + tmp_manifest.write(line) + # Replace the manifest file with our temporary file that we created + os.rename(tmp_manifest.name, manifest_fn) + + +def determine_targets(): + """ + Determine which image packages can be created by the files in the current directory + :return: list of valid targets + """ + found_targets = [] + for target, target_info in PACKAGE_MAPPING.items(): + # Grab the list of files required, but remove any files that we're going to build here, + # like the hash files + required_files = copy.deepcopy(target_info['files']) + required_files[:] = [filename for filename in required_files if '.md5' not in filename] + + if all([os.path.exists(img_file) for img_file in required_files]): + found_targets.append(target) + return found_targets + + +def main(): + """Generate image packages using commandline arguments""" + args = parse_args() + if args.md5 or args.sha256 or args.files or args.output: + print("Unsupported argument: only --pkg_targets is currently supported.") + if args.targets: + pkg_targets = [ss.strip() for ss in args.targets.split(',')] + else: + pkg_targets = determine_targets() + print("Targets to package:\n{}".format( + "\n".join("--{}".format(pkg) for pkg in pkg_targets))) + gen_package(pkg_targets=pkg_targets, repo_and_hash=args.githash, manifest_fn=args.manifest) + return True + + +if __name__ == "__main__": + sys.exit(main()) |