diff options
author | Patrik Teivonen <patrik.teivonen@qt.io> | 2022-08-22 12:03:59 +0300 |
---|---|---|
committer | Patrik Teivonen <patrik.teivonen@qt.io> | 2022-11-25 10:47:43 +0000 |
commit | 57ba1ab210280360c9769d02258ec060a67328cd (patch) | |
tree | f61aea4155e61d65e73c9ab166cd289dd5c412ae | |
parent | af10f08f8d9ccc3a68b58aa701cb820c581c27c7 (diff) |
Refactor create_installer.get_component_data
-Split up the get_component_data function
-Add doc strings, unit tests to new functions
-Add type annotations for QtInstallerTask
Change-Id: I54ad3f683818b29b5a7244fff2056647e3a79bbb
Reviewed-by: Iikka Eklund <iikka.eklund@qt.io>
-rw-r--r-- | packaging-tools/bldinstallercommon.py | 4 | ||||
-rw-r--r-- | packaging-tools/create_installer.py | 467 | ||||
-rwxr-xr-x | packaging-tools/release_repo_updater.py | 4 | ||||
-rw-r--r-- | packaging-tools/tests/test_create_installer.py | 38 |
4 files changed, 360 insertions, 153 deletions
diff --git a/packaging-tools/bldinstallercommon.py b/packaging-tools/bldinstallercommon.py index b02149280..081c448aa 100644 --- a/packaging-tools/bldinstallercommon.py +++ b/packaging-tools/bldinstallercommon.py @@ -249,12 +249,12 @@ def replace_in_files(filelist: List[str], regexp: str, replacement_string: str) ############################### # function ############################### -def safe_config_key_fetch(conf: ConfigParser, section: str, key: str) -> Any: +def safe_config_key_fetch(conf: ConfigParser, section: str, key: str) -> str: if not conf.has_section(section): return '' if not conf.has_option(section, key): return '' - return config_section_map(conf, section)[key] + return str(config_section_map(conf, section)[key]) ############################### diff --git a/packaging-tools/create_installer.py b/packaging-tools/create_installer.py index a2e1150fc..a3eff24d3 100644 --- a/packaging-tools/create_installer.py +++ b/packaging-tools/create_installer.py @@ -40,7 +40,7 @@ from dataclasses import dataclass, field from multiprocessing import cpu_count from pathlib import Path from time import gmtime, strftime -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Dict, Generator, Generic, List, Optional, Tuple, TypeVar from bld_utils import download, is_linux, is_macos, is_windows from bldinstallercommon import ( @@ -70,6 +70,8 @@ if is_windows(): log = init_logger(__name__, debug_mode=False) +QtInstallerTaskType = TypeVar("QtInstallerTaskType", bound="QtInstallerTask[Any]") + # ---------------------------------------------------------------------- TARGET_INSTALL_DIR_NAME_TAG = '%TARGET_INSTALL_DIR%' ARCHIVES_EXTRACT_DIR_NAME_TAG = '%ARCHIVES_EXTRACT_DIR%' @@ -98,7 +100,7 @@ def check_required_tools() -> None: ############################################################## # Cleanup ############################################################## -def clean_work_dirs(task: Any) -> None: +def clean_work_dirs(task: QtInstallerTaskType) -> None: """Clean working directories.""" log.info("Cleaning work environment") for item in [task.packages_full_path_dst, task.repo_output_dir, task.config_dir_dst]: @@ -110,7 +112,7 @@ def clean_work_dirs(task: Any) -> None: ############################################################## # Set the config directory ############################################################## -def set_config_directory(task: Any) -> None: +def set_config_directory(task: QtInstallerTaskType) -> None: """Copy config directory into correct place.""" log.info("Set config directory") config_dir_template = task.config.get('ConfigDir', 'template_name') @@ -126,7 +128,7 @@ def set_config_directory(task: Any) -> None: ############################################################## # Set the config.xml ############################################################## -def set_config_xml(task: Any) -> Any: +def set_config_xml(task: QtInstallerTaskType) -> Any: """Copy config.xml template into correct place.""" log.info("Set config.xml") @@ -160,8 +162,9 @@ def set_config_xml(task: Any) -> Any: ############################################################## # Substitute common version numbers etc., match against tags ############################################################## -def substitute_global_tags(task: Any) -> None: - """ Substitute common version numbers etc., match against tags """ +def substitute_global_tags(task: QtInstallerTaskType) -> None: + """Substitute common version numbers etc., match against tags """ + log.info("Substituting global tags:") log.info("%%PACKAGE_CREATION_DATE%% = %s", task.build_timestamp) log.info("%%VERSION_NUMBER_AUTO_INCREASE%% = %s", task.version_number_auto_increase_value) @@ -216,7 +219,9 @@ def substitute_component_tags(tag_pair_list: List[List[str]], meta_dir_dest: str ############################################################## # Parse SDK components ############################################################## -def parse_component_data(task: Any, configuration_file: str, configurations_base_path: str) -> None: +def parse_component_data( + task: QtInstallerTaskType, configuration_file: str, configurations_base_path: str +) -> None: """Parse SDK component data""" file_full_path = configuration_file if not os.path.isfile(file_full_path): @@ -232,10 +237,10 @@ def parse_component_data(task: Any, configuration_file: str, configurations_base configuration.read_file(cfgfile) # parse package ignore list first - sdk_component_exclude_list = safe_config_key_fetch(configuration, 'PackageIgnoreList', 'packages') + sdk_component_exclude_list: str = safe_config_key_fetch(configuration, 'PackageIgnoreList', 'packages') if sdk_component_exclude_list: sdk_component_exclude_list = sdk_component_exclude_list.replace(' ', '') - pkg_list = sdk_component_exclude_list.split(',') + pkg_list: List[str] = sdk_component_exclude_list.split(',') for item in pkg_list: task.sdk_component_ignore_list.append(item) # parse sdk components @@ -287,7 +292,7 @@ def parse_component_data(task: Any, configuration_file: str, configurations_base ############################################################## # Parse SDK components ############################################################## -def parse_components(task: Any) -> None: +def parse_components(task: QtInstallerTaskType) -> None: """Parse SDK all components""" log.info("Parse target configuration files") conf_base_path = task.configurations_dir + os.sep + task.platform_identifier + os.sep @@ -317,125 +322,293 @@ def create_metadata_map(sdk_component: IfwSdkComponent) -> List[List[str]]: return component_metadata_tag_pair_list -def get_component_sha1_file(sdk_component: IfwSdkComponent, sha1_file_dest: str) -> None: - """download component sha1 file""" - download(sdk_component.comp_sha1_uri, sha1_file_dest) +def read_component_sha(sdk_comp: IfwSdkComponent, sha1_file_path: Path) -> None: + """ + Read IfwSdkComponent's sha1 from the given file path and add it to the given component + + Args: + sdk_comp: An instance of IfwSdkComponent with the component_sha1 attribute to be updated + sha1_file_path: A file system path to a text file where to read the SHA1 from + + Raises: + CreateInstallerError: When SHA1 file doesn't exist, or SHA1 value is malformed + """ + if sha1_file_path.exists(): + with open(sha1_file_path, "r", encoding="utf-8") as sha1_file: + sdk_comp.component_sha1 = sha1_file.read().strip() + # Validate SHA1 hex characters and length + if re.fullmatch(r"\b[0-9a-f]{5,40}\b", sdk_comp.component_sha1) is None: + raise CreateInstallerError(f"Got invalid SHA1: '{sdk_comp.component_sha1}'") + else: + raise CreateInstallerError(f"Component SHA1 file not found: '{sha1_file_path}'") + +def get_component_sha1(sdk_comp: IfwSdkComponent, sha1_file_dest: Path) -> None: + """ + Download sha1 file from URI specified in the component and add it to the same component + + Args: + sdk_comp: An instance of IfwSdkComponent with the comp_sha1_uri URI to download from + sha1_file_dest: A file system path for the download target + + Raises: + Exception: When downloading or saving the file in download() function fails + CreateInstallerError: When the downloaded SHA cannot be read in read_component_sha() + """ + download(sdk_comp.comp_sha1_uri, str(sha1_file_dest)) # read sha1 from the file - with open(sha1_file_dest, "r", encoding="utf-8") as sha1_file: - sdk_component.component_sha1 = sha1_file.read().strip() + read_component_sha(sdk_comp=sdk_comp, sha1_file_path=sha1_file_dest) + + +def strip_dirs(iterations: int, install_dir: Path) -> None: + """ + Strip out unnecessary folder structure based on the configuration + + Args: + iterations: Specifies how many tree levels to remove (0=do nothing) + install_dir: A file system path to the folder to strip from + + Raises: + IOError: When there are too many tree levels to be removed based on the structure + """ + while iterations: + remove_one_tree_level(str(install_dir)) # TODO: use shutil.move here + iterations -= 1 + + +def delete_docs(directory: Path) -> None: + """ + Delete doc directory from directory if exists + + Args: + directory: A file system path to a directory to search from + + Raises: + PackagingError: When no directory with the name 'doc' found from path, handled with warning + """ + try: + doc_dir = locate_path(directory, ["doc"], filters=[os.path.isdir]) + log.info("Erasing doc: %s", doc_dir) + shutil.rmtree(doc_dir) + except PackagingError: + log.warning("Skipping option 'delete_doc_directory': Directory doesn't contain 'doc' dir") + + +def process_qml_examples(directory: Path) -> None: + """ + Find qml examples directory from directory if it exists. Remove other content. + + Args: + directory: A file system path to a directory to search from + + Raises: + PackagingError: When no 'examples' directory found from path, handled with warning + """ + try: + qml_examples_dir = locate_path(directory, ["examples"], filters=[os.path.isdir]) + qml_examples_only(examples_dir=qml_examples_dir) + except PackagingError: + log.warning("Skipping option 'qml_examples_only': The 'examples' directory does not exist") + + +def finalize_items(task: QtInstallerTaskType, items: str, install_dir: Path) -> None: + """ + Perform package finalization tasks for the given archive + + Args: + task: An instance of QtInstallerTask to get a build timestamp for 'set_licheck' + items: List of operations to perform as part of finalization + install_dir: A file system path for a folder containing the archive contents to patch + """ + if not items: + return + if "delete_doc_directory" in items: + delete_docs(directory=install_dir) + if "cleanup_doc_directory" in items: + cleanup_docs(install_dir=str(install_dir)) + if "qml_examples_only" in items: + process_qml_examples(directory=install_dir) + if "patch_qt" in items: + patch_files(str(install_dir), product="qt_framework") + if "set_executable" in items: + handle_set_executable(str(install_dir), items) + if "set_licheck" in items: + handle_set_licheck(task, str(install_dir), items) + + +def exec_action_script(archive_action: Tuple[Path, str], input_dir: Path) -> None: + """ + Perform a custom action script for the extracted archive. + An action script is a Python or a shell script that takes in at least the --input-dir argument. + Additional arguments can be specified alongside the script location. + + Args: + archive_action: Contains a path to the action script to execute, as well as its arguments + input_dir: A file system path to pass onto the action script via --input-dir option + + Raises: + CalledProcessError: When executing the script fails + """ + script_path, script_args = archive_action + cmd = [str(script_path), "--input-dir=" + str(input_dir), script_args.strip()] + if script_path.suffix == ".py": + cmd.insert(0, sys.executable) + run_cmd(cmd=cmd) + + +def process_debug_files_and_libs( + arch_name: str, + install_dir: Path, + pdb_files: bool = False, + debug_information_files: bool = False, + debug_libraries: bool = False, +) -> None: + """ + Remove debug information files and libraries when explicitly defined so + + Args: + arch_name: Name of the archive file, used for filtering debug information archives + install_dir: A file system path to the directory to remove from + pdb_files: Whether to remove Windows-only pdb files (Obsolete) + debug_information_files: Whether to remove platform-specific debug information files + debug_libraries: Whether to remove all the debug libraries + """ + if not pdb_files or not debug_information_files: + # don't remove debug information files from debug information archives + if not arch_name.endswith("debug-symbols.7z"): + # Check if debug information file types are defined + if pdb_files or debug_information_files: + # Remove debug information files according to host platform defaults + remove_all_debug_information_files(str(install_dir)) + if debug_libraries: + remove_all_debug_libraries(str(install_dir)) + + +def extract_component_data(source_archive: Path, target_directory: Path) -> None: + """ + Extract component payload to the specified directory and remove the old archive + + Args: + source_archive: A file system path to the archive to extract + target_directory: A file system path to the target directory to create and extract to + + Raises: + CreateInstallerError: When payload extraction is unsuccessful + """ + # make sure the target resolves to a path and parent directories exist + target_directory = target_directory.resolve(strict=True) + target_directory.mkdir(parents=True, exist_ok=True) + # extract contents, raise error on unsuccessful extraction + if not extract_file(str(source_archive), str(target_directory)): + raise CreateInstallerError(f"Could not extract '{source_archive}' to '{target_directory}'") + # remove the original archive after extraction complete + source_archive.unlink() + + +def patch_component_data( + task: QtInstallerTaskType, + archive: IfwPayloadItem, + install_dir: Path, +) -> None: + """ + Perform patching operations for component payload unpacked to install_dir. + + Args: + task: An instance of QtInstallerTask, required for finalize_items + archive: An instance of IfwPayloadItem, containing the payload attributes + payload: An install directory for the payload, required for patching + """ + if archive.archive_action: + exec_action_script(archive.archive_action, install_dir) + strip_dirs(archive.package_strip_dirs, install_dir) + finalize_items(task, archive.package_finalize_items, install_dir) + process_debug_files_and_libs( + archive.arch_name, + install_dir, + task.remove_pdb_files, + task.remove_debug_information_files, + task.remove_debug_libraries, + ) + if archive.rpath_target and is_linux(): + handle_component_rpath(str(install_dir), archive.rpath_target) + + +def recompress_component( + task: QtInstallerTaskType, archive: IfwPayloadItem, destination_dir: Path, compress_dir: Path +) -> None: + """ + Recompress the patched component to a .7z archive + + Args: + task: An instance of QtInstallerTask used for getting the archivegen tool + archive: An instance of IfwPayloadItem, containing the archive name + destination_dir: A directory for where to save the compressed archive + compress_dir: A directory containing the content to add to the archive + + Raises: + CalledProcessError: When running the archivegen command fails + """ + # change payload name to desired final archive format (.7z) + if not archive.arch_name.endswith(".7z"): + while Path(archive.arch_name).suffix in archive.supported_arch_formats: + archive.arch_name = Path(archive.arch_name).stem + archive.arch_name = Path(archive.arch_name + ".7z").name + # add compress_dir in front of every item to ensure correct install paths + content_list = [str(compress_dir / x) for x in os.listdir(compress_dir)] + saveas = Path(destination_dir, archive.arch_name) + run_cmd([task.archivegen_tool, "-f", ".7z", str(saveas)] + content_list, destination_dir) + if not saveas.exists(): + raise CreateInstallerError(f"Generated archive doesn't exist: {saveas}") def get_component_data( - task: Any, - sdk_component: IfwSdkComponent, + task: QtInstallerTaskType, + sdk_comp: IfwSdkComponent, archive: IfwPayloadItem, - install_dir: str, - data_dir_dest: str, - compress_content_dir: str, + install_dir: Path, + data_dir_dest: Path, + compress_dir: Path, ) -> None: - """Download and create data for a component""" + """ + Download and create data for a component's payload item including patching operations + + Args: + task: An instance of QtInstallerTask + sdk_comp: An instance of IfwSdkComponent, specifies the component that the data is part of + archive: An instance of IfwPayloadItem, specifies the payload item to process + install_dir: A directory resembling the final installation directory structure + data_dir_dest: A directory location for the final component data + compress_dir: A directory containing the items to compress to the final archive + """ # Continue if payload item has no data if not os.path.basename(archive.archive_uri): + log.info("[%s] Payload item has no data", archive.package_name) return # Download payload to data_dir_dest downloaded_file = Path(data_dir_dest, archive.arch_name) + log.info("[%s] Download: %s", archive.package_name, archive.arch_name) download(archive.archive_uri, str(downloaded_file)) # For non-archive payload, move to install_dir for packing if not archive.archive_uri.endswith(archive.supported_arch_formats): shutil.move(str(downloaded_file), install_dir) # For payload already in IFW compatible format, use the raw artifact and continue elif not archive.requires_extraction and archive.archive_uri.endswith(archive.ifw_arch_formats): + log.info("[%s] Use raw artifact: %s", archive.package_name, archive.arch_name) return # Extract payload archive if it requires to be patched or recompressed to a compatible format else: - if not extract_file(str(downloaded_file), install_dir): - # Raise error on unsuccessful extraction - raise CreateInstallerError(f"Couldn't extract archive: {downloaded_file}") - # Remove original archive after extraction complete - os.remove(downloaded_file) + log.info("[%s] Extract: %s", archive.package_name, archive.arch_name) + extract_component_data(downloaded_file, install_dir) # If patching items are specified, execute them here if archive.requires_patching: - # perform custom action script for the extracted archive - if archive.archive_action: - script_file, script_args = archive.archive_action - script_args = script_args or "" - script_path = Path(__file__).parent.resolve() / script_file - if not script_path.exists(): - raise CreateInstallerError(f"Custom archive action script missing: {script_path}") - cmd = [str(script_path), "--input-dir=" + install_dir, script_args.strip()] - if script_path.suffix == ".py": - cmd.insert(0, sys.executable) - run_cmd(cmd=cmd) - - # strip out unnecessary folder structure based on the configuration - count = 0 - iterations = int(archive.package_strip_dirs) - while count < iterations: - count = count + 1 - remove_one_tree_level(install_dir) - # perform package finalization tasks for the given archive - if 'delete_doc_directory' in archive.package_finalize_items: - try: - doc_dir = locate_path(install_dir, ["doc"], filters=[os.path.isdir]) - log.info("Erasing doc: %s", doc_dir) - shutil.rmtree(doc_dir) - except PackagingError: - pass - if 'cleanup_doc_directory' in archive.package_finalize_items: - cleanup_docs(install_dir) - if 'qml_examples_only' in archive.package_finalize_items: - try: - examples_dir = locate_path(install_dir, ["examples"], filters=[os.path.isdir]) - qml_examples_only(examples_dir) - except PackagingError: - pass - if 'patch_qt' in archive.package_finalize_items: - patch_files(install_dir, product="qt_framework") - if 'set_executable' in archive.package_finalize_items: - handle_set_executable(install_dir, archive.package_finalize_items) - if 'set_licheck' in archive.package_finalize_items: - handle_set_licheck(task, install_dir, archive.package_finalize_items) - - # remove debug information files when explicitly defined so - if not task.remove_pdb_files or not task.remove_debug_information_files: - # don't remove debug information files from debug information archives - if not archive.arch_name.endswith("debug-symbols.7z"): - # Check if debug information file types are defined - if task.remove_pdb_files or task.remove_debug_information_files: - # Remove debug information files according to host platform defaults - remove_all_debug_information_files(install_dir) - - # remove debug libraries - if task.remove_debug_libraries: - remove_all_debug_libraries(install_dir) - - if archive.rpath_target: - if not archive.rpath_target.startswith(os.sep): - archive.rpath_target = os.sep + archive.rpath_target - if is_linux(): - handle_component_rpath(install_dir, archive.rpath_target) - + log.info("[%s] Patch: %s", archive.package_name, archive.arch_name) + patch_component_data(task, archive, install_dir) + # Add SHA1 from payload to component if specified if archive.component_sha1: - # read sha1 from the file - sha1_file_path = install_dir + os.sep + archive.component_sha1 - if os.path.exists(sha1_file_path): - with open(sha1_file_path, "r", encoding="utf-8") as sha1_file: - sdk_component.component_sha1 = sha1_file.read().strip() - else: - raise CreateInstallerError( - f"Component SHA1 file '{archive.component_sha1}' not found" - ) - # Lastly, compress the component back to a 7z archive - if not archive.arch_name.endswith(".7z"): # Remove old archive suffix - while Path(archive.arch_name).suffix in archive.supported_arch_formats: - archive.arch_name = Path(archive.arch_name).stem - archive.arch_name = Path(archive.arch_name + ".7z").name - content_list = os.listdir(compress_content_dir) - # Add compress_content_dir in front of every item - content_list = [(compress_content_dir + os.sep + x) for x in content_list] - save_as = os.path.normpath(os.path.join(data_dir_dest, archive.arch_name)) - run_cmd(cmd=[task.archivegen_tool, save_as] + content_list, cwd=data_dir_dest) + read_component_sha(sdk_comp, install_dir / archive.component_sha1) + # Finally compress the content of the component to the final package + log.info("[%s] Compress: %s", archive.package_name, archive.arch_name) + recompress_component(task, archive, data_dir_dest, compress_dir) def handle_set_executable(base_dir: str, package_finalize_items: str) -> None: @@ -447,7 +620,9 @@ def handle_set_executable(base_dir: str, package_finalize_items: str) -> None: log.info("Executable bit set for: %s", expected_path) -def handle_set_licheck(task: Any, base_dir: str, package_finalize_items: str) -> None: +def handle_set_licheck( + task: QtInstallerTaskType, base_dir: str, package_finalize_items: str +) -> None: for licheck_file_name in parse_package_finalize_items(package_finalize_items, 'set_licheck'): licheck_file_path = os.path.join(base_dir, licheck_file_name) if not os.path.exists(licheck_file_path): @@ -468,7 +643,7 @@ def parse_package_finalize_items(package_finalize_items: str, item_category: str ############################################################## # Substitute pkg template directory names ############################################################## -def substitute_package_name(task: Any, package_name: str) -> str: +def substitute_package_name(task: QtInstallerTaskType, package_name: str) -> str: for key, value in task.substitutions.items(): package_name = package_name.replace(key, value) @@ -556,7 +731,7 @@ def remove_all_debug_libraries(install_dir: str) -> None: ############################################################## # Create target components ############################################################## -def create_target_components(task: Any) -> None: +def create_target_components(task: QtInstallerTaskType) -> None: """Create target components.""" Path(task.packages_full_path_dst).mkdir(parents=True, exist_ok=True) @@ -576,24 +751,24 @@ def create_target_components(task: Any) -> None: # substitute pkg_template dir names and package_name package_name = substitute_package_name(task, sdk_comp.ifw_sdk_comp_name) sdk_comp.ifw_sdk_comp_name = package_name - dest_base = task.packages_full_path_dst + os.sep + package_name + os.sep - meta_dir_dest = os.path.normpath(dest_base + 'meta') - data_dir_dest = os.path.normpath(dest_base + 'data') - temp_data_dir = os.path.normpath(dest_base + 'tmp') + dest_base = Path(task.packages_full_path_dst) / package_name + meta_dir_dest = dest_base / "meta" + data_dir_dest = dest_base / "data" + temp_data_dir = dest_base / "tmp" # save path for later substitute_component_tags call - sdk_comp.meta_dir_dest = Path(meta_dir_dest) + sdk_comp.meta_dir_dest = meta_dir_dest # create meta destination folder - sdk_comp.meta_dir_dest.mkdir(parents=True, exist_ok=True) + meta_dir_dest.mkdir(parents=True, exist_ok=True) # Copy Meta data metadata_content_source_root = os.path.join(sdk_comp.pkg_template_folder, "meta") - copy_tree(metadata_content_source_root, meta_dir_dest) + copy_tree(metadata_content_source_root, str(meta_dir_dest)) if os.path.isfile(os.path.join(task.script_root_dir, "lrelease")): # create translation binaries if translation source files exist for component update_script = os.path.join(task.script_root_dir, "update_component_translations.sh") lrelease_tool = os.path.join(task.script_root_dir, "lrelease") - run_cmd(cmd=[update_script, "-r", lrelease_tool, dest_base]) + run_cmd(cmd=[update_script, "-r", lrelease_tool, str(dest_base)]) # add files into tag substitution - task.directories_for_substitutions.append(meta_dir_dest) + task.directories_for_substitutions.append(str(meta_dir_dest)) # handle archives if sdk_comp.downloadable_archives: # save path for later substitute_component_tags call @@ -604,19 +779,15 @@ def create_target_components(task: Any) -> None: # for online installer just handle the metadata if task.offline_installer or task.create_repository: # Create needed data dirs - compress_content_dir = os.path.normpath( - temp_data_dir + os.sep + archive.arch_name - ) - install_dir = os.path.normpath( - compress_content_dir + archive.get_archive_install_dir() - ) + compress_dir = temp_data_dir / archive.arch_name + install_dir = compress_dir / archive.get_archive_install_dir().strip("/") # adding get_component_data task to our work queue # Create needed data dirs before the threads start to work - Path(install_dir).mkdir(parents=True, exist_ok=True) - Path(data_dir_dest).mkdir(parents=True, exist_ok=True) + install_dir.mkdir(parents=True, exist_ok=True) + data_dir_dest.mkdir(parents=True, exist_ok=True) if is_windows(): - install_dir = win32api.GetShortPathName(install_dir) - data_dir_dest = win32api.GetShortPathName(data_dir_dest) + install_dir = Path(win32api.GetShortPathName(str(install_dir))) + data_dir_dest = Path(win32api.GetShortPathName(str(data_dir_dest))) get_component_data_work.add_task( f"adding {archive.arch_name} to {sdk_comp.ifw_sdk_comp_name}", get_component_data, @@ -625,14 +796,14 @@ def create_target_components(task: Any) -> None: archive, install_dir, data_dir_dest, - compress_content_dir, + compress_dir, ) # handle component sha1 uri if sdk_comp.comp_sha1_uri: - sha1_file_dest = os.path.normpath(dest_base + 'SHA1') + sha1_file_dest = (dest_base / "SHA1") get_component_data_work.add_task( f"getting component sha1 file for {sdk_comp.ifw_sdk_comp_name}", - get_component_sha1_file, + get_component_sha1, sdk_comp, sha1_file_dest, ) @@ -640,22 +811,24 @@ def create_target_components(task: Any) -> None: # maybe there is some static data data_content_source_root = os.path.normpath(sdk_comp.pkg_template_folder + os.sep + "data") if os.path.exists(data_content_source_root): - Path(data_dir_dest).mkdir(parents=True, exist_ok=True) - copy_tree(data_content_source_root, data_dir_dest) + data_dir_dest.mkdir(parents=True, exist_ok=True) + copy_tree(data_content_source_root, str(data_dir_dest)) if not task.dry_run: # start the work threaded, more than 8 parallel downloads are not so useful get_component_data_work.run(min([task.max_cpu_count, cpu_count()])) - for sdk_component in task.sdk_component_list: + for sdk_comp in task.sdk_component_list: # substitute tags - substitute_component_tags(create_metadata_map(sdk_component), sdk_component.meta_dir_dest) - if sdk_component.temp_data_dir and os.path.exists(sdk_component.temp_data_dir): - # lastly remove temp dir after all data is prepared - if not remove_tree(str(sdk_component.temp_data_dir)): - raise CreateInstallerError(f"Unable to remove directory: {sdk_component.temp_data_dir}") - # substitute downloadable archive names in installscript.qs - substitute_component_tags(sdk_component.generate_downloadable_archive_list(), sdk_component.meta_dir_dest) + substitute_component_tags(create_metadata_map(sdk_comp), str(sdk_comp.meta_dir_dest)) + if sdk_comp.temp_data_dir is None or not sdk_comp.temp_data_dir.exists(): + continue + # lastly remove temp dir after all data is prepared + if not remove_tree(str(sdk_comp.temp_data_dir)): + raise CreateInstallerError(f"Unable to remove dir: {sdk_comp.temp_data_dir}") + # substitute downloadable archive names in installscript.qs + archive_list = sdk_comp.generate_downloadable_archive_list() + substitute_component_tags(archive_list, str(sdk_comp.meta_dir_dest)) ############################################################## @@ -727,7 +900,7 @@ def cleanup_docs(install_dir: str) -> None: ############################################################## # Create the final installer binary ############################################################## -def create_installer_binary(task: Any) -> None: +def create_installer_binary(task: QtInstallerTaskType) -> None: """Create installer binary files using binarycreator tool.""" log.info("Create installer binary") @@ -795,7 +968,7 @@ def create_installer_binary(task: Any) -> None: ############################################################## # Create the repository ############################################################## -def create_online_repository(task: Any) -> None: +def create_online_repository(task: QtInstallerTaskType) -> None: """Create online repository using repogen tool.""" log.info("Create online repository") @@ -822,7 +995,7 @@ def create_online_repository(task: Any) -> None: ############################################################## # Create MaintenanceTool resource file ############################################################## -def create_maintenance_tool_resource_file(task: Any) -> None: +def create_maintenance_tool_resource_file(task: QtInstallerTaskType) -> None: """Create MaintenanceTool resource file.""" log.info("Create MaintenanceTool resource file") set_config_directory(task) @@ -875,7 +1048,7 @@ def inject_update_rcc_to_archive(archive_file_path: str, file_to_be_injected: st ############################################################## # Create the final installer binary ############################################################## -def create_mac_disk_image(task: Any) -> None: +def create_mac_disk_image(task: QtInstallerTaskType) -> None: """Create Apple disk image.""" log.info("Create Apple disk image") output_dir = INSTALLER_OUTPUT_DIR_NAME @@ -889,7 +1062,7 @@ def create_mac_disk_image(task: Any) -> None: ############################################################## # All main build steps ############################################################## -def create_installer(task: Any) -> None: +def create_installer(task: QtInstallerTaskType) -> None: """Installer creation main steps.""" log.info("Creating Qt Installer Framework based installer/online repository") # check required tools @@ -929,7 +1102,7 @@ def str2bool(value: str) -> bool: @dataclass -class QtInstallerTask: +class QtInstallerTask(Generic[QtInstallerTaskType]): """QtInstallerTask dataclass""" config = ConfigParser(interpolation=ExtendedInterpolation()) @@ -1155,7 +1328,7 @@ def main() -> None: help="Create resource file for Maintenance Tool") args = parser.parse_args(sys.argv[1:]) - task: QtInstallerTask = QtInstallerTask( + task: QtInstallerTask[Any] = QtInstallerTask( configurations_dir=args.configurations_dir, configuration_file=args.configuration_file, offline_installer=args.offline_installer, diff --git a/packaging-tools/release_repo_updater.py b/packaging-tools/release_repo_updater.py index 58cb77e72..4b50fbe4a 100755 --- a/packaging-tools/release_repo_updater.py +++ b/packaging-tools/release_repo_updater.py @@ -525,7 +525,7 @@ async def build_online_repositories(tasks: List[ReleaseTask], license_: str, ins if not os.path.isfile(installer_config_file): raise PackagingError(f"Invalid 'config_file' path: {installer_config_file}") - installer_task = QtInstallerTask( + installer_task: QtInstallerTask[Any] = QtInstallerTask( configurations_dir=installer_config_base_dir, configuration_file=installer_config_file, create_repository=True, @@ -740,7 +740,7 @@ async def _build_offline_tasks(staging_server: str, staging_server_root: str, ta if not os.path.isfile(installer_config_file): raise PackagingError(f"Invalid 'config_file' path: {installer_config_file}") - installer_task = QtInstallerTask( + installer_task: QtInstallerTask[Any] = QtInstallerTask( configurations_dir=installer_config_base_dir, configuration_file=installer_config_file, offline_installer=True, diff --git a/packaging-tools/tests/test_create_installer.py b/packaging-tools/tests/test_create_installer.py index 1eeb43162..2dd93b68d 100644 --- a/packaging-tools/tests/test_create_installer.py +++ b/packaging-tools/tests/test_create_installer.py @@ -33,13 +33,14 @@ import os import unittest from pathlib import Path from tempfile import TemporaryDirectory -from typing import Tuple +from typing import Optional, Tuple from ddt import data, ddt # type: ignore from bld_utils import is_macos, is_windows from bldinstallercommon import locate_paths -from create_installer import remove_all_debug_libraries +from create_installer import CreateInstallerError, read_component_sha, remove_all_debug_libraries +from sdkcomponent import IfwSdkComponent @ddt @@ -72,6 +73,39 @@ class TestCommon(unittest.TestCase): else: self.assertCountEqual(result_rel, remaining_files) + @data( # type: ignore + ("8843d7f92416211de9ebb963ff4ce28125932878", "8843d7f92416211de9ebb963ff4ce28125932878"), + ("8843d", "8843d"), + ) + def test_read_component_sha(self, test_data: Tuple[str, Optional[str]]) -> None: + sha, exp = test_data + sdk_comp = IfwSdkComponent("", "", "", "", "", "", "", "", "", "", "") # type: ignore + with TemporaryDirectory(dir=Path.cwd()) as tmpdir: + test_sha = Path(tmpdir) / "test" + test_sha.write_text(sha, encoding="utf-8") + read_component_sha(sdk_comp, test_sha) + self.assertEqual(sdk_comp.component_sha1, exp) + + @data( # type: ignore + ("foobar"), + ("8843"), + ("8843d7f92416211de9ebb963ff4ce2812593287g"), + (""), + ) + def test_read_component_sha_invalid_content(self, test_sha1: str) -> None: + sdk_comp = IfwSdkComponent("", "", "", "", "", "", "", "", "", "", "") # type: ignore + with TemporaryDirectory(dir=Path.cwd()) as tmpdir: + test_sha = Path(tmpdir) / "test" + test_sha.write_text(test_sha1, encoding="utf-8") + with self.assertRaises(CreateInstallerError): + read_component_sha(sdk_comp, test_sha) + + def test_read_component_sha_invalid_path(self) -> None: + sdk_comp = IfwSdkComponent("", "", "", "", "", "", "", "", "", "", "") # type: ignore + with TemporaryDirectory(dir=Path.cwd()) as tmpdir: + with self.assertRaises(CreateInstallerError): + read_component_sha(sdk_comp, Path(tmpdir) / "invalid") + if __name__ == "__main__": unittest.main() |