aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPatrik Teivonen <patrik.teivonen@qt.io>2022-11-28 10:47:55 +0000
committerPatrik Teivonen <patrik.teivonen@qt.io>2022-11-28 11:00:15 +0000
commite3502143b1ee8febd162eab0531c2d8a774af617 (patch)
tree364d68290673deee39394ae2420774be2f77f7f3
parent034a2a7f5ca04226ab3a06f2f1584495337c3b56 (diff)
Revert "Refactor sdkcomponent.py"
This reverts commit af10f08f8d9ccc3a68b58aa701cb820c581c27c7. Reason for revert: archive handling issue Change-Id: Ia6bb03911502c84d7b52b1eaca18758f9f5f4cab Reviewed-by: Antti Kokko <antti.kokko@qt.io>
-rw-r--r--packaging-tools/archiveresolver.py173
-rw-r--r--packaging-tools/create_installer.py303
-rw-r--r--packaging-tools/sdkcomponent.py663
-rw-r--r--packaging-tools/tests/test_sdkcomponent.py256
4 files changed, 559 insertions, 836 deletions
diff --git a/packaging-tools/archiveresolver.py b/packaging-tools/archiveresolver.py
new file mode 100644
index 000000000..e7a47f8c1
--- /dev/null
+++ b/packaging-tools/archiveresolver.py
@@ -0,0 +1,173 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+#############################################################################
+#
+# Copyright (C) 2022 The Qt Company Ltd.
+# Contact: https://www.qt.io/licensing/
+#
+# This file is part of the release tools of the Qt Toolkit.
+#
+# $QT_BEGIN_LICENSE:GPL-EXCEPT$
+# Commercial License Usage
+# Licensees holding valid commercial Qt licenses may use this file in
+# accordance with the commercial license agreement provided with the
+# Software or, alternatively, in accordance with the terms contained in
+# a written agreement between you and The Qt Company. For licensing terms
+# and conditions see https://www.qt.io/terms-conditions. For further
+# information use the contact form at https://www.qt.io/contact-us.
+#
+# GNU General Public License Usage
+# Alternatively, this file may be used under the terms of the GNU
+# General Public License version 3 as published by the Free Software
+# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+# included in the packaging of this file. Please review the following
+# information to ensure the GNU General Public License requirements will
+# be met: https://www.gnu.org/licenses/gpl-3.0.html.
+#
+# $QT_END_LICENSE$
+#
+#############################################################################
+
+import os
+from configparser import ConfigParser
+from typing import List
+from urllib.parse import urlparse
+
+from bldinstallercommon import config_section_map, is_content_url_valid, safe_config_key_fetch
+from logging_util import init_logger
+from pkg_constants import PKG_TEMPLATE_BASE_DIR_NAME
+
+SERVER_NAMESPACE = 'ArchiveRemoteLocation'
+PACKAGE_REMOTE_LOCATION_RELEASE = 'release'
+PACKAGE_ARCHIVE_TAG = 'ARCHIVE_TAG'
+log = init_logger(__name__, debug_mode=False)
+
+
+###############################
+# class ArchiveLocationResolver
+###############################
+class ArchiveLocationResolver:
+ """Helper class to resolve full URI for archive"""
+
+ ######################################
+ # inner class ArchiveRemoteLocation
+ ######################################
+ class ArchiveRemoteLocation:
+ """Container class for server URL data"""
+
+ ###############################
+ # Constructor
+ ###############################
+ def __init__(self, server_name: str, server_base_url: str, server_base_path: str) -> None:
+ self.server_name = server_name
+ temp = server_base_url
+ if not temp.endswith('/') and not server_base_path.startswith('/'):
+ temp = temp + '/'
+ temp = temp + server_base_path
+ self.server_url = temp
+
+ ###############################
+ # Constructor
+ ###############################
+ def __init__(
+ self,
+ target_config: ConfigParser,
+ server_base_url_override: str,
+ configurations_root_dir: str,
+ key_substitution_list: List[List[str]],
+ ) -> None:
+ """Init data based on the target configuration"""
+ self.server_list = []
+ self.pkg_templates_dir_list: List[str] = []
+ self.default_server = None
+ self.configurations_root_dir = configurations_root_dir
+ self.key_substitution_list = key_substitution_list
+ # get packages tempalates src dir first
+ pkg_templates_dir = os.path.normpath(config_section_map(target_config, 'PackageTemplates')['template_dirs'])
+ self.pkg_templates_dir_list = pkg_templates_dir.replace(' ', '').rstrip(',\n').split(',')
+ # next read server list
+ if server_base_url_override:
+ server_obj = ArchiveLocationResolver.ArchiveRemoteLocation('default_server_name', server_base_url_override, '')
+ self.server_list.append(server_obj)
+ else:
+ for section in target_config.sections():
+ if section.startswith(SERVER_NAMESPACE):
+ server_name = section.split('.')[-1]
+ base_url = safe_config_key_fetch(target_config, section, 'base_url')
+ base_path = safe_config_key_fetch(target_config, section, 'base_path')
+ base_path.replace(' ', '')
+ # if base path is defined, then the following logic applies:
+ # if script is used in testclient mode fetch the packages from "RnD" location
+ # otherwise fetch packages from "release" location.
+ # If the base_path is not defined, use the address as-is
+ if base_path:
+ base_path = base_path + PACKAGE_REMOTE_LOCATION_RELEASE
+ server_obj = ArchiveLocationResolver.ArchiveRemoteLocation(server_name, base_url, base_path)
+ self.server_list.append(server_obj)
+ if len(self.server_list) == 1:
+ self.default_server = self.server_list[0]
+
+ ###############################
+ # Get full server URL by name
+ ###############################
+ def server_url_by_name(self, server_name: str) -> str:
+ """Get server URL by name. If empty name given, return the default server (may be null)."""
+ if not server_name:
+ return self.default_server.server_url if self.default_server else ""
+ for server in self.server_list:
+ if server.server_name == server_name:
+ return server.server_url
+ raise RuntimeError(f"*** Error! Unable to find server by name: {server_name}")
+
+ ###############################
+ # Get full server URI
+ ###############################
+ def resolve_full_uri(self, package_name: str, server_name: str, archive_uri: str) -> str:
+ """Resolve the full URI in the following order
+ 1. is archive_uri a valid URI as such
+ 2. check if given archive_uri denotes a package under package templates directory
+ 3. check if given URI is valid full URL
+ 4. try to compose full URL
+ return the resolved URI
+ """
+ # substitute key value pairs if any
+ for item in self.key_substitution_list:
+ temp = archive_uri.replace(item[0], item[1])
+ if temp != archive_uri:
+ archive_uri = temp
+ # 1. check if given archive_uri denotes a package under package templates directory
+ base_path = os.path.join(self.configurations_root_dir, PKG_TEMPLATE_BASE_DIR_NAME)
+ package_path = package_name + os.sep + 'data' + os.sep + archive_uri
+ # find the correct template subdirectory
+ for subdir in self.pkg_templates_dir_list:
+ path_temp = os.path.join(base_path, subdir)
+ if not os.path.isdir(path_temp):
+ path_temp = path_temp.replace(os.sep + PKG_TEMPLATE_BASE_DIR_NAME, '')
+ if os.path.isdir(path_temp):
+ temp = os.path.join(path_temp, package_path)
+ if os.path.isfile(temp):
+ return temp
+ # 2. check if given URI is valid full URL
+ res = is_content_url_valid(archive_uri)
+ if res:
+ return archive_uri
+ parts = urlparse(archive_uri)
+ if parts.scheme and parts.netloc:
+ raise RuntimeError(f"Url: [{archive_uri}] points to valid location but it is inaccessible.")
+ # 3. try to compose full URL
+ temp = self.server_url_by_name(server_name)
+ if not temp.endswith('/') and not archive_uri.startswith('/'):
+ temp = temp + '/'
+ return temp + archive_uri
+
+ ###############################
+ # Print out server list
+ ###############################
+ def print_server_list(self) -> None:
+ log.info("--------------------------------------------------")
+ log.info(" Server list:")
+ for server in self.server_list:
+ log.info(" ---------------------------------------------")
+ log.info(" Server name: %s", server.server_name)
+ log.info(" Server url: %s", server.server_url)
diff --git a/packaging-tools/create_installer.py b/packaging-tools/create_installer.py
index a2e1150fc..413ad173d 100644
--- a/packaging-tools/create_installer.py
+++ b/packaging-tools/create_installer.py
@@ -40,8 +40,10 @@ 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, Generator, List, Optional
+import pkg_constants
+from archiveresolver import ArchiveLocationResolver
from bld_utils import download, is_linux, is_macos, is_windows
from bldinstallercommon import (
copy_tree,
@@ -60,9 +62,9 @@ from bldinstallercommon import (
from installer_utils import PackagingError
from logging_util import init_logger
from patch_qt import patch_files, patch_qt_edition
-from pkg_constants import INSTALLER_OUTPUT_DIR_NAME, PKG_TEMPLATE_BASE_DIR_NAME
+from pkg_constants import INSTALLER_OUTPUT_DIR_NAME
from runner import run_cmd
-from sdkcomponent import IfwPayloadItem, IfwSdkComponent, IfwSdkError, parse_ifw_sdk_comp
+from sdkcomponent import SdkComponent
from threadedwork import ThreadedWork
if is_windows():
@@ -152,8 +154,8 @@ def set_config_xml(task: Any) -> Any:
fileslist = [config_template_dest]
replace_in_files(fileslist, UPDATE_REPOSITORY_URL_TAG, update_repository_url)
# substitute values also from global substitution list
- for key, value in task.substitutions.items():
- replace_in_files(fileslist, key, value)
+ for item in task.substitutions:
+ replace_in_files(fileslist, item[0], item[1])
return config_template_dest
@@ -165,8 +167,8 @@ def substitute_global_tags(task: Any) -> None:
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)
- for key, value in task.substitutions.items():
- log.info("%s = %s", key, value)
+ for item in task.substitutions:
+ log.info("%s = %s", item[0], item[1])
# initialize the file list
fileslist = []
@@ -181,8 +183,8 @@ def substitute_global_tags(task: Any) -> None:
replace_in_files(fileslist, PACKAGE_CREATION_DATE_TAG, task.build_timestamp)
if task.force_version_number_increase:
replace_in_files(fileslist, VERSION_NUMBER_AUTO_INCREASE_TAG, task.version_number_auto_increase_value)
- for key, value in task.substitutions.items():
- replace_in_files(fileslist, key, value)
+ for item in task.substitutions:
+ replace_in_files(fileslist, item[0], item[1])
##############################################################
@@ -243,36 +245,30 @@ def parse_component_data(task: Any, configuration_file: str, configurations_base
section_namespace = section.split(".")[0]
if section_namespace in task.package_namespace:
if section not in task.sdk_component_ignore_list:
- sdk_comp = parse_ifw_sdk_comp(
- config=configuration,
- section=section,
- pkg_template_search_dirs=task.packages_dir_name_list,
- substitutions=task.substitutions,
- file_share_base_url=task.archive_base_url,
+ sdk_component = SdkComponent(
+ section_name=section,
+ target_config=configuration,
+ packages_full_path_list=task.packages_dir_name_list,
+ archive_location_resolver=task.archive_location_resolver,
+ key_value_substitution_list=task.substitutions,
)
- try:
- # Validate component
- sdk_comp.validate()
- # Skip archive download if dry run
- if task.dry_run:
- sdk_comp.archive_skip = True
- except IfwSdkError as err:
- if not task.strict_mode:
- raise CreateInstallerError from err
- log.warning(
- "Skip invalid component (missing payload/metadata?): [%s]",
- sdk_comp.ifw_sdk_comp_name
- )
- sdk_comp.archive_skip = True
- # if include filter defined for component it is included only if LICENSE_TYPE
- # matches to include_filter
- # same configuration file can contain components that are included only to
- # either edition
- if sdk_comp.include_filter and sdk_comp.include_filter in task.license_type:
- task.sdk_component_list.append(sdk_comp)
- # components without include_filter definition are added by default
- elif not sdk_comp.include_filter:
- task.sdk_component_list.append(sdk_comp)
+ if task.dry_run:
+ sdk_component.set_archive_skip(True)
+ # validate component
+ sdk_component.validate()
+ if sdk_component.is_valid():
+ # if include filter defined for component it is included only if LICENSE_TYPE matches to include_filter
+ # same configuration file can contain components that are included only to either edition
+ if sdk_component.include_filter and sdk_component.include_filter in task.license_type:
+ task.sdk_component_list.append(sdk_component)
+ # components without include_filter definition are added by default
+ elif not sdk_component.include_filter:
+ task.sdk_component_list.append(sdk_component)
+ else:
+ if task.strict_mode:
+ raise CreateInstallerError(f"{sdk_component.error_msg()}")
+ log.warning("Ignore invalid component (missing payload/metadata?): %s", section)
+ task.sdk_component_list_skipped.append(sdk_component)
# check for extra configuration files if defined
extra_conf_list = safe_config_key_fetch(configuration, 'PackageConfigurationFiles', 'file_list')
if extra_conf_list:
@@ -295,7 +291,7 @@ def parse_components(task: Any) -> None:
parse_component_data(task, main_conf_file, conf_base_path)
-def create_metadata_map(sdk_component: IfwSdkComponent) -> List[List[str]]:
+def create_metadata_map(sdk_component: SdkComponent) -> List[List[str]]:
"""create lists for component specific tag substitutions"""
component_metadata_tag_pair_list = []
# version tag substitution if exists
@@ -304,6 +300,12 @@ def create_metadata_map(sdk_component: IfwSdkComponent) -> List[List[str]]:
# default package info substitution if exists
if sdk_component.package_default:
component_metadata_tag_pair_list.append([PACKAGE_DEFAULT_TAG, sdk_component.package_default])
+ # install priority info substitution if exists
+ if sdk_component.install_priority:
+ component_metadata_tag_pair_list.append([INSTALL_PRIORITY_TAG, sdk_component.install_priority])
+ # install priority info substitution if exists
+ if sdk_component.sorting_priority:
+ component_metadata_tag_pair_list.append([SORTING_PRIORITY_TAG, sdk_component.sorting_priority])
# target install dir substitution
if sdk_component.target_install_base:
component_metadata_tag_pair_list.append([TARGET_INSTALL_DIR_NAME_TAG, sdk_component.target_install_base])
@@ -317,9 +319,9 @@ 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:
+def get_component_sha1_file(sdk_component: SdkComponent, sha1_file_dest: str) -> None:
"""download component sha1 file"""
- download(sdk_component.comp_sha1_uri, sha1_file_dest)
+ download(sdk_component.component_sha1_uri, sha1_file_dest)
# read sha1 from the file
with open(sha1_file_dest, "r", encoding="utf-8") as sha1_file:
@@ -328,45 +330,61 @@ def get_component_sha1_file(sdk_component: IfwSdkComponent, sha1_file_dest: str)
def get_component_data(
task: Any,
- sdk_component: IfwSdkComponent,
- archive: IfwPayloadItem,
+ sdk_component: SdkComponent,
+ archive: SdkComponent.DownloadableArchive,
install_dir: str,
data_dir_dest: str,
compress_content_dir: str,
) -> None:
- """Download and create data for a component"""
- # Continue if payload item has no data
- if not os.path.basename(archive.archive_uri):
+ """download and create data for a component"""
+ package_raw_name = os.path.basename(archive.archive_uri)
+
+ # if no data to be installed, then just continue
+ if not package_raw_name:
return
- # Download payload to data_dir_dest
- downloaded_file = Path(data_dir_dest, 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):
+ if not archive.package_strip_dirs:
+ archive.package_strip_dirs = '0'
+
+ if package_raw_name.endswith(('.7z', '.tar.xz')) \
+ and archive.package_strip_dirs == '0' \
+ and not archive.package_finalize_items \
+ and not archive.archive_action \
+ and not archive.rpath_target \
+ and sdk_component.target_install_base == '/' \
+ and not archive.target_install_dir:
+ log.info("No repackaging actions required for the package, just download it directly to data directory")
+ downloaded_archive = os.path.normpath(data_dir_dest + os.sep + archive.archive_name)
+ # start download
+ download(archive.archive_uri, downloaded_archive)
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)
- # If patching items are specified, execute them here
- if archive.requires_patching:
+
+ downloaded_archive = os.path.normpath(install_dir + os.sep + package_raw_name)
+ # start download
+ download(archive.archive_uri, downloaded_archive)
+
+ # repackage content so that correct dir structure will get into the package
+
+ if not archive.extract_archive:
+ archive.extract_archive = 'yes'
+
+ # extract contents
+ if archive.extract_archive == 'yes':
+ extracted = extract_file(downloaded_archive, install_dir)
+ # remove old package if extraction was successful, else keep it
+ if extracted:
+ os.remove(downloaded_archive)
+
# perform custom action script for the extracted archive
if archive.archive_action:
- script_file, script_args = archive.archive_action
+ script_file, script_args = archive.archive_action.split(",")
script_args = script_args or ""
- script_path = Path(__file__).parent.resolve() / script_file
- if not script_path.exists():
+ script_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), script_file)
+ if not os.path.exists(script_path):
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 = [script_path, "--input-dir=" + install_dir, script_args.strip()]
+ if script_path.endswith(".py"):
cmd.insert(0, sys.executable)
- run_cmd(cmd=cmd)
+ run_cmd(cmd)
# strip out unnecessary folder structure based on the configuration
count = 0
@@ -391,51 +409,47 @@ def get_component_data(
except PackagingError:
pass
if 'patch_qt' in archive.package_finalize_items:
- patch_files(install_dir, product="qt_framework")
+ 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)
-
- if archive.component_sha1:
+ # 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.archive_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)
+
+ if archive.component_sha1_file:
# read sha1 from the file
- sha1_file_path = install_dir + os.sep + archive.component_sha1
+ sha1_file_path = install_dir + os.sep + archive.component_sha1_file
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
+ raise CreateInstallerError(f"Component SHA1 file '{archive.component_sha1_file}' not found")
+
+ # lastly compress the component back to .7z archive
content_list = os.listdir(compress_content_dir)
- # Add compress_content_dir in front of every item
+ # adding 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)
+
+ saveas = os.path.normpath(data_dir_dest + os.sep + archive.archive_name)
+ run_cmd(cmd=[task.archivegen_tool, saveas] + content_list, cwd=data_dir_dest)
def handle_set_executable(base_dir: str, package_finalize_items: str) -> None:
@@ -469,8 +483,8 @@ 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:
- for key, value in task.substitutions.items():
- package_name = package_name.replace(key, value)
+ for item in task.substitutions:
+ package_name = package_name.replace(item[0], item[1])
return package_name
@@ -565,27 +579,22 @@ def create_target_components(task: Any) -> None:
if task.create_repository and os.environ.get("LRELEASE_TOOL"):
if not os.path.isfile(os.path.join(task.script_root_dir, "lrelease")):
download(os.environ.get("LRELEASE_TOOL", ""), task.script_root_dir)
- extract_file(
- os.path.basename(os.environ.get("LRELEASE_TOOL", "")), task.script_root_dir
- )
+ extract_file(os.path.basename(os.environ.get("LRELEASE_TOOL", "")), task.script_root_dir)
get_component_data_work = ThreadedWork("get components data")
- for sdk_comp in task.sdk_component_list:
- log.info(sdk_comp)
- if sdk_comp.archive_skip:
- break
+ for sdk_component in task.sdk_component_list:
+ sdk_component.print_component_data()
# 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
+ package_name = substitute_package_name(task, sdk_component.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')
# save path for later substitute_component_tags call
- sdk_comp.meta_dir_dest = Path(meta_dir_dest)
+ sdk_component.meta_dir_dest = meta_dir_dest
# create meta destination folder
- sdk_comp.meta_dir_dest.mkdir(parents=True, exist_ok=True)
+ Path(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")
+ metadata_content_source_root = os.path.normpath(sdk_component.pkg_template_dir + os.sep + 'meta')
copy_tree(metadata_content_source_root, 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
@@ -595,21 +604,16 @@ def create_target_components(task: Any) -> None:
# add files into tag substitution
task.directories_for_substitutions.append(meta_dir_dest)
# handle archives
- if sdk_comp.downloadable_archives:
+ if sdk_component.downloadable_archive_list:
# save path for later substitute_component_tags call
- sdk_comp.temp_data_dir = Path(temp_data_dir)
+ sdk_component.temp_data_dir = temp_data_dir
# Copy archives into temporary build directory if exists
- for archive in sdk_comp.downloadable_archives:
- # fetch packages only if offline installer or repo creation,
- # for online installer just handle the metadata
+ for archive in sdk_component.downloadable_archive_list:
+ # fetch packages only if offline installer or repo creation, 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_content_dir = os.path.normpath(temp_data_dir + os.sep + archive.archive_name)
+ install_dir = os.path.normpath(compress_content_dir + archive.get_archive_installation_directory())
# 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)
@@ -617,28 +621,16 @@ def create_target_components(task: Any) -> None:
if is_windows():
install_dir = win32api.GetShortPathName(install_dir)
data_dir_dest = win32api.GetShortPathName(data_dir_dest)
- get_component_data_work.add_task(
- f"adding {archive.arch_name} to {sdk_comp.ifw_sdk_comp_name}",
- get_component_data,
- task,
- sdk_comp,
- archive,
- install_dir,
- data_dir_dest,
- compress_content_dir,
- )
+ get_component_data_work.add_task(f"adding {archive.archive_name} to {sdk_component.package_name}",
+ get_component_data, task, sdk_component, archive, install_dir, data_dir_dest, compress_content_dir)
# handle component sha1 uri
- if sdk_comp.comp_sha1_uri:
+ if sdk_component.component_sha1_uri:
sha1_file_dest = os.path.normpath(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,
- sdk_comp,
- sha1_file_dest,
- )
+ get_component_data_work.add_task(f"getting component sha1 file for {sdk_component.package_name}",
+ get_component_sha1_file, sdk_component, sha1_file_dest)
# maybe there is some static data
- data_content_source_root = os.path.normpath(sdk_comp.pkg_template_folder + os.sep + "data")
+ data_content_source_root = os.path.normpath(sdk_component.pkg_template_dir + 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)
@@ -650,9 +642,9 @@ def create_target_components(task: Any) -> None:
for sdk_component 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):
+ if hasattr(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)):
+ if not remove_tree(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)
@@ -949,11 +941,12 @@ class QtInstallerTask:
platform_identifier: str = ""
installer_name: str = ""
packages_dir_name_list: List[str] = field(default_factory=list)
- substitutions: Dict[str, str] = field(default_factory=dict)
+ substitutions: List[List[str]] = field(default_factory=list)
directories_for_substitutions: List[str] = field(default_factory=list)
- sdk_component_list: List[IfwSdkComponent] = field(default_factory=list)
- sdk_component_list_skipped: List[IfwSdkComponent] = field(default_factory=list)
+ sdk_component_list: List[SdkComponent] = field(default_factory=list)
+ sdk_component_list_skipped: List[SdkComponent] = field(default_factory=list)
sdk_component_ignore_list: List[str] = field(default_factory=list)
+ archive_location_resolver: Optional[ArchiveLocationResolver] = None
archive_base_url: str = ""
remove_debug_information_files: bool = False
remove_debug_libraries: bool = False
@@ -984,6 +977,10 @@ class QtInstallerTask:
self.config.get("PackageTemplates", "template_dirs"), self.configurations_dir
)
self._parse_substitutions()
+ if self.archive_location_resolver is None:
+ self.archive_location_resolver = ArchiveLocationResolver(
+ self.config, self.archive_base_url, self.configurations_dir, self.substitutions
+ )
def __str__(self) -> str:
return f"""Installer task:
@@ -1017,8 +1014,8 @@ class QtInstallerTask:
key, value = item.split("=", maxsplit=1)
if not value:
log.warning("Empty value for substitution string given, substituting anyway: %s", item)
- self.substitutions[key] = value # pylint: disable=unsupported-assignment-operation
- self.substitutions["%LICENSE%"] = self.license_type # pylint: disable=E1137
+ self.substitutions.append([key, value]) # pylint: disable=no-member
+ self.substitutions.append(['%LICENSE%', self.license_type]) # pylint: disable=no-member
def parse_ifw_pkg_template_dirs(self, template_list: str, configurations_dir: str) -> List[str]:
ret = []
@@ -1032,7 +1029,7 @@ class QtInstallerTask:
ret.append(package_template_dir)
else:
# first check if the pkg templates are under assumed "/configurations/pkg_templates" directory
- pkg_template_dir = os.path.join(configurations_dir, PKG_TEMPLATE_BASE_DIR_NAME,
+ pkg_template_dir = os.path.join(configurations_dir, pkg_constants.PKG_TEMPLATE_BASE_DIR_NAME,
package_template_dir)
if os.path.exists(pkg_template_dir):
ret.append(pkg_template_dir)
diff --git a/packaging-tools/sdkcomponent.py b/packaging-tools/sdkcomponent.py
index 8b4ffed96..231921331 100644
--- a/packaging-tools/sdkcomponent.py
+++ b/packaging-tools/sdkcomponent.py
@@ -29,441 +29,250 @@
#
#############################################################################
+import ntpath
import os
from configparser import ConfigParser
-from dataclasses import dataclass, field
-from pathlib import Path
-from typing import Dict, List, Optional, Tuple
-from urllib.parse import urlparse
+from typing import Any, List
+from archiveresolver import ArchiveLocationResolver
+from bldinstallercommon import config_section_map, is_content_url_valid, safe_config_key_fetch
from logging_util import init_logger
log = init_logger(__name__, debug_mode=False)
-
-class IfwSdkError(Exception):
- """Exception class for IfwSdkComponent errors"""
-
-
-@dataclass
-class IfwPayloadItem:
- """Payload item class for IfwSdkComponent's archives"""
-
- package_name: str
- archive_uri: str
- archive_action: Optional[Tuple[Path, str]]
- extract_archive: bool
- package_strip_dirs: int
- package_finalize_items: str
- parent_target_install_base: str
- arch_target_install_base: str
- arch_target_install_dir: str
- rpath_target: str
- component_sha1: str
- arch_name: str
- errors: List[str] = field(default_factory=list)
- # List of archive formats supported by Installer Framework:
- ifw_arch_formats: Tuple[str, ...] = (".7z", ".tar", ".gz", ".zip", ".xz", ".bz2")
- # List of payload archive formats supported by scripts for extraction:
- supported_arch_formats: Tuple[str, ...] = (".7z", ".tar", ".gz", ".zip", ".xz", ".bz2")
- _requires_extraction: Optional[bool] = None
- _requires_patching: Optional[bool] = None
-
- def __post_init__(self) -> None:
- """Post init: run sanity checks"""
- assert self.package_name, "The 'package_name' was not set?"
- self.arch_name = self._ensure_ifw_arch_name()
- self._sanity_check()
-
- def _sanity_check(self) -> None:
- """Perform a sanity check on the payload archive configuration and append the errors"""
- if self.archive_action:
- script_path, _ = self.archive_action
- if not script_path.exists() and script_path.is_file():
- self.errors += [f"Unable to locate custom archive action script: {script_path}"]
- if not self.archive_uri:
- self.errors += [f"[{self.package_name}] is missing 'archive_uri'"]
- if self.package_strip_dirs is None:
- self.errors += [f"[{self.package_name}] is missing 'package_strip_dirs'"]
- if not self.get_archive_install_dir():
- self.errors += [f"[{self.package_name}] is missing payload installation directory"]
- if not self.extract_archive and self.requires_patching:
- self.errors += [f"[{self.package_name}] patching specified with extract_archive=no"]
- if not self.archive_uri.endswith(self.supported_arch_formats) and self.requires_patching:
- if self.package_strip_dirs != 0:
- self.errors += [f"[{self.package_name}] package_strip_dirs!=0 for a non-archive"]
- if self.package_finalize_items:
- self.errors += [f"[{self.package_name}] package_finalize_items for a non-archive"]
+ONLINE_ARCHIVE_LIST_TAG = '<!--ONLINE_ARCHIVE_LIST-->'
+
+
+class SdkComponent:
+ """SdkComponent class contains all required info for one installable SDK component"""
+ class DownloadableArchive:
+ """DownloadableArchive subclass contains all required info about data packages for one SDK component"""
+ def __init__(
+ self,
+ archive: str,
+ package_name: str,
+ parent_target_install_base: str,
+ archive_server_name: str,
+ target_config: ConfigParser,
+ archive_location_resolver: ArchiveLocationResolver,
+ key_value_substitution_list: List[str],
+ ) -> None:
+ self.archive_uri = config_section_map(target_config, archive)['archive_uri']
+ self.archive_action = safe_config_key_fetch(target_config, archive, 'archive_action')
+ self.extract_archive = safe_config_key_fetch(target_config, archive, 'extract_archive')
+ self.package_strip_dirs = safe_config_key_fetch(target_config, archive, 'package_strip_dirs')
+ self.package_finalize_items = safe_config_key_fetch(target_config, archive, 'package_finalize_items')
+ # parent's 'target_install_base'
+ self.parent_target_install_base = parent_target_install_base
+ # in case the individual archive needs to be installed outside the root dir specified by the parent component
+ self.target_install_base: str = safe_config_key_fetch(target_config, archive, 'target_install_base')
+ # this is relative to 1) current archive's 'target_install_base' 2) parent components 'target_install_base'. (1) takes priority
+ self.target_install_dir: str = safe_config_key_fetch(target_config, archive, 'target_install_dir').lstrip(os.path.sep)
+ self.rpath_target = safe_config_key_fetch(target_config, archive, 'rpath_target')
+ self.component_sha1_file = safe_config_key_fetch(target_config, archive, 'component_sha1_file')
+ self.nomalize_archive_uri(package_name, archive_server_name, archive_location_resolver)
+ self.archive_name = safe_config_key_fetch(target_config, archive, 'archive_name')
+ if not self.archive_name:
+ self.archive_name = self.path_leaf(self.archive_uri)
+ # Parse unnecessary extensions away from filename (QTBUG-39219)
+ known_archive_types = ['.tar.gz', '.tar', '.zip', '.tar.xz', '.tar.bz2']
+ for item in known_archive_types:
+ if self.archive_name.endswith(item):
+ self.archive_name = self.archive_name.replace(item, '')
+ if not self.archive_name.endswith('.7z'):
+ self.archive_name += '.7z'
+ # substitute key-value pairs if any
+ for item in key_value_substitution_list:
+ self.target_install_base = self.target_install_base.replace(item[0], item[1])
+ self.target_install_dir = self.target_install_dir.replace(item[0], item[1])
+ self.archive_name = self.archive_name.replace(item[0], item[1])
+
+ def nomalize_archive_uri(
+ self, package_name: str, archive_server_name: str, archive_location_resolver: ArchiveLocationResolver
+ ) -> None:
+ self.archive_uri = archive_location_resolver.resolve_full_uri(package_name, archive_server_name, self.archive_uri)
+
+ def check_archive_data(self) -> Any:
+ if self.archive_uri.startswith('http'):
+ res = is_content_url_valid(self.archive_uri)
+ if not res:
+ return '*** Archive check fail! ***\n*** Unable to locate archive: ' + self.archive_uri
+ elif not os.path.isfile(self.archive_uri):
+ return '*** Archive check fail! ***\n*** Unable to locate archive: ' + self.archive_uri
+ return None
+
+ def path_leaf(self, path: str) -> str:
+ head, tail = ntpath.split(path)
+ return tail or ntpath.basename(head)
+
+ def get_archive_installation_directory(self) -> str:
+ if self.target_install_base:
+ return self.target_install_base + os.path.sep + self.target_install_dir
+ return self.parent_target_install_base + os.path.sep + self.target_install_dir
+
+ def __init__(
+ self,
+ section_name: str,
+ target_config: ConfigParser,
+ packages_full_path_list: List[str],
+ archive_location_resolver: ArchiveLocationResolver,
+ key_value_substitution_list: List[str],
+ ):
+ self.static_component = safe_config_key_fetch(target_config, section_name, 'static_component')
+ self.root_component = safe_config_key_fetch(target_config, section_name, 'root_component')
+ self.package_name = section_name
+ self.package_subst_name = section_name
+ self.packages_full_path_list = packages_full_path_list
+ self.archives = safe_config_key_fetch(target_config, section_name, 'archives')
+ self.archives = self.archives.replace(' ', '').replace('\n', '')
+ self.archives_extract_dir = safe_config_key_fetch(target_config, section_name, 'archives_extract_dir')
+ self.archive_server_name = safe_config_key_fetch(target_config, section_name, 'archive_server_name')
+ self.downloadable_archive_list: List[SdkComponent.DownloadableArchive] = [] # pylint: disable=E0601
+ self.target_install_base = safe_config_key_fetch(target_config, section_name, 'target_install_base')
+ self.version = safe_config_key_fetch(target_config, section_name, 'version')
+ self.version_tag = safe_config_key_fetch(target_config, section_name, 'version_tag')
+ self.package_default = safe_config_key_fetch(target_config, section_name, 'package_default')
+ self.install_priority = safe_config_key_fetch(target_config, section_name, 'install_priority')
+ self.sorting_priority = safe_config_key_fetch(target_config, section_name, 'sorting_priority')
+ self.component_sha1 = ""
+ self.component_sha1_uri = safe_config_key_fetch(target_config, section_name, 'component_sha1_uri')
+ if self.component_sha1_uri:
+ self.component_sha1_uri = archive_location_resolver.resolve_full_uri(self.package_name, self.archive_server_name, self.component_sha1_uri)
+ self.key_value_substitution_list = key_value_substitution_list
+ self.archive_skip = False
+ self.include_filter = safe_config_key_fetch(target_config, section_name, 'include_filter')
+ self.downloadable_arch_list_qs: List[Any] = []
+ self.pkg_template_dir = ''
+ self.sanity_check_error_msg = ''
+ self.target_config = target_config
+ self.archive_location_resolver = archive_location_resolver
+ self.meta_dir_dest: str = ""
+ self.temp_data_dir: str = ""
+ # substitute key-value pairs if any
+ for item in self.key_value_substitution_list:
+ self.target_install_base = self.target_install_base.replace(item[0], item[1])
+ self.version = self.version.replace(item[0], item[1])
+
+ def is_root_component(self) -> bool:
+ if self.root_component in ('yes', 'true'):
+ return True
+ return False
+
+ def set_archive_skip(self, do_skip: bool) -> None:
+ self.archive_skip = do_skip
def validate(self) -> None:
- """
- Validate IfwPayloadItem, log the errors
-
- Raises:
- IfwSdkError: If there are errors in the payload item
- """
- log.info("[[%s]] - %s", self.package_name, "NOK" if self.errors else "OK")
- if self.errors:
- for err in self.errors:
- log.error(err)
- log.debug(self) # Log also the details of the payload item with errors
- raise IfwSdkError(
- f"[[{self.package_name}]] Invalid payload configuration - check your configs!"
- )
-
- def _ensure_ifw_arch_name(self) -> str:
- """
- Get the archive name by splitting from its uri if a name doesn't already exist
-
- Returns:
- Name for the payload item
- """
- arch_name: str = self.arch_name or Path(self.archive_uri).name
- return arch_name
-
- def get_archive_install_dir(self) -> str:
- """
- Resolve archive install directory based on config
-
- Returns:
- Resolved install directory for the archive
- """
- ret = os.path.join(
- self.arch_target_install_base or self.parent_target_install_base,
- self.arch_target_install_dir.lstrip(os.path.sep),
- )
- return ret.rstrip(os.path.sep) or ret
-
- @property
- def requires_patching(self) -> bool:
- """
- A property to determine whether the payload content needs to be patched.
- The value is calculated once and saved to _requires_patching.
-
- Returns:
- A boolean for whether patching the payload is needed.
- """
- if self._requires_patching is None:
- self._requires_patching = not (
- self.package_strip_dirs == 0
- and not self.package_finalize_items
- and not self.archive_action
- and not self.rpath_target
- and self.parent_target_install_base == "/"
- and not self.arch_target_install_dir
- )
- return self._requires_patching
-
- @property
- def requires_extraction(self) -> bool:
- """
- A property to determine whether the archive needs to be extracted.
- The value is calculated once and saved to _requires_extraction.
-
- Returns:
- A boolean for whether extracting the payload is needed.
- """
- if self._requires_extraction is None:
- if self.archive_uri.endswith(self.ifw_arch_formats):
- # Extract IFW supported archives if patching required or archive has a sha1 file
- # Otherwise, use the raw CI artifact
- self._requires_extraction = bool(self.component_sha1) or self.requires_patching
- # It is also possible to disable the extraction in config (extract_archive=False)
- if not self.extract_archive:
- self._requires_extraction = False
- elif self.archive_uri.endswith(self.supported_arch_formats):
- # Repack supported archives to IFW friendly archive format
- self._requires_extraction = True
- else:
- # Payload not a supported archive type, use as-is
- self._requires_extraction = False
- return self._requires_extraction
-
- def __str__(self) -> str:
- return f"""
-- Downloadable payload name: {self.arch_name}
- Payload URI: {self.archive_uri}
- Extract archive: {self.requires_extraction}
- Patch payload: {self.requires_patching}""" + (
- f""", config:
- Strip package dirs: {self.package_strip_dirs}
- Finalize items: {self.package_finalize_items}
- Action script: {self.archive_action}
- RPath target: {self.rpath_target}
- Target install dir: {self.get_archive_install_dir()}"""
- if self.requires_patching else ""
- )
-
-
-class ArchiveResolver:
- """Resolver class for archive payload uris"""
-
- def __init__(self, file_share_base_url: str, pkg_template_folder: str) -> None:
- self.file_share_base_url = file_share_base_url
- self.pkg_template_folder = pkg_template_folder
-
- def resolve_payload_uri(self, unresolved_archive_uri: str) -> str:
- """
- Resolves the given archive URI and resolves it based on the type of URI given
- Available URI types:
- - file system string paths, file system URIs
- - network locations e.g. HTTP URLs
- - file system string paths relative to data folder under package template root
-
- Args:
- unresolved_archive_uri: Original URI to resolve
-
- Returns:
- A resolved URI location for the payload
- """
- # is it a file system path or an absolute URL which can be downloaded
- if os.path.exists(unresolved_archive_uri) or urlparse(unresolved_archive_uri).netloc:
- return unresolved_archive_uri
- # is it relative to pkg template root dir, under the 'data' directory
- pkg_data_dir = os.path.join(self.pkg_template_folder, "data", unresolved_archive_uri)
- if os.path.exists(pkg_data_dir):
- return pkg_data_dir
- # ok, we assume this is a URL which can be downloaded
- return self.file_share_base_url.rstrip("/") + "/" + unresolved_archive_uri.lstrip("/")
-
-
-@dataclass
-class IfwSdkComponent:
- """Installer framework sdk component class"""
-
- ifw_sdk_comp_name: str
- pkg_template_folder: str
- archive_resolver: ArchiveResolver
- downloadable_archives: List[IfwPayloadItem]
- archives_extract_dir: str
- target_install_base: str
- version: str
- version_tag: str
- package_default: str
- comp_sha1_uri: str
- include_filter: str
- component_sha1: Optional[str] = None
- temp_data_dir: Optional[Path] = None
- meta_dir_dest: Optional[Path] = None
- archive_skip: bool = False
-
- def __post_init__(self) -> None:
- """Post init: convert component sha1 uri to resolved uri if it exists"""
- if self.comp_sha1_uri:
- self.comp_sha1_uri = self.archive_resolver.resolve_payload_uri(self.comp_sha1_uri)
-
- def validate(self) -> None:
- """
- Perform validation on IfwSdkComponent, raise error if component not valid
-
- Raises:
- AssertionError: When the component's package name doesn't exist
- IfwSdkError: When component with payload doesn't have target install base configured
- """
- assert self.ifw_sdk_comp_name, "Undefined package name?"
- if self.downloadable_archives and not self.target_install_base:
- raise IfwSdkError(f"[{self.ifw_sdk_comp_name}] is missing 'target_install_base'")
+ # look up correct package template directory from list
+ found = False
+ for item in self.key_value_substitution_list:
+ self.package_name = self.package_name.replace(item[0], item[1])
+ for item in self.packages_full_path_list:
+ template_full_path = os.path.normpath(item + os.sep + self.package_subst_name)
+ if os.path.exists(template_full_path):
+ if not found:
+ # take the first match
+ self.pkg_template_dir = template_full_path
+ found = True
+ else:
+ # sanity check, duplicate template should not exist to avoid
+ # problems!
+ log.warning("Found duplicate template for: %s", self.package_name)
+ log.warning("Ignoring: %s", template_full_path)
+ log.warning("Using: %s", self.pkg_template_dir)
+ self.parse_archives(self.target_config, self.archive_location_resolver)
+ self.check_component_data()
+
+ def check_component_data(self) -> None:
+ if self.static_component:
+ if not os.path.isfile(self.static_component):
+ self.sanity_check_fail(self.package_name, 'Unable to locate given static package: ' + self.static_component)
+ return
+ # no more checks needed for static component
+ return
+ if not self.package_name:
+ self.sanity_check_fail(self.package_name, 'Undefined package name?')
+ return
+ if self.archives and not self.target_install_base:
+ self.sanity_check_fail(self.package_name, 'Undefined target_install_base?')
+ return
+ if self.version and not self.version_tag:
+ self.sanity_check_fail(self.package_name, 'Undefined version_tag?')
+ return
+ if self.version_tag and not self.version:
+ self.sanity_check_fail(self.package_name, 'Undefined version?')
+ return
+ if self.package_default not in ['true', 'false', 'script']:
+ self.package_default = 'false'
+ # check that package template exists
+ if not os.path.exists(self.pkg_template_dir):
+ self.sanity_check_fail(self.package_name, 'Package template dir does not exist: ' + self.pkg_template_dir)
+ return
+ if not self.archive_skip:
+ # next check that archive locations exist
+ for archive in self.downloadable_archive_list:
+ error_msg = archive.check_archive_data()
+ if error_msg:
+ self.sanity_check_fail(self.package_name, error_msg)
+ return
+
+ def sanity_check_fail(self, component_name: str, message: str) -> None:
+ self.sanity_check_error_msg = '*** Sanity check fail! ***\n*** Component: [' + component_name + ']\n*** ' + message
+
+ def is_valid(self) -> bool:
+ if self.sanity_check_error_msg:
+ return False
+ return True
+
+ def error_msg(self) -> str:
+ return self.sanity_check_error_msg
+
+ def parse_archives(self, target_config: ConfigParser, archive_location_resolver: ArchiveLocationResolver) -> None:
+ if self.archives:
+ archives_list = self.archives.split(',')
+ for archive in archives_list:
+ if not archive:
+ log.warning("[%s]: Archive list in config has ',' issues", self.package_name)
+ continue
+ # check that archive template exists
+ if not target_config.has_section(archive):
+ raise RuntimeError(f'*** Error! Given archive section does not exist in configuration file: {archive}')
+ archive_obj = SdkComponent.DownloadableArchive(archive, self.package_name, self.target_install_base, self.archive_server_name,
+ target_config, archive_location_resolver,
+ self.key_value_substitution_list)
+ self.downloadable_archive_list.append(archive_obj)
def generate_downloadable_archive_list(self) -> List[List[str]]:
- """
- Generate list that is embedded into package.xml
-
- Returns:
- Generated downloaded archive list
- """
- archive_list: List[str] = [a.arch_name for a in self.downloadable_archives]
- return [["<!--ONLINE_ARCHIVE_LIST-->", ", ".join(archive_list)]]
-
- def __str__(self) -> str:
- print_data = f"""
-[{self.ifw_sdk_comp_name}]
-Include filter: {self.include_filter}
-Target install base: {self.target_install_base}
-Version: {self.version}
-Version tag: {self.version_tag}
-Package default: {self.package_default}
-Archives:"""
- for archive in self.downloadable_archives:
- print_data += str(archive)
- return print_data
-
-
-class ConfigSubst:
- """Configuration file key substitutor and resolver"""
-
- def __init__(self, config: ConfigParser, section: str, substitutions: Dict[str, str]) -> None:
- if not config.has_section(section):
- raise IfwSdkError(f"Missing section in configuration file: {section}")
- self.config = config
- self.section = section
- self.substitutions: Dict[str, str] = substitutions
- self.resolved: Dict[str, str] = {}
-
- def get(self, key: str, default: str = "") -> str:
- """
- Perform substitutions for the given key and return resolved key value.
- The values are saved to self.resolved for future lookups.
-
- Args:
- key: The key to look up from already resolved dict or to resolve
- default: This value is used when key not found from config section
-
- Returns:
- A string value for the key or the given default (default=empty string)
-
- Raises:
- KeyError: When value for given key doesn't exist yet, handled
- """
- try:
- return self.resolved[key]
- except KeyError:
- tmp = self.config[self.section].get(key, default)
- for subst_key, subst_value in self.substitutions.items():
- tmp = tmp.replace(subst_key, subst_value)
- self.resolved[key] = tmp
- return self.resolved[key]
-
-
-def locate_pkg_templ_dir(search_dirs: List[str], component_name: str) -> str:
- """
- Return one result for given component name from given search directories or fail
-
- Args:
- search_dirs: The list of string file system paths for the directories to look from
- component_name: The component's directory name to match for
-
- Returns:
- A matching file system string path to a component's template folder
-
- Raises:
- IfwSdkError: When there are more than one matches
- """
- # look up correct package template directory from list
- log.info("Searching pkg template '%s' folder from: %s", component_name, search_dirs)
- matches: List[str] = []
- for item in search_dirs:
- matches.extend([str(p) for p in Path(item).resolve(strict=True).rglob(component_name)])
- if len(matches) < 1:
- raise IfwSdkError(f"Expected to find one result for '{component_name}' from {search_dirs}")
- return matches.pop()
-
-
-def parse_ifw_sdk_comp(
- config: ConfigParser,
- section: str,
- pkg_template_search_dirs: List[str],
- substitutions: Dict[str, str],
- file_share_base_url: str,
-) -> IfwSdkComponent:
- """
- Parse IfwSdkComponent from the given config
-
- Args:
- config: The given config to parse via ConfigParser
- section: The section name for the component
- pkg_template_search_dirs: Paths that should contain the template folder for the component
- substitutions: String substitutions to apply for the config/template while parsing
- file_share_base_url: URL to the file share server containing the payload content
-
- Returns:
- An instance of the parsed IfwSdkComponent
- """
- log.info("Parsing section: %s", section)
- config_subst = ConfigSubst(config, section, substitutions)
- pkg_template_folder = locate_pkg_templ_dir(pkg_template_search_dirs, component_name=section)
- archive_resolver = ArchiveResolver(file_share_base_url, pkg_template_folder)
- archives = config[section].get("archives", "")
- archive_sections = [s.strip() for s in archives.split(",") if s.strip() != ""]
- archives_extract_dir = config_subst.get("archives_extract_dir")
- target_install_base = config_subst.get("target_install_base")
- version = config_subst.get("version")
- version_tag = config_subst.get("version_tag")
- package_default = config_subst.get("package_default", "false")
- comp_sha1_uri = config_subst.get("component_sha1_uri", "")
- include_filter = config_subst.get("include_filter")
- parsed_archives = parse_ifw_sdk_archives(
- config=config,
- archive_sections=archive_sections,
- archive_resolver=archive_resolver,
- parent_target_install_base=target_install_base,
- substitutions=substitutions,
- )
- return IfwSdkComponent(
- ifw_sdk_comp_name=section,
- pkg_template_folder=pkg_template_folder,
- archive_resolver=archive_resolver,
- downloadable_archives=parsed_archives,
- archives_extract_dir=archives_extract_dir,
- target_install_base=target_install_base,
- version=version,
- version_tag=version_tag,
- package_default=package_default,
- comp_sha1_uri=comp_sha1_uri,
- include_filter=include_filter,
- )
-
-
-def parse_ifw_sdk_archives(
- config: ConfigParser,
- archive_sections: List[str],
- archive_resolver: ArchiveResolver,
- parent_target_install_base: str,
- substitutions: Dict[str, str],
-) -> List[IfwPayloadItem]:
- """
- Parsed IfwPayloadItems for the given payload sections in config
-
- Args:
- config: The config containing the payload sections via ConfigParser
- archive_sections: The payload sections for the component
- archive_resolver: The resolver to use for payload URIs
- parent_target_install_base: The parent component's root install folder
- substitutions: The string substitutions to apply while parsing config/templates
-
- Returns:
- A list of parsed IfwPayloadItems for the component
- """
- parsed_archives = []
- for arch_section_name in archive_sections:
- config_subst = ConfigSubst(config, arch_section_name, substitutions)
- unresolved_archive_uri = config_subst.get("archive_uri")
- resolved_archive_uri = archive_resolver.resolve_payload_uri(unresolved_archive_uri)
- archive_action_string = config_subst.get("archive_action", "")
- archive_action: Optional[Tuple[Path, str]] = None
- if archive_action_string:
- script_path, script_args = archive_action_string.split(",")
- archive_action = Path(__file__).parent / script_path, script_args.strip() or ""
- extract_archive = bool(
- config_subst.get("extract_archive", "yes").lower() in ["yes", "true", "1"]
- )
- package_strip_dirs = int(config_subst.get("package_strip_dirs") or 0)
- package_finalize_items = config_subst.get("package_finalize_items")
- # in case the individual archive needs to be installed outside the root dir specified by
- # the parent component
- target_install_base = config_subst.get("target_install_base", "")
- # this is relative to:
- # 1) current archive's 'target_install_base'
- # 2) parent components 'target_install_base'. (1) takes priority
- target_install_dir = config_subst.get("target_install_dir")
- rpath_target = config_subst.get("rpath_target")
- if rpath_target and not rpath_target.startswith(os.sep):
- rpath_target = os.sep + rpath_target
- component_sha1_file = config_subst.get("component_sha1_file")
- archive_name = config_subst.get("archive_name")
- payload = IfwPayloadItem(
- package_name=arch_section_name,
- archive_uri=resolved_archive_uri,
- archive_action=archive_action,
- extract_archive=extract_archive,
- package_strip_dirs=package_strip_dirs,
- package_finalize_items=package_finalize_items,
- parent_target_install_base=parent_target_install_base,
- arch_target_install_base=target_install_base,
- arch_target_install_dir=target_install_dir,
- rpath_target=rpath_target,
- component_sha1=component_sha1_file,
- arch_name=archive_name,
- )
- payload.validate()
- parsed_archives.append(payload)
- return parsed_archives
+ """Generate list that is embedded into package.xml"""
+ output = ''
+ for item in self.downloadable_archive_list:
+ if not output:
+ output = item.archive_name
+ else:
+ output = output + ', ' + item.archive_name
+
+ temp_list = []
+ temp_list.append([ONLINE_ARCHIVE_LIST_TAG, output])
+ return temp_list
+
+ def print_component_data(self) -> None:
+ log.info("=============================================================")
+ log.info("[%s]", self.package_name)
+ if self.static_component:
+ log.info("Static component: %s", self.static_component)
+ return
+ if self.root_component:
+ log.info("Root component: %s", self.root_component)
+ log.info("Include filter: %s", self.include_filter)
+ log.info("Target install base: %s", self.target_install_base)
+ log.info("Version: %s", self.version)
+ log.info("Version tag: %s", self.version_tag)
+ log.info("Package default: %s", self.package_default)
+ if self.downloadable_archive_list:
+ log.info(" Archives:")
+ for archive in self.downloadable_archive_list:
+ log.info("---------------------------------------------------------------")
+ log.info("Downloadable archive name: %s", archive.archive_name)
+ log.info("Strip dirs: %s", archive.package_strip_dirs)
+ log.info("Target install dir: %s", archive.get_archive_installation_directory())
+ log.info("RPath target: %s", archive.rpath_target)
+ log.info("URI: %s", archive.archive_uri)
diff --git a/packaging-tools/tests/test_sdkcomponent.py b/packaging-tools/tests/test_sdkcomponent.py
deleted file mode 100644
index ce5523a7f..000000000
--- a/packaging-tools/tests/test_sdkcomponent.py
+++ /dev/null
@@ -1,256 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-#############################################################################
-#
-# Copyright (C) 2022 The Qt Company Ltd.
-# Contact: https://www.qt.io/licensing/
-#
-# This file is part of the release tools of the Qt Toolkit.
-#
-# $QT_BEGIN_LICENSE:GPL-EXCEPT$
-# Commercial License Usage
-# Licensees holding valid commercial Qt licenses may use this file in
-# accordance with the commercial license agreement provided with the
-# Software or, alternatively, in accordance with the terms contained in
-# a written agreement between you and The Qt Company. For licensing terms
-# and conditions see https://www.qt.io/terms-conditions. For further
-# information use the contact form at https://www.qt.io/contact-us.
-#
-# GNU General Public License Usage
-# Alternatively, this file may be used under the terms of the GNU
-# General Public License version 3 as published by the Free Software
-# Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
-# included in the packaging of this file. Please review the following
-# information to ensure the GNU General Public License requirements will
-# be met: https://www.gnu.org/licenses/gpl-3.0.html.
-#
-# $QT_END_LICENSE$
-#
-#############################################################################
-
-import os
-import tempfile
-import unittest
-from configparser import ConfigParser, ExtendedInterpolation
-from pathlib import Path
-from typing import Dict, List, Tuple
-
-from ddt import data, ddt, unpack # type: ignore
-
-from sdkcomponent import (
- ArchiveResolver,
- IfwPayloadItem,
- IfwSdkError,
- locate_pkg_templ_dir,
- parse_ifw_sdk_comp,
-)
-
-
-def ifw_sdk_config_valid(section_name: str) -> ConfigParser:
- conf = ConfigParser(interpolation=ExtendedInterpolation())
- conf.add_section(section_name)
- conf.set(section_name, "archives", "sub.arch1, sub.arch2, sub.sub3.arch3")
- conf.set(section_name, "target_install_base", "/%QT_VERSION%/gcc_64")
- conf.add_section("sub.arch1")
- conf.set(
- "sub.arch1", "archive_uri", "/qt/dev/release_content/qtbase/qtbase-%LINUX_GCC64_TARGET%.7z"
- )
- conf.add_section("sub.arch2")
- conf.set(
- "sub.arch2", "archive_uri", "/qt/dev/release_content/qtsvg/qtsvg-%LINUX_GCC64_TARGET%.7z"
- )
- conf.add_section("sub.sub3.arch3")
- conf.set(
- "sub.sub3.arch3",
- "archive_uri",
- "/%LICENSE%/%QT_VERSION%/foo/qtdeclarative-%LINUX_GCC64_TARGET%.7z",
- )
- return conf
-
-
-def key_value_subst_dict() -> Dict[str, str]:
- return {
- "%LICENSE%": "opensource",
- "%QT_VERSION%": "5.10.0",
- "%LINUX_GCC64_TARGET%": "RHEL_7_4",
- }
-
-
-def ifw_pkg_templ_dirs(ifw_pkg_names: List[str]) -> List[str]:
- ret: List[str] = []
- for ifw_pkg_name in ifw_pkg_names:
- ret.extend(
- [
- f"pkg_templates/product1/{ifw_pkg_name}/meta/package.xml",
- f"pkg_templates/product1/{ifw_pkg_name}/meta/installscript.qs",
- f"pkg_templates/product1/{ifw_pkg_name}/data/readme.txt",
- ]
- )
- return ret
-
-
-def create_paths(root_folder: str, paths: List[str]) -> List[str]:
- ret: List[str] = []
- for item in paths:
- full_path = os.path.join(root_folder, item)
- ret.append(full_path)
- head, tail = os.path.split(full_path)
- os.makedirs(head, exist_ok=True)
- if tail:
- with open(full_path, "a", encoding="utf-8"):
- pass
- return ret
-
-
-@ddt
-class TestRunner(unittest.TestCase):
- def test_parse_ifw_sdk_archives(self) -> None:
- section = "qt.qt5.5100.gcc_64"
- pkg_template_paths = ifw_pkg_templ_dirs([section])
- ifw_sdk_config = ifw_sdk_config_valid(section)
-
- with tempfile.TemporaryDirectory(dir=os.getcwd()) as tmp_base_dir:
- pkg_template_search_dirs: List[str] = []
- create_paths(tmp_base_dir, pkg_template_paths)
- pkg_template_search_dirs.append(os.path.join(tmp_base_dir, "pkg_templates"))
-
- file_share_base_url = "http://fileshare.intra/base/path/"
- comp = parse_ifw_sdk_comp(
- ifw_sdk_config,
- section,
- pkg_template_search_dirs,
- key_value_subst_dict(),
- file_share_base_url,
- )
-
- self.assertEqual(len(comp.downloadable_archives), 3)
- self.assertListEqual(
- comp.generate_downloadable_archive_list(),
- [
- [
- "<!--ONLINE_ARCHIVE_LIST-->",
- "qtbase-RHEL_7_4.7z, qtsvg-RHEL_7_4.7z, qtdeclarative-RHEL_7_4.7z",
- ]
- ],
- )
- self.assertEqual(
- {a.archive_uri for a in comp.downloadable_archives},
- {
- file_share_base_url + "qt/dev/release_content/qtbase/qtbase-RHEL_7_4.7z",
- file_share_base_url + "qt/dev/release_content/qtsvg/qtsvg-RHEL_7_4.7z",
- file_share_base_url + "opensource/5.10.0/foo/qtdeclarative-RHEL_7_4.7z",
- },
- )
- self.assertEqual(
- {a.arch_name for a in comp.downloadable_archives},
- {"qtbase-RHEL_7_4.7z", "qtsvg-RHEL_7_4.7z", "qtdeclarative-RHEL_7_4.7z"},
- )
- for downloadable_archive in comp.downloadable_archives:
- self.assertFalse(downloadable_archive.errors)
- self.assertEqual(downloadable_archive.get_archive_install_dir(), "/5.10.0/gcc_64")
-
- def test_ifw_payload_item_invalid(self) -> None:
- with self.assertRaises(AssertionError):
- IfwPayloadItem(
- package_name="",
- archive_uri="http://foo.com/readme.7z",
- archive_action=None,
- extract_archive=True,
- package_strip_dirs=0,
- package_finalize_items="",
- parent_target_install_base="/base/install/dir",
- arch_target_install_base="/foo/bar",
- arch_target_install_dir="/bar/foo",
- rpath_target="",
- component_sha1="",
- arch_name="readme.7z",
- )
-
- def test_ifw_payload_item_valid(self) -> None:
- item = IfwPayloadItem(
- package_name="foobar",
- archive_uri="http://foo.com/readme.7z",
- archive_action=None,
- extract_archive=True,
- package_strip_dirs=0,
- package_finalize_items="",
- parent_target_install_base="",
- arch_target_install_base="/foo/bar",
- arch_target_install_dir="",
- rpath_target="",
- component_sha1="",
- arch_name="readme.7z",
- )
- self.assertFalse(item.errors)
-
- @data( # type: ignore
- ("foo.7z", None, True, 0, "", "/", "", "", "", False, False),
- ("foo.7z", None, False, 0, "", "/", "", "", "", False, False),
- ("foo.7z", (Path("foo.sh"), ""), True, 0, "", "/", "", "", "", True, True),
- ("foo.7z", None, True, 1, "", "/", "", "", "", True, True),
- ("foo.7z", None, True, 0, "foo", "/", "", "", "", True, True),
- ("foo.7z", None, True, 0, "", "/foo", "", "", "", True, True),
- ("foo.7z", None, True, 0, "", "/", "foo", "", "", True, True),
- ("foo.7z", None, True, 0, "", "/", "", "foo", "", True, True),
- ("foo.7z", None, True, 0, "", "/", "", "", "foo", True, False),
- ("foo.txt", None, True, 0, "", "/", "", "", "foo", False, False),
- ("foo.txt", None, True, 0, "", "/", "", "", "", False, False),
- ("foo.txt", None, True, 0, "", "/", "foo/bar", "", "", False, True),
- )
- @unpack # type: ignore
- def test_ifw_payload_item_requires_packaging_operations(
- self,
- archive_uri: str,
- archive_action: Tuple[Path, str],
- extract_archive: bool,
- package_strip_dirs: int,
- package_finalize_items: str,
- parent_target_install_base: str,
- arch_target_install_dir: str,
- rpath_target: str,
- component_sha1: str,
- expected_requires_extraction: bool,
- expected_requires_patching: bool,
- ) -> None:
- item = IfwPayloadItem(
- package_name="foobar",
- archive_uri=archive_uri,
- archive_action=archive_action,
- extract_archive=extract_archive,
- package_strip_dirs=package_strip_dirs,
- package_finalize_items=package_finalize_items,
- parent_target_install_base=parent_target_install_base,
- arch_target_install_base="/foo/bar",
- arch_target_install_dir=arch_target_install_dir,
- rpath_target=rpath_target,
- component_sha1=component_sha1,
- arch_name="readme.7z",
- )
- self.assertEqual(item.requires_extraction, expected_requires_extraction)
- self.assertEqual(item.requires_patching, expected_requires_patching)
-
- def test_archive_resolver(self) -> None:
- with tempfile.TemporaryDirectory(dir=os.getcwd()) as tmp_base_dir:
- template_folder = os.path.join(tmp_base_dir, "qt.tools.foo")
- data_folder = os.path.join(template_folder, "data")
- payload_file = os.path.join(data_folder, "readme.txt")
- os.makedirs(data_folder, exist_ok=True)
- with open(payload_file, "a", encoding="utf-8"):
- pass
-
- resolver = ArchiveResolver("http://intranet.local.it/artifacts", template_folder)
- self.assertEqual(resolver.resolve_payload_uri("readme.txt"), payload_file)
- self.assertEqual(
- resolver.resolve_payload_uri("qt/qtsvg/qtsvg-RHEL_7_4.7z"),
- "http://intranet.local.it/artifacts/qt/qtsvg/qtsvg-RHEL_7_4.7z",
- )
- self.assertEqual(resolver.resolve_payload_uri(__file__), __file__)
-
- def test_locate_pkg_templ_dir_invalid(self) -> None:
- with tempfile.TemporaryDirectory(dir=os.getcwd()) as tmp_base_dir:
- with self.assertRaises(IfwSdkError):
- locate_pkg_templ_dir([tmp_base_dir], "qt.foo")
-
-
-if __name__ == "__main__":
- unittest.main()