diff options
| author | Jaime Resano <Jaime.Resano-Aisa@qt.io> | 2025-01-21 11:17:07 +0100 |
|---|---|---|
| committer | Jaime Resano <Jaime.Resano-Aisa@qt.io> | 2025-01-22 22:46:43 +0100 |
| commit | 23b7ff61fb899ef8ae305df134d2c0968dce1fa2 (patch) | |
| tree | 15f5a0afdf5335b883f531d5824d8994306625b4 /sources/pyside-tools/project_lib/project_data.py | |
| parent | 30f8707e1c677e3a42a93d1b2b17bd563b9de847 (diff) | |
pyside6-deploy: 3. Rename project folder to project_lib
This is a refactor in order to improve the code clarity. In the testing
of the pyside6-project command, importlib.import_module is used to
import the project_lib folder. Currently,
importlib.import_module("project") is ambiguous because it may refer
to both the file and the folder. It chooses the folder over the file.
Task-number: PYSIDE-1612
Pick-to: 6.8
Change-Id: I8903ea9d2112cf2eb7a68d0e302d3c74edcf2c22
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'sources/pyside-tools/project_lib/project_data.py')
| -rw-r--r-- | sources/pyside-tools/project_lib/project_data.py | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/sources/pyside-tools/project_lib/project_data.py b/sources/pyside-tools/project_lib/project_data.py new file mode 100644 index 000000000..b9c428a08 --- /dev/null +++ b/sources/pyside-tools/project_lib/project_data.py @@ -0,0 +1,244 @@ +# Copyright (C) 2022 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only +from __future__ import annotations + +import json +import os +import subprocess +import sys +from pathlib import Path +from . import (METATYPES_JSON_SUFFIX, PROJECT_FILE_SUFFIX, TRANSLATION_SUFFIX, + qt_metatype_json_dir, MOD_CMD, QML_IMPORT_MAJOR_VERSION, + QML_IMPORT_MINOR_VERSION, QML_IMPORT_NAME, QT_MODULES) + + +def is_python_file(file: Path) -> bool: + return (file.suffix == ".py" + or sys.platform == "win32" and file.suffix == ".pyw") + + +class ProjectData: + def __init__(self, project_file: Path) -> None: + """Parse the project.""" + self._project_file = project_file.resolve() + self._sub_projects_files: list[Path] = [] + + # All sources except subprojects + self._files: list[Path] = [] + # QML files + self._qml_files: list[Path] = [] + # Python files + self.main_file: Path = None + self._python_files: list[Path] = [] + # ui files + self._ui_files: list[Path] = [] + # qrc files + self._qrc_files: list[Path] = [] + # ts files + self._ts_files: list[Path] = [] + + with project_file.open("r") as pyf: + pyproject = json.load(pyf) + for f in pyproject["files"]: + file = Path(project_file.parent / f) + if file.suffix == PROJECT_FILE_SUFFIX: + self._sub_projects_files.append(file) + else: + self._files.append(file) + if file.suffix == ".qml": + self._qml_files.append(file) + elif is_python_file(file): + if file.stem == "main": + self.main_file = file + self._python_files.append(file) + elif file.suffix == ".ui": + self._ui_files.append(file) + elif file.suffix == ".qrc": + self._qrc_files.append(file) + elif file.suffix == TRANSLATION_SUFFIX: + self._ts_files.append(file) + + if not self.main_file: + self._find_main_file() + + @property + def project_file(self): + return self._project_file + + @property + def files(self): + return self._files + + @property + def main_file(self): + return self._main_file + + @main_file.setter + def main_file(self, main_file): + self._main_file = main_file + + @property + def python_files(self): + return self._python_files + + @property + def ui_files(self): + return self._ui_files + + @property + def qrc_files(self): + return self._qrc_files + + @property + def qml_files(self): + return self._qml_files + + @property + def ts_files(self): + return self._ts_files + + @property + def sub_projects_files(self): + return self._sub_projects_files + + def _find_main_file(self) -> str: + """Find the entry point file containing the main function""" + + def is_main(file): + return "__main__" in file.read_text(encoding="utf-8") + + if not self.main_file: + for python_file in self.python_files: + if is_main(python_file): + self.main_file = python_file + return str(python_file) + + # __main__ not found + print( + "Python file with main function not found. Add the file to" f" {self.project_file}", + file=sys.stderr, + ) + sys.exit(1) + + +class QmlProjectData: + """QML relevant project data.""" + + def __init__(self): + self._import_name: str = "" + self._import_major_version: int = 0 + self._import_minor_version: int = 0 + self._qt_modules: list[str] = [] + + def registrar_options(self): + result = [ + "--import-name", + self._import_name, + "--major-version", + str(self._import_major_version), + "--minor-version", + str(self._import_minor_version), + ] + if self._qt_modules: + # Add Qt modules as foreign types + foreign_files: list[str] = [] + meta_dir = qt_metatype_json_dir() + for mod in self._qt_modules: + mod_id = mod[2:].lower() + pattern = f"qt6{mod_id}_*" + if sys.platform != "win32": + pattern += "_" # qt6core_debug_metatypes.json (Linux) + pattern += METATYPES_JSON_SUFFIX + for f in meta_dir.glob(pattern): + foreign_files.append(os.fspath(f)) + break + if foreign_files: + foreign_files_str = ",".join(foreign_files) + result.append(f"--foreign-types={foreign_files_str}") + return result + + @property + def import_name(self): + return self._import_name + + @import_name.setter + def import_name(self, n): + self._import_name = n + + @property + def import_major_version(self): + return self._import_major_version + + @import_major_version.setter + def import_major_version(self, v): + self._import_major_version = v + + @property + def import_minor_version(self): + return self._import_minor_version + + @import_minor_version.setter + def import_minor_version(self, v): + self._import_minor_version = v + + @property + def qt_modules(self): + return self._qt_modules + + @qt_modules.setter + def qt_modules(self, v): + self._qt_modules = v + + def __str__(self) -> str: + vmaj = self._import_major_version + vmin = self._import_minor_version + return f'"{self._import_name}" v{vmaj}.{vmin}' + + def __bool__(self) -> bool: + return len(self._import_name) > 0 and self._import_major_version > 0 + + +def _has_qml_decorated_class(class_list: list) -> bool: + """Check for QML-decorated classes in the moc json output.""" + for d in class_list: + class_infos = d.get("classInfos") + if class_infos: + for e in class_infos: + if "QML" in e["name"]: + return True + return False + + +def check_qml_decorators(py_file: Path) -> tuple[bool, QmlProjectData]: + """Check if a Python file has QML-decorated classes by running a moc check + and return whether a class was found and the QML data.""" + data = None + try: + cmd = [MOD_CMD, "--quiet", os.fspath(py_file)] + with subprocess.Popen(cmd, stdout=subprocess.PIPE) as proc: + data = json.load(proc.stdout) + proc.wait() + except Exception as e: + t = type(e).__name__ + print(f"{t}: running {MOD_CMD} on {py_file}: {e}", file=sys.stderr) + sys.exit(1) + + qml_project_data = QmlProjectData() + if not data: + return (False, qml_project_data) # No classes in file + + first = data[0] + class_list = first["classes"] + has_class = _has_qml_decorated_class(class_list) + if has_class: + v = first.get(QML_IMPORT_NAME) + if v: + qml_project_data.import_name = v + v = first.get(QML_IMPORT_MAJOR_VERSION) + if v: + qml_project_data.import_major_version = v + qml_project_data.import_minor_version = first.get(QML_IMPORT_MINOR_VERSION) + v = first.get(QT_MODULES) + if v: + qml_project_data.qt_modules = v + return (has_class, qml_project_data) |
