|
| 1 | +# Copyright (C) 2022 The Qt Company Ltd. |
| 2 | +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 3 | +from __future__ import annotations |
| 4 | + |
| 5 | +import calendar |
| 6 | +import datetime |
| 7 | +import os |
| 8 | +import site |
| 9 | +import sys |
| 10 | +from pathlib import Path |
| 11 | + |
| 12 | +from build_scripts.options import has_option, option_value |
| 13 | +from build_scripts.utils import (parse_cmake_conf_assignments_by_key, |
| 14 | + remove_tree, run_instruction) |
| 15 | + |
| 16 | + |
| 17 | +class CI: |
| 18 | + def __init__(self): |
| 19 | + # Values must match COIN thrift |
| 20 | + self.HOST_OS = option_value("os") |
| 21 | + self.TARGET_OS = option_value("targetOs") |
| 22 | + self.HOST_ARCH = option_value("hostArch") |
| 23 | + self.TARGET_ARCH = option_value("targetArch") |
| 24 | + self.HOST_OS_VER = option_value("osVer") |
| 25 | + self.ENV_INSTALL_DIR = option_value("instdir") |
| 26 | + self.ENV_AGENT_DIR = option_value("agentdir") or "." |
| 27 | + self.COMPILER = option_value("compiler") |
| 28 | + self.USE_SCCACHE = option_value("compiler-launcher") |
| 29 | + self.INTEGRATION_ID = option_value("coinIntegrationId") or str( |
| 30 | + calendar.timegm(datetime.datetime.now().timetuple()) |
| 31 | + ) |
| 32 | + self.FEATURES = [] |
| 33 | + _ci_features = option_value("features") |
| 34 | + if _ci_features is not None: |
| 35 | + for f in _ci_features.split(", "): |
| 36 | + self.FEATURES.append(f) |
| 37 | + self.RELEASE_CONF = has_option("packaging") |
| 38 | + self.TEST_PHASE = option_value("phase") |
| 39 | + if self.TEST_PHASE not in ["ALL", "BUILD"]: |
| 40 | + self.TEST_PHASE = "ALL" |
| 41 | + |
| 42 | + |
| 43 | +def get_ci_exe_path(ci_install_dir, ci_host_os, qtexe): |
| 44 | + """ |
| 45 | + qtexe can only be 'qmake' or 'qtpaths' |
| 46 | + """ |
| 47 | + ext = "" |
| 48 | + if ci_host_os == "Windows": |
| 49 | + ext = ".exe" |
| 50 | + |
| 51 | + _path = Path(ci_install_dir) / "bin" / f"{qtexe}{ext}" |
| 52 | + |
| 53 | + return f"--{qtexe}={_path}" |
| 54 | + |
| 55 | + |
| 56 | +def get_env_or_raise(name: str) -> str: |
| 57 | + o = os.getenv(name) |
| 58 | + if o is None: |
| 59 | + raise Exception(f"Variable not defined: {name}") |
| 60 | + return o |
| 61 | + |
| 62 | + |
| 63 | +def get_qtci_virtualenv(python_ver, log, host, host_arch, target_arch): |
| 64 | + _exe = "python" |
| 65 | + _env = os.environ.get("PYSIDE_VIRTUALENV") or f"env{python_ver}" |
| 66 | + env_python = f"{_env}/bin/python" |
| 67 | + env_pip = f"{_env}/bin/pip" |
| 68 | + |
| 69 | + if host == "Windows": |
| 70 | + log.info("New virtualenv to build {target_arch} in {host_arch} host") |
| 71 | + _exe = "python.exe" |
| 72 | + if python_ver.startswith("3"): |
| 73 | + var = f"PYTHON{python_ver}-64_PATH" |
| 74 | + log.info(f"Try to find python from {var} env variable") |
| 75 | + _path = Path(os.getenv(var, "")) |
| 76 | + _exe = _path / "python.exe" |
| 77 | + if not _exe.is_file(): |
| 78 | + log.warning(f"Can't find python.exe from {_exe}, using default python3") |
| 79 | + _exe = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe" |
| 80 | + env_python = rf"{_env}\Scripts\python.exe" |
| 81 | + env_pip = rf"{_env}\Scripts\pip.exe" |
| 82 | + else: |
| 83 | + _exe = f"python{python_ver}" |
| 84 | + try: |
| 85 | + run_instruction([_exe, "--version"], f"Failed to guess python version {_exe}") |
| 86 | + except Exception as e: |
| 87 | + print(f"Exception {type(e).__name__}: {e}") |
| 88 | + _exe = "python3" |
| 89 | + return (_exe, _env, env_pip, env_python) |
| 90 | + |
| 91 | + |
| 92 | +def get_current_script_path(): |
| 93 | + """Returns the absolute path containing this script.""" |
| 94 | + try: |
| 95 | + this_file = __file__ |
| 96 | + except NameError: |
| 97 | + this_file = sys.argv[0] |
| 98 | + this_file = Path(this_file).resolve() |
| 99 | + return this_file.parents[0] |
| 100 | + |
| 101 | + |
| 102 | +def is_snapshot_build(): |
| 103 | + """ |
| 104 | + Returns True if project needs to be built with --snapshot-build |
| 105 | +
|
| 106 | + This is true if the version found in .cmake.conf is not a |
| 107 | + pre-release version (no alphas, betas). |
| 108 | +
|
| 109 | + This eliminates the need to remove the --snapshot-build option |
| 110 | + on a per-release branch basis (less things to remember to do |
| 111 | + for a release). |
| 112 | + """ |
| 113 | + # This returns pyside-setup/coin/ so we go one level down |
| 114 | + # to get the root of the repo |
| 115 | + setup_script_dir = get_current_script_path() |
| 116 | + pyside_project_dir = setup_script_dir / ".." / "sources" / "pyside6" |
| 117 | + |
| 118 | + d = parse_cmake_conf_assignments_by_key(str(pyside_project_dir)) |
| 119 | + release_version_type = d.get("pyside_PRE_RELEASE_VERSION_TYPE") |
| 120 | + pre_release_version = d.get("pyside_PRE_RELEASE_VERSION") |
| 121 | + if pre_release_version and release_version_type: |
| 122 | + return True |
| 123 | + return False |
| 124 | + |
| 125 | + |
| 126 | +def get_architecture(ci): |
| 127 | + return "32" if ci.TARGET_ARCH == "X86" else "64" |
| 128 | + |
| 129 | + |
| 130 | +def get_python_version(ci): |
| 131 | + python_ver = "3" |
| 132 | + if ci.TARGET_OS == "Linux" and ci.HOST_ARCH != "aarch64": |
| 133 | + python_ver = "3.11" |
| 134 | + elif ci.TARGET_OS == "Windows": |
| 135 | + python_ver = "3.10.0" |
| 136 | + return python_ver |
| 137 | + |
| 138 | + |
| 139 | +def remove_variables(vars): |
| 140 | + for env_var in vars: |
| 141 | + if os.environ.get(env_var): |
| 142 | + del os.environ[env_var] |
| 143 | + |
| 144 | + |
| 145 | +def setup_virtualenv(python, exe, env, pip, log): |
| 146 | + run_instruction( |
| 147 | + [str(python), "-m", "pip", "install", "--user", "virtualenv==20.7.2"], |
| 148 | + "Failed to pin virtualenv", |
| 149 | + ) |
| 150 | + # installing to user base might not be in PATH by default. |
| 151 | + env_path = Path(str(site.USER_BASE)) / "bin" |
| 152 | + v_env = env_path / "virtualenv" |
| 153 | + if sys.platform == "win32": |
| 154 | + env_path = os.path.join(site.USER_BASE, "Scripts") |
| 155 | + v_env = os.path.join(env_path, "virtualenv.exe") |
| 156 | + try: |
| 157 | + run_instruction([str(v_env), "--version"], "Using default virtualenv") |
| 158 | + except Exception as e: |
| 159 | + log.info("Failed to use the default virtualenv") |
| 160 | + log.info(f"{type(e).__name__}: {e}") |
| 161 | + v_env = "virtualenv" |
| 162 | + run_instruction([str(v_env), "-p", str(exe), str(env)], "Failed to create virtualenv") |
| 163 | + # Pip is always upgraded when CI template is provisioned, |
| 164 | + # upgrading it in later phase may cause perm issue |
| 165 | + run_instruction( |
| 166 | + [str(pip), "install", "-r", "requirements.txt"], "Failed to install dependencies" |
| 167 | + ) |
| 168 | + |
| 169 | + |
| 170 | +def call_setup(python_ver, ci, phase, log, buildnro=0): |
| 171 | + print("call_setup") |
| 172 | + print("python_ver", python_ver) |
| 173 | + print("phase", phase) |
| 174 | + exe, env, pip, env_python = get_qtci_virtualenv( |
| 175 | + python_ver, log, ci.HOST_OS, ci.HOST_ARCH, ci.TARGET_ARCH |
| 176 | + ) |
| 177 | + |
| 178 | + if phase not in ["BUILD", "TEST"]: |
| 179 | + sys.exit(1) |
| 180 | + |
| 181 | + remove_tree(env, True) |
| 182 | + # Pinning the virtualenv before creating one |
| 183 | + # Use pip3 if possible while pip seems to install the virtualenv to wrong dir in some OS |
| 184 | + python = "python3" |
| 185 | + if sys.platform == "win32": |
| 186 | + python = Path(get_env_or_raise("PYTHON3_PATH")) / "python.exe" |
| 187 | + |
| 188 | + if phase == "BUILD": |
| 189 | + setup_virtualenv(python, exe, env, pip, log) |
| 190 | + elif phase == "TEST": |
| 191 | + |
| 192 | + if ci.HOST_OS == "MacOS" and ci.HOST_ARCH == "ARM64": |
| 193 | + v_env = "virtualenv" |
| 194 | + run_instruction([str(v_env), "-p", str(exe), str(env)], "Failed to create virtualenv") |
| 195 | + run_instruction( |
| 196 | + [pip, "install", "-r", "requirements.txt"], "Failed to install dependencies" |
| 197 | + ) |
| 198 | + else: |
| 199 | + setup_virtualenv(python, exe, env, pip, log) |
| 200 | + # Install distro to replace missing platform.linux_distribution() in python3.8 |
| 201 | + run_instruction([pip, "install", "distro"], "Failed to install distro") |
| 202 | + |
| 203 | + if phase == "BUILD": |
| 204 | + cmd = [ |
| 205 | + env_python, |
| 206 | + "-u", |
| 207 | + "setup.py", |
| 208 | + "build", |
| 209 | + "--standalone", |
| 210 | + "--unity", |
| 211 | + "--build-tests", |
| 212 | + "--log-level=verbose", |
| 213 | + "--limited-api=yes", |
| 214 | + ] |
| 215 | + |
| 216 | + if ci.TARGET_ARCH == "X86_64-ARM64": |
| 217 | + cmd += ["--macos-arch='x86_64;arm64'"] |
| 218 | + |
| 219 | + if ci.USE_SCCACHE: |
| 220 | + cmd += [f"--compiler-launcher={ci.USE_SCCACHE}"] |
| 221 | + |
| 222 | + if is_snapshot_build(): |
| 223 | + cmd += ["--snapshot-build"] |
| 224 | + |
| 225 | + qtpaths_path = get_ci_exe_path(ci.ENV_INSTALL_DIR, ci.HOST_OS, "qtpaths") |
| 226 | + cmd.append(qtpaths_path) |
| 227 | + |
| 228 | + # Due to certain older CMake versions generating very long paths |
| 229 | + # (at least with CMake 3.6.2) when using the export() function, |
| 230 | + # pass the shorter paths option on Windows so we don't hit |
| 231 | + # the path character length limit (260). |
| 232 | + if ci.HOST_OS == "Windows": |
| 233 | + cmd += ["--shorter-paths"] |
| 234 | + |
| 235 | + cmd += ["--package-timestamp=" + ci.INTEGRATION_ID] |
| 236 | + |
| 237 | + env = os.environ |
| 238 | + run_instruction(cmd, "Failed to run setup.py for build", initial_env=env) |
| 239 | + elif phase == "TEST": |
| 240 | + cmd = [ |
| 241 | + env_python, |
| 242 | + "testrunner.py", |
| 243 | + "test", |
| 244 | + "--blacklist", |
| 245 | + "build_history/blacklist.txt", |
| 246 | + f"--buildno={buildnro}", |
| 247 | + ] |
| 248 | + run_instruction(cmd, "Failed to run testrunner.py") |
| 249 | + |
| 250 | + qmake_path = get_ci_exe_path(ci.ENV_INSTALL_DIR, ci.HOST_OS, "qmake") |
| 251 | + |
| 252 | + # Try to install built wheels, and build some buildable examples. |
| 253 | + if ci.RELEASE_CONF: |
| 254 | + wheel_tester_path = os.path.join("testing", "wheel_tester.py") |
| 255 | + # Run the test for the new set of wheels |
| 256 | + cmd = [env_python, wheel_tester_path, qmake_path, "--wheels-dir=dist", "--new"] |
| 257 | + run_instruction(cmd, "Error while running wheel_tester.py on new wheels") |
0 commit comments