diff options
Diffstat (limited to 'build_scripts/utils.py')
-rw-r--r-- | build_scripts/utils.py | 546 |
1 files changed, 206 insertions, 340 deletions
diff --git a/build_scripts/utils.py b/build_scripts/utils.py index fd9b8f71d..74d9e6fc5 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -4,7 +4,6 @@ import errno import fnmatch import glob -import itertools import os import re import shutil @@ -15,17 +14,12 @@ import tempfile import urllib.request as urllib from collections import defaultdict from pathlib import Path +from textwrap import dedent, indent + +from .log import log +from . import (PYSIDE_PYTHON_TOOLS, PYSIDE_LINUX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS, + PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS) -try: - # Using the distutils implementation within setuptools - from setuptools._distutils import log - from setuptools._distutils.errors import DistutilsSetupError -except ModuleNotFoundError: - # This is motivated by our CI using an old version of setuptools - # so then the coin_build_instructions.py script is executed, and - # import from this file, it was failing. - from distutils import log - from distutils.errors import DistutilsSetupError try: WindowsError @@ -33,6 +27,23 @@ except NameError: WindowsError = None +def which(name): + """ + Like shutil.which, but accepts a string or a PathLike and returns a Path + """ + path = None + try: + if isinstance(name, Path): + name = str(name) + path = shutil.which(name) + if path is None: + raise TypeError("None was returned") + path = Path(path) + except TypeError as e: + log.error(f"{name} was not found in PATH: {e}") + return path + + def is_64bit(): return sys.maxsize > 2147483647 @@ -49,7 +60,7 @@ def filter_match(name, patterns): def update_env_path(newpaths): paths = os.environ['PATH'].lower().split(os.pathsep) for path in newpaths: - if not path.lower() in paths: + if str(path).lower() not in paths: log.info(f"Inserting path '{path}' to environment") paths.insert(0, path) os.environ['PATH'] = f"{path}{os.pathsep}{os.environ['PATH']}" @@ -64,157 +75,6 @@ def get_numpy_location(): return None -def winsdk_setenv(platform_arch, build_type): - from setuptools._distutils import msvc9compiler as msvc9 - - sdk_version_map = { - "v6.0a": 9.0, - "v6.1": 9.0, - "v7.0": 9.0, - "v7.0a": 10.0, - "v7.1": 10.0 - } - - log.info(f"Searching Windows SDK with MSVC compiler version {msvc9.VERSION}") - setenv_paths = [] - for base in msvc9.HKEYS: - sdk_versions = msvc9.Reg.read_keys(base, msvc9.WINSDK_BASE) - if sdk_versions: - for sdk_version in sdk_versions: - installationfolder = msvc9.Reg.get_value(f"{msvc9.WINSDK_BASE}\\{sdk_version}", - "installationfolder") - # productversion = msvc9.Reg.get_value( - # "{}\\{}".format(msvc9.WINSDK_BASE, sdk_version), - # "productversion") - setenv_path = os.path.join(installationfolder, os.path.join('bin', 'SetEnv.cmd')) - if not os.path.exists(setenv_path): - continue - if sdk_version not in sdk_version_map: - continue - if sdk_version_map[sdk_version] != msvc9.VERSION: - continue - setenv_paths.append(setenv_path) - if len(setenv_paths) == 0: - raise DistutilsSetupError("Failed to find the Windows SDK with MSVC compiler " - f"version {msvc9.VERSION}") - for setenv_path in setenv_paths: - log.info(f"Found {setenv_path}") - - # Get SDK env (use latest SDK version installed on system) - setenv_path = setenv_paths[-1] - log.info(f"Using {setenv_path} ") - build_arch = "/x86" if platform_arch.startswith("32") else "/x64" - build_type = "/Debug" if build_type.lower() == "debug" else "/Release" - setenv_cmd = [setenv_path, build_arch, build_type] - setenv_env = get_environment_from_batch_command(setenv_cmd) - _setenv_paths = [setenv_env[k] for k in setenv_env if k.upper() == 'PATH'] - setenv_env_paths = os.pathsep.join(_setenv_paths).split(os.pathsep) - setenv_env_without_paths = {k: setenv_env[k] for k in setenv_env if k.upper() != 'PATH'} - - # Extend os.environ with SDK env - log.info("Initializing Windows SDK env...") - update_env_path(setenv_env_paths) - for k in sorted(setenv_env_without_paths): - v = setenv_env_without_paths[k] - log.info(f"Inserting '{k} = {v}' to environment") - os.environ[k] = v - log.info("Done initializing Windows SDK env") - - -def find_vcdir(version): - """ - This is the customized version of - setuptools._distutils.msvc9compiler.find_vcvarsall method - """ - from setuptools._distutils import msvc9compiler as msvc9 - vsbase = msvc9.VS_BASE % version - try: - productdir = msvc9.Reg.get_value(rf"{vsbase}\Setup\VC", "productdir") - except KeyError: - productdir = None - - # trying Express edition - if productdir is None: - try: - hasattr(msvc9, VSEXPRESS_BASE) # noqa: VSEXPRESS_BASE get defined with msvc9 - except AttributeError: - pass - else: - vsbase = VSEXPRESS_BASE % version # noqa: VSEXPRESS_BASE get defined with msvc9 - try: - productdir = msvc9.Reg.get_value(rf"{vsbase}\Setup\VC", "productdir") - except KeyError: - productdir = None - log.debug("Unable to find productdir in registry") - - if not productdir or not os.path.isdir(productdir): - toolskey = f"VS{version:0.0f}0COMNTOOLS" - toolsdir = os.environ.get(toolskey, None) - - if toolsdir and os.path.isdir(toolsdir): - productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") - productdir = os.path.abspath(productdir) - if not os.path.isdir(productdir): - log.debug(f"{productdir} is not a valid directory") - return None - else: - log.debug(f"Env var {toolskey} is not set or invalid") - if not productdir: - log.debug("No productdir found") - return None - return productdir - - -def init_msvc_env(platform_arch, build_type): - from setuptools._distutils import msvc9compiler as msvc9 - - log.info(f"Searching MSVC compiler version {msvc9.VERSION}") - vcdir_path = find_vcdir(msvc9.VERSION) - if not vcdir_path: - raise DistutilsSetupError(f"Failed to find the MSVC compiler version {msvc9.VERSION} on " - "your system.") - else: - log.info(f"Found {vcdir_path}") - - log.info(f"Searching MSVC compiler {msvc9.VERSION} environment init script") - if platform_arch.startswith("32"): - vcvars_path = os.path.join(vcdir_path, "bin", "vcvars32.bat") - else: - vcvars_path = os.path.join(vcdir_path, "bin", "vcvars64.bat") - if not os.path.exists(vcvars_path): - vcvars_path = os.path.join(vcdir_path, "bin", "amd64", "vcvars64.bat") - if not os.path.exists(vcvars_path): - vcvars_path = os.path.join(vcdir_path, "bin", "amd64", "vcvarsamd64.bat") - - if not os.path.exists(vcvars_path): - # MSVC init script not found, try to find and init Windows SDK env - log.error("Failed to find the MSVC compiler environment init script " - "(vcvars.bat) on your system.") - winsdk_setenv(platform_arch, build_type) - return - else: - log.info(f"Found {vcvars_path}") - - # Get MSVC env - log.info(f"Using MSVC {msvc9.VERSION} in {vcvars_path}") - msvc_arch = "x86" if platform_arch.startswith("32") else "amd64" - log.info(f"Getting MSVC env for {msvc_arch} architecture") - vcvars_cmd = [vcvars_path, msvc_arch] - msvc_env = get_environment_from_batch_command(vcvars_cmd) - _msvc_paths = [msvc_env[k] for k in msvc_env if k.upper() == 'PATH'] - msvc_env_paths = os.pathsep.join(_msvc_paths).split(os.pathsep) - msvc_env_without_paths = {k: msvc_env[k] for k in msvc_env if k.upper() != 'PATH'} - - # Extend os.environ with MSVC env - log.info("Initializing MSVC env...") - update_env_path(msvc_env_paths) - for k in sorted(msvc_env_without_paths): - v = msvc_env_without_paths[k] - log.info(f"Inserting '{k} = {v}' to environment") - os.environ[k] = v - log.info("Done initializing MSVC env") - - def platform_cmake_options(as_tuple_list=False): result = [] if sys.platform == 'win32': @@ -230,16 +90,19 @@ def platform_cmake_options(as_tuple_list=False): def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False, make_writable_by_owner=False): - if _vars is not None: - src = src.format(**_vars) - dst = dst.format(**_vars) - - if not os.path.exists(src) and not force: + if isinstance(src, str): + src = Path(src.format(**_vars)) if _vars else Path(src) + if isinstance(dst, str): + dst = Path(dst.format(**_vars)) if _vars else Path(dst) + assert (isinstance(src, Path)) + assert (isinstance(dst, Path)) + + if not src.exists() and not force: log.info(f"**Skipping copy file\n {src} to\n {dst}\n Source does not exist") return - if not os.path.islink(src) or force_copy_symlink: - if os.path.isfile(dst): + if not src.is_symlink() or force_copy_symlink: + if dst.is_file(): src_stat = os.stat(src) dst_stat = os.stat(dst) if (src_stat.st_size == dst_stat.st_size @@ -247,23 +110,25 @@ def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False, log.info(f"{dst} is up to date.") return dst - log.info(f"Copying file\n {src} to\n {dst}.") + log.debug(f"Copying file\n {src} to\n {dst}.") shutil.copy2(src, dst) if make_writable_by_owner: make_file_writable_by_owner(dst) return dst - link_target_path = os.path.realpath(src) - if os.path.dirname(link_target_path) == os.path.dirname(src): - link_target = os.path.basename(link_target_path) - link_name = os.path.basename(src) - current_directory = os.getcwd() + # We use 'strict=False' to mimic os.path.realpath in case + # the directory doesn't exist. + link_target_path = src.resolve(strict=False) + if link_target_path.parent == src.parent: + link_target = Path(link_target_path.name) + link_name = Path(src.name) + current_directory = Path.cwd() try: - target_dir = dst if os.path.isdir(dst) else os.path.dirname(dst) + target_dir = dst if dst.is_dir() else dst.parent os.chdir(target_dir) - if os.path.exists(link_name): - if (os.path.islink(link_name) + if link_name.exists(): + if (link_name.is_symlink() and os.readlink(link_name) == link_target): log.info(f"Symlink already exists\n {link_name} ->\n {link_target}") return dst @@ -285,13 +150,13 @@ def makefile(dst, content=None, _vars=None): if _vars is not None: if content is not None: content = content.format(**_vars) - dst = dst.format(**_vars) + dst = Path(dst.format(**_vars)) log.info(f"Making file {dst}.") - dstdir = os.path.dirname(dst) - if not os.path.exists(dstdir): - os.makedirs(dstdir) + dstdir = dst.parent + if not dstdir.exists(): + dstdir.mkdir(parents=True) with open(dst, "wt") as f: if content is not None: @@ -301,30 +166,35 @@ def makefile(dst, content=None, _vars=None): def copydir(src, dst, _filter=None, ignore=None, force=True, recursive=True, _vars=None, dir_filter_function=None, file_filter_function=None, force_copy_symlinks=False): + if isinstance(src, str): + src = Path(src.format(**_vars)) if _vars else Path(src) + if isinstance(dst, str): + dst = Path(dst.format(**_vars)) if _vars else Path(dst) + assert (isinstance(src, Path)) + assert (isinstance(dst, Path)) + if _vars is not None: - src = src.format(**_vars) - dst = dst.format(**_vars) if _filter is not None: _filter = [i.format(**_vars) for i in _filter] if ignore is not None: ignore = [i.format(**_vars) for i in ignore] - if not os.path.exists(src) and not force: + if not src.exists() and not force: log.info(f"**Skipping copy tree\n {src} to\n {dst}\n Source does not exist. " f"filter={_filter}. ignore={ignore}.") return [] - log.info(f"Copying tree\n {src} to\n {dst}. filter={_filter}. ignore={ignore}.") + log.debug(f"Copying tree\n {src} to\n {dst}. filter={_filter}. ignore={ignore}.") names = os.listdir(src) results = [] copy_errors = [] for name in names: - srcname = os.path.join(src, name) - dstname = os.path.join(dst, name) + srcname = src / name + dstname = dst / name try: - if os.path.isdir(srcname): + if srcname.is_dir(): if (dir_filter_function and not dir_filter_function(name, src, srcname)): continue if recursive: @@ -336,8 +206,8 @@ def copydir(src, dst, _filter=None, ignore=None, force=True, recursive=True, _va or (_filter is not None and not filter_match(name, _filter)) or (ignore is not None and filter_match(name, ignore))): continue - if not os.path.exists(dst): - os.makedirs(dst) + if not dst.is_dir(): + dst.mkdir(parents=True) results.append(copyfile(srcname, dstname, True, _vars, force_copy_symlinks)) # catch the Error from the recursive copytree so that we can # continue with other files @@ -346,8 +216,8 @@ def copydir(src, dst, _filter=None, ignore=None, force=True, recursive=True, _va except EnvironmentError as why: copy_errors.append((srcname, dstname, str(why))) try: - if os.path.exists(dst): - shutil.copystat(src, dst) + if dst.exists(): + shutil.copystat(str(src), str(dst)) except OSError as why: if WindowsError is not None and isinstance(why, WindowsError): # Copying file access times may fail on Windows @@ -394,7 +264,7 @@ def run_process(args, initial_env=None): No output is captured. """ command = " ".join([(" " in x and f'"{x}"' or x) for x in args]) - log.info(f"In directory {os.getcwd()}:\n\tRunning command: {command}") + log.debug(f"In directory {Path.cwd()}:\n\tRunning command: {command}") if initial_env is None: initial_env = os.environ @@ -406,62 +276,10 @@ def run_process(args, initial_env=None): return exit_code -def get_environment_from_batch_command(env_cmd, initial=None): - """ - Take a command (either a single command or list of arguments) - and return the environment created after running that command. - Note that if the command must be a batch file or .cmd file, or the - changes to the environment will not be captured. - - If initial is supplied, it is used as the initial environment passed - to the child process. - """ - - def validate_pair(ob): - if len(ob) != 2: - log.error(f"Unexpected result: {ob}") - return False - return True - - def consume(it): - try: - while True: - next(it) - except StopIteration: - pass - - if not isinstance(env_cmd, (list, tuple)): - env_cmd = [env_cmd] - # construct the command that will alter the environment - env_cmd = subprocess.list2cmdline(env_cmd) - # create a tag so we can tell in the output when the proc is done - tag = 'Done running command' - # construct a cmd.exe command to do accomplish this - cmd = f'cmd.exe /E:ON /V:ON /s /c "{env_cmd} && echo "{tag}" && set"' - # launch the process - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial) - # parse the output sent to stdout - lines = proc.stdout - # make sure the lines are strings - lines = [s.decode() for s in lines] - # consume whatever output occurs until the tag is reached - consume(itertools.takewhile(lambda l: tag not in l, lines)) - # define a way to handle each KEY=VALUE line - # parse key/values into pairs - pairs = [l.rstrip().split('=', 1) for l in lines] - # make sure the pairs are valid - valid_pairs = filter(validate_pair, pairs) - # construct a dictionary of the pairs - result = dict(valid_pairs) - # let the process finish - proc.communicate() - return result - - def back_tick(cmd, ret_err=False): """ - Run command `cmd`, return stdout, or stdout, stderr, - return_code if `ret_err` is True. + Run command `cmd`, return stdout, or (stdout, stderr, + return_code) if `ret_err` is True. Parameters ---------- @@ -485,22 +303,20 @@ def back_tick(cmd, ret_err=False): Raises RuntimeError if command returns non-zero exit code when ret_err isn't set. """ - proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) - out, err = proc.communicate() - if not isinstance(out, str): - # python 3 - out = out.decode() - err = err.decode() - retcode = proc.returncode - if retcode is None and not ret_err: - proc.terminate() - raise RuntimeError(f"{cmd} process did not terminate") - if retcode != 0 and not ret_err: - raise RuntimeError(f"{cmd} process returned code {retcode}\n*** {err}") - out = out.strip() + with subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) as proc: + out_bytes, err_bytes = proc.communicate() + out = out_bytes.decode().strip() + err = err_bytes.decode().strip() + retcode = proc.returncode + if retcode is None and not ret_err: + proc.terminate() + raise RuntimeError(f"{cmd} process did not terminate") + if retcode != 0 and not ret_err: + raise RuntimeError(f"{cmd} process returned code {retcode}\n*** {err}") if not ret_err: return out - return out, err.strip(), retcode + return out, err, retcode MACOS_OUTNAME_RE = re.compile(r'\(compatibility version [\d.]+, current version [\d.]+\)') @@ -657,7 +473,7 @@ def find_glob_in_path(pattern): pattern += '.exe' for path in os.environ.get('PATH', '').split(os.pathsep): - for match in glob.glob(os.path.join(path, pattern)): + for match in glob.glob(str(Path(path) / pattern)): result.append(match) return result @@ -682,7 +498,7 @@ def detect_clang(): clang_dir = os.environ.get(source, None) if not clang_dir: raise OSError("clang not found") - return (clang_dir, source) + return (Path(clang_dir), source) _7z_binary = None @@ -708,8 +524,8 @@ def download_and_extract_7z(fileurl, target): outputDir = f"-o{target}" if not _7z_binary: if sys.platform == "win32": - candidate = "c:\\Program Files\\7-Zip\\7z.exe" - if os.path.exists(candidate): + candidate = Path("c:\\Program Files\\7-Zip\\7z.exe") + if candidate.exists(): _7z_binary = candidate if not _7z_binary: _7z_binary = '7z' @@ -839,7 +655,8 @@ def _ldd_ldso(executable_path): # Choose appropriate runtime dynamic linker. for rtld in rtld_list: - if os.path.isfile(rtld) and os.access(rtld, os.X_OK): + rtld = Path(rtld) + if rtld.is_file() and os.access(rtld, os.X_OK): (_, _, code) = back_tick(rtld, True) # Code 127 is returned by ld.so when called without any # arguments (some kind of sanity check I guess). @@ -885,7 +702,7 @@ def ldd(executable_path): result = _ldd_ldd(executable_path) except RuntimeError as e: message = f"ldd: Falling back to ld.so ({str(e)})" - log.warn(message) + log.warning(message) if not result: result = _ldd_ldso(executable_path) return result @@ -893,8 +710,8 @@ def ldd(executable_path): def find_files_using_glob(path, pattern): """ Returns list of files that matched glob `pattern` in `path`. """ - final_pattern = os.path.join(path, pattern) - maybe_files = glob.glob(final_pattern) + final_pattern = Path(path) / pattern + maybe_files = glob.glob(str(final_pattern)) return maybe_files @@ -911,14 +728,16 @@ def find_qt_core_library_glob(lib_dir): # ldd for the specified platforms. # This has less priority because ICU libs are not used in the default # Qt configuration build. +# Note: Uses ldd to query shared library dependencies and thus does not +# work for cross builds. def copy_icu_libs(patchelf, destination_lib_dir): """ Copy ICU libraries that QtCore depends on, to given `destination_lib_dir`. """ - qt_core_library_path = find_qt_core_library_glob(destination_lib_dir) + qt_core_library_path = Path(find_qt_core_library_glob(destination_lib_dir)) - if not qt_core_library_path or not os.path.exists(qt_core_library_path): + if not qt_core_library_path or not qt_core_library_path.exists(): raise RuntimeError(f"QtCore library does not exist at path: {qt_core_library_path}. " "Failed to copy ICU libraries.") @@ -937,14 +756,15 @@ def copy_icu_libs(patchelf, destination_lib_dir): paths = ldd_get_paths_for_dependencies(icu_regex, dependencies=dependencies) if not paths: raise RuntimeError("Failed to find the necessary ICU libraries required by QtCore.") - log.info('Copying the detected ICU libraries required by QtCore.') + log.debug('Copying the detected ICU libraries required by QtCore.') - if not os.path.exists(destination_lib_dir): - os.makedirs(destination_lib_dir) + destination_lib_dir = Path(destination_lib_dir) + if not destination_lib_dir.exists(): + destination_lib_dir.mkdir(parents=True) for path in paths: - basename = os.path.basename(path) - destination = os.path.join(destination_lib_dir, basename) + basename = Path(path).name + destination = destination_lib_dir / basename copyfile(path, destination, force_copy_symlink=True) # Patch the ICU libraries to contain the $ORIGIN rpath # value, so that only the local package libraries are used. @@ -969,7 +789,7 @@ def linux_run_read_elf(executable_path): def linux_set_rpaths(patchelf, executable_path, rpath_string): """ Patches the `executable_path` with a new rpath string. """ - cmd = [patchelf, '--set-rpath', rpath_string, executable_path] + cmd = [str(patchelf), '--set-rpath', str(rpath_string), str(executable_path)] if run_process(cmd) != 0: raise RuntimeError(f"Error patching rpath in {executable_path}") @@ -1073,6 +893,7 @@ def linux_fix_rpaths_for_library(patchelf, executable_path, qt_rpath, override=F existing_rpaths = linux_get_rpaths(executable_path) rpaths.extend(existing_rpaths) + qt_rpath = str(qt_rpath) if linux_needs_qt_rpath(executable_path) and qt_rpath not in existing_rpaths: rpaths.append(qt_rpath) @@ -1111,26 +932,9 @@ def get_python_dict(python_script_path): raise -def install_pip_package_from_url_specifier(env_pip, url, upgrade=True): - args = [env_pip, "install", url] - if upgrade: - args.append("--upgrade") - args.append(url) - run_instruction(args, f"Failed to install {url}") - - -def install_pip_dependencies(env_pip, packages, upgrade=True): - for p in packages: - args = [env_pip, "install"] - if upgrade: - args.append("--upgrade") - args.append(p) - run_instruction(args, f"Failed to install {p}") - - def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _pExe = "python" - _env = f"env{python_ver}" + _env = f"{os.environ.get('PYSIDE_VIRTUALENV') or 'env'+python_ver}" env_python = f"{_env}/bin/python" env_pip = f"{_env}/bin/pip" @@ -1142,22 +946,22 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): if python_ver.startswith("3"): var = f"PYTHON{python_ver}-32_PATH" log.info(f"Try to find python from {var} env variable") - _path = os.getenv(var, "") - _pExe = os.path.join(_path, "python.exe") - if not os.path.isfile(_pExe): - log.warn(f"Can't find python.exe from {_pExe}, using default python3") - _pExe = os.path.join(os.getenv("PYTHON3_32_PATH"), "python.exe") + _path = Path(os.getenv(var, "")) + _pExe = _path / "python.exe" + if not _pExe.is_file(): + log.warning(f"Can't find python.exe from {_pExe}, using default python3") + _pExe = Path(os.getenv("PYTHON3_32_PATH")) / "python.exe" else: - _pExe = os.path.join(os.getenv("PYTHON2_32_PATH"), "python.exe") + _pExe = Path(os.getenv("PYTHON2_32_PATH")) / "python.exe" else: if python_ver.startswith("3"): var = f"PYTHON{python_ver}-64_PATH" log.info(f"Try to find python from {var} env variable") - _path = os.getenv(var, "") - _pExe = os.path.join(_path, "python.exe") - if not os.path.isfile(_pExe): - log.warn(f"Can't find python.exe from {_pExe}, using default python3") - _pExe = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe") + _path = Path(os.getenv(var, "")) + _pExe = _path / "python.exe" + if not _pExe.is_file(): + log.warning(f"Can't find python.exe from {_pExe}, using default python3") + _pExe = Path(os.getenv("PYTHON3_PATH")) / "python.exe" env_python = f"{_env}\\Scripts\\python.exe" env_pip = f"{_env}\\Scripts\\pip.exe" else: @@ -1167,7 +971,7 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): except Exception as e: print(f"Exception {type(e).__name__}: {e}") _pExe = "python3" - return(_pExe, _env, env_pip, env_python) + return (_pExe, _env, env_pip, env_python) def run_instruction(instruction, error, initial_env=None): @@ -1180,23 +984,6 @@ def run_instruction(instruction, error, initial_env=None): exit(result) -def acceptCITestConfiguration(hostOS, hostOSVer, targetArch, compiler): - # Disable unsupported CI configs for now - # NOTE: String must match with QT CI's storagestruct thrift - if (hostOSVer in ["WinRT_10", "WebAssembly", "Ubuntu_18_04", "Android_ANY"] - or hostOSVer.startswith("SLES_")): - log.info("Disabled {hostOSVer} from Coin configuration") - return False - # With 5.11 CI will create two sets of release binaries, - # one with msvc 2015 and one with msvc 2017 - # we shouldn't release the 2015 version. - # BUT, 32 bit build is done only on msvc 2015... - if compiler in ["MSVC2015"] and targetArch in ["X86_64"]: - log.warn(f"Disabled {compiler} to {targetArch} from Coin configuration") - return False - return True - - def get_ci_qtpaths_path(ci_install_dir, ci_host_os): qtpaths_path = f"--qtpaths={ci_install_dir}" if ci_host_os == "MacOS": @@ -1230,6 +1017,40 @@ def parse_cmake_conf_assignments_by_key(source_dir): return d +def _configure_failure_message(project_path, cmd, return_code, output, error, env): + """Format a verbose message about configure_cmake_project() failures.""" + cmd_string = ' '.join(cmd) + error_text = indent(error.strip(), " ") + output_text = indent(output.strip(), " ") + result = dedent(f""" + Failed to configure CMake project: '{project_path}' + Configure args were: + {cmd_string} + Return code: {return_code} + """) + + first = True + for k, v in env.items(): + if k.startswith("CMAKE"): + if first: + result += "Environment:\n" + first = False + result += f" {k}={v}\n" + + result += f"\nwith error:\n{error_text}\n" + + CMAKE_CMAKEOUTPUT_LOG_PATTERN = r'See also "([^"]+CMakeOutput\.log)"\.' + cmakeoutput_log_match = re.search(CMAKE_CMAKEOUTPUT_LOG_PATTERN, output) + if cmakeoutput_log_match: + cmakeoutput_log = Path(cmakeoutput_log_match.group(1)) + if cmakeoutput_log.is_file(): + log = indent(cmakeoutput_log.read_text().strip(), " ") + result += f"CMakeOutput.log:\n{log}\n" + + result += f"Output:\n{output_text}\n" + return result + + def configure_cmake_project(project_path, cmake_path, build_path=None, @@ -1257,23 +1078,18 @@ def configure_cmake_project(project_path, for arg, value in cmake_cache_args: cmd.extend([f'-D{arg}={value}']) - cmd_string = ' '.join(cmd) - # FIXME Python 3.7: Use subprocess.run() - proc = subprocess.Popen(cmd, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - shell=False, - cwd=build_path, - universal_newlines=True) - output, error = proc.communicate() - proc.wait() + cmd = [str(i) for i in cmd] + + proc = subprocess.run(cmd, shell=False, cwd=build_path, + capture_output=True, universal_newlines=True) return_code = proc.returncode + output = proc.stdout + error = proc.stderr if return_code != 0: - raise RuntimeError(f"\nFailed to configure CMake project \n " - f"'{project_path}' \n with error: \n {error}\n " - f"Return code: {return_code}\n" - f"Configure args were:\n {cmd_string}") + m = _configure_failure_message(project_path, cmd, return_code, + output, error, os.environ) + raise RuntimeError(m) if clean_temp_dir: remove_tree(build_path) @@ -1295,3 +1111,53 @@ def parse_cmake_project_message_info(output): value = found.group(3).strip() result[category][key] = str(value) return result + + +def available_pyside_tools(qt_tools_path: Path, package_for_wheels: bool = False): + pyside_tools = PYSIDE_PYTHON_TOOLS.copy() + + if package_for_wheels: + # Qt wrappers in build/{python_env_name}/package_for_wheels/PySide6 + bin_path = qt_tools_path + else: + bin_path = qt_tools_path / "bin" + + def tool_exist(tool_path: Path): + if tool_path.exists(): + return True + else: + log.warning(f"{tool_path} not found. pyside-{tool_path.name} not included.") + return False + + if sys.platform == 'win32': + pyside_tools.extend([tool for tool in PYSIDE_WINDOWS_BIN_TOOLS + if tool_exist(bin_path / f"{tool}.exe")]) + else: + lib_exec_path = qt_tools_path / "Qt" / "libexec" if package_for_wheels \ + else qt_tools_path / "libexec" + pyside_tools.extend([tool for tool in PYSIDE_UNIX_LIBEXEC_TOOLS + if tool_exist(lib_exec_path / tool)]) + if sys.platform == 'darwin': + def name_to_path(name): + return f"{name.capitalize()}.app/Contents/MacOS/{name.capitalize()}" + + pyside_tools.extend([tool for tool in PYSIDE_UNIX_BIN_TOOLS + if tool_exist(bin_path / tool)]) + pyside_tools.extend([tool for tool in PYSIDE_UNIX_BUNDLED_TOOLS + if tool_exist(bin_path / name_to_path(tool))]) + else: + pyside_tools.extend([tool for tool in PYSIDE_LINUX_BIN_TOOLS + if tool_exist(bin_path / tool)]) + + return pyside_tools + + +def copy_qt_metatypes(destination_qt_dir, _vars): + """Copy the Qt metatypes files which changed location in 6.5""" + # <qt>/[lib]?/metatypes/* -> <setup>/{st_package_name}/Qt/[lib]?/metatypes + qt_meta_types_dir = "{qt_metatypes_dir}".format(**_vars) + qt_prefix_dir = "{qt_prefix_dir}".format(**_vars) + rel_meta_data_dir = os.fspath(Path(qt_meta_types_dir).relative_to(qt_prefix_dir)) + copydir(qt_meta_types_dir, destination_qt_dir / rel_meta_data_dir, + _filter=["*.json"], + recursive=False, _vars=_vars, force_copy_symlinks=True) |