First upload, 18 controller version
This commit is contained in:
348
.venv_nopip/Lib/site-packages/PySide6/scripts/project.py
Normal file
348
.venv_nopip/Lib/site-packages/PySide6/scripts/project.py
Normal file
@@ -0,0 +1,348 @@
|
||||
# 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 sys
|
||||
import os
|
||||
from pathlib import Path
|
||||
from argparse import ArgumentParser, RawTextHelpFormatter
|
||||
|
||||
from project_lib import (QmlProjectData, check_qml_decorators, is_python_file, migrate_pyproject,
|
||||
QMLDIR_FILE, MOD_CMD, METATYPES_JSON_SUFFIX, SHADER_SUFFIXES,
|
||||
TRANSLATION_SUFFIX, requires_rebuild, run_command, remove_path,
|
||||
ProjectData, resolve_valid_project_file, new_project, NewProjectTypes,
|
||||
ClOptions, DesignStudioProject)
|
||||
|
||||
DESCRIPTION = """
|
||||
pyside6-project is a command line tool for creating, building and deploying Qt for Python
|
||||
applications. It operates on project files which are also used by Qt Creator.
|
||||
|
||||
Official documentation:
|
||||
https://doc.qt.io/qtforpython-6/tools/pyside-project.html
|
||||
"""
|
||||
|
||||
OPERATION_HELP = {
|
||||
"build": "Build the project. Compiles resources, UI files, and QML files if existing and "
|
||||
"necessary.",
|
||||
"run": "Build and run the project.",
|
||||
"clean": "Clean build artifacts and generated files from the project directory.",
|
||||
"qmllint": "Run the qmllint tool on QML files in the project.",
|
||||
"deploy": "Create a deployable package of the application including all dependencies.",
|
||||
"lupdate": "Update translation files (.ts) with new strings from source files.",
|
||||
"migrate-pyproject": "Migrate a *.pyproject file to pyproject.toml format."
|
||||
}
|
||||
|
||||
UIC_CMD = "pyside6-uic"
|
||||
RCC_CMD = "pyside6-rcc"
|
||||
LRELEASE_CMD = "pyside6-lrelease"
|
||||
LUPDATE_CMD = "pyside6-lupdate"
|
||||
QMLTYPEREGISTRAR_CMD = "pyside6-qmltyperegistrar"
|
||||
QMLLINT_CMD = "pyside6-qmllint"
|
||||
QSB_CMD = "pyside6-qsb"
|
||||
DEPLOY_CMD = "pyside6-deploy"
|
||||
|
||||
|
||||
def _sort_sources(files: list[Path]) -> list[Path]:
|
||||
"""Sort the sources for building, ensure .qrc is last since it might depend
|
||||
on generated files."""
|
||||
|
||||
def key_func(p: Path):
|
||||
return p.suffix if p.suffix != ".qrc" else ".zzzz"
|
||||
|
||||
return sorted(files, key=key_func)
|
||||
|
||||
|
||||
class Project:
|
||||
"""
|
||||
Class to wrap the various operations on Project
|
||||
"""
|
||||
|
||||
def __init__(self, project_file: Path):
|
||||
self.project = ProjectData(project_file=project_file)
|
||||
self.cl_options = ClOptions()
|
||||
|
||||
# Files for QML modules using the QmlElement decorators
|
||||
self._qml_module_sources: list[Path] = []
|
||||
self._qml_module_dir: Path | None = None
|
||||
self._qml_dir_file: Path | None = None
|
||||
self._qml_project_data = QmlProjectData()
|
||||
self._qml_module_check()
|
||||
|
||||
def _qml_module_check(self):
|
||||
"""Run a pre-check on Python source files and find the ones with QML
|
||||
decorators (representing a QML module)."""
|
||||
# Quick check for any QML files (to avoid running moc for no reason).
|
||||
if not self.cl_options.qml_module and not self.project.qml_files:
|
||||
return
|
||||
for file in self.project.files:
|
||||
if is_python_file(file):
|
||||
has_class, data = check_qml_decorators(file)
|
||||
if has_class:
|
||||
self._qml_module_sources.append(file)
|
||||
if data:
|
||||
self._qml_project_data = data
|
||||
|
||||
if not self._qml_module_sources:
|
||||
return
|
||||
if not self._qml_project_data:
|
||||
print("Detected QML-decorated files, " "but was unable to detect QML_IMPORT_NAME")
|
||||
sys.exit(1)
|
||||
|
||||
self._qml_module_dir = self.project.project_file.parent
|
||||
for uri_dir in self._qml_project_data.import_name.split("."):
|
||||
self._qml_module_dir /= uri_dir
|
||||
print(self._qml_module_dir)
|
||||
self._qml_dir_file = self._qml_module_dir / QMLDIR_FILE
|
||||
|
||||
if not self.cl_options.quiet:
|
||||
count = len(self._qml_module_sources)
|
||||
print(f"{self.project.project_file.name}, {count} QML file(s),"
|
||||
f" {self._qml_project_data}")
|
||||
|
||||
def _get_artifacts(self, file: Path, output_path: Path | None = None) -> \
|
||||
tuple[list[Path], list[str] | None]:
|
||||
"""Return path and command for a file's artifact"""
|
||||
if file.suffix == ".ui": # Qt form files
|
||||
py_file = f"{file.parent}/ui_{file.stem}.py"
|
||||
return [Path(py_file)], [UIC_CMD, os.fspath(file), "--rc-prefix", "-o", py_file]
|
||||
if file.suffix == ".qrc": # Qt resources
|
||||
if not output_path:
|
||||
py_file = f"{file.parent}/rc_{file.stem}.py"
|
||||
else:
|
||||
py_file = str(output_path.resolve())
|
||||
return [Path(py_file)], [RCC_CMD, os.fspath(file), "-o", py_file]
|
||||
# generate .qmltypes from sources with Qml decorators
|
||||
if file.suffix == ".py" and file in self._qml_module_sources:
|
||||
assert self._qml_module_dir
|
||||
qml_module_dir = os.fspath(self._qml_module_dir)
|
||||
json_file = f"{qml_module_dir}/{file.stem}{METATYPES_JSON_SUFFIX}"
|
||||
return [Path(json_file)], [MOD_CMD, "-o", json_file, os.fspath(file)]
|
||||
# Run qmltyperegistrar
|
||||
if file.name.endswith(METATYPES_JSON_SUFFIX):
|
||||
assert self._qml_module_dir
|
||||
stem = file.name[: len(file.name) - len(METATYPES_JSON_SUFFIX)]
|
||||
qmltypes_file = self._qml_module_dir / f"{stem}.qmltypes"
|
||||
cpp_file = self._qml_module_dir / f"{stem}_qmltyperegistrations.cpp"
|
||||
cmd = [QMLTYPEREGISTRAR_CMD, "--generate-qmltypes",
|
||||
os.fspath(qmltypes_file), "-o", os.fspath(cpp_file),
|
||||
os.fspath(file)]
|
||||
cmd.extend(self._qml_project_data.registrar_options())
|
||||
return [qmltypes_file, cpp_file], cmd
|
||||
|
||||
if file.name.endswith(TRANSLATION_SUFFIX):
|
||||
qm_file = f"{file.parent}/{file.stem}.qm"
|
||||
cmd = [LRELEASE_CMD, os.fspath(file), "-qm", qm_file]
|
||||
return [Path(qm_file)], cmd
|
||||
|
||||
if file.suffix in SHADER_SUFFIXES:
|
||||
qsb_file = f"{file.parent}/{file.stem}.qsb"
|
||||
cmd = [QSB_CMD, "-o", qsb_file, os.fspath(file)]
|
||||
return [Path(qsb_file)], cmd
|
||||
|
||||
return [], None
|
||||
|
||||
def _regenerate_qmldir(self):
|
||||
"""Regenerate the 'qmldir' file."""
|
||||
if self.cl_options.dry_run or not self._qml_dir_file:
|
||||
return
|
||||
if self.cl_options.force or requires_rebuild(self._qml_module_sources, self._qml_dir_file):
|
||||
with self._qml_dir_file.open("w") as qf:
|
||||
qf.write(f"module {self._qml_project_data.import_name}\n")
|
||||
for f in self._qml_module_dir.glob("*.qmltypes"):
|
||||
qf.write(f"typeinfo {f.name}\n")
|
||||
|
||||
def _build_file(self, source: Path, output_path: Path | None = None):
|
||||
"""Build an artifact if necessary."""
|
||||
artifacts, command = self._get_artifacts(source, output_path)
|
||||
for artifact in artifacts:
|
||||
if self.cl_options.force or requires_rebuild([source], artifact):
|
||||
run_command(command, cwd=self.project.project_file.parent)
|
||||
self._build_file(artifact) # Recurse for QML (json->qmltypes)
|
||||
|
||||
def build_design_studio_resources(self):
|
||||
"""
|
||||
The resources that need to be compiled are defined in autogen/settings.py
|
||||
"""
|
||||
ds_project = DesignStudioProject(self.project.main_file)
|
||||
if (resources_file_path := ds_project.get_resource_file_path()) is None:
|
||||
return
|
||||
|
||||
compiled_resources_file_path = ds_project.get_compiled_resources_file_path()
|
||||
self._build_file(resources_file_path, compiled_resources_file_path)
|
||||
|
||||
def build(self):
|
||||
"""Build the whole project"""
|
||||
for sub_project_file in self.project.sub_projects_files:
|
||||
Project(project_file=sub_project_file).build()
|
||||
|
||||
if self._qml_module_dir:
|
||||
self._qml_module_dir.mkdir(exist_ok=True, parents=True)
|
||||
|
||||
for file in _sort_sources(self.project.files):
|
||||
self._build_file(file)
|
||||
|
||||
if DesignStudioProject.is_ds_project(self.project.main_file):
|
||||
self.build_design_studio_resources()
|
||||
|
||||
self._regenerate_qmldir()
|
||||
|
||||
def run(self) -> int:
|
||||
"""Runs the project"""
|
||||
self.build()
|
||||
cmd = [sys.executable, str(self.project.main_file)]
|
||||
return run_command(cmd, cwd=self.project.project_file.parent)
|
||||
|
||||
def _clean_file(self, source: Path):
|
||||
"""Clean an artifact."""
|
||||
artifacts, command = self._get_artifacts(source)
|
||||
for artifact in artifacts:
|
||||
remove_path(artifact)
|
||||
self._clean_file(artifact) # Recurse for QML (json->qmltypes)
|
||||
|
||||
def clean(self):
|
||||
"""Clean build artifacts."""
|
||||
for sub_project_file in self.project.sub_projects_files:
|
||||
Project(project_file=sub_project_file).clean()
|
||||
for file in self.project.files:
|
||||
self._clean_file(file)
|
||||
if self._qml_module_dir and self._qml_module_dir.is_dir():
|
||||
remove_path(self._qml_module_dir)
|
||||
# In case of a dir hierarchy ("a.b" -> a/b), determine and delete
|
||||
# the root directory
|
||||
if self._qml_module_dir.parent != self.project.project_file.parent:
|
||||
project_dir_parts = len(self.project.project_file.parent.parts)
|
||||
first_module_dir = self._qml_module_dir.parts[project_dir_parts]
|
||||
remove_path(self.project.project_file.parent / first_module_dir)
|
||||
|
||||
if DesignStudioProject.is_ds_project(self.project.main_file):
|
||||
DesignStudioProject(self.project.main_file).clean()
|
||||
|
||||
def _qmllint(self):
|
||||
"""Helper for running qmllint on .qml files (non-recursive)."""
|
||||
if not self.project.qml_files:
|
||||
print(f"{self.project.project_file.name}: No QML files found", file=sys.stderr)
|
||||
return
|
||||
|
||||
cmd = [QMLLINT_CMD]
|
||||
if self._qml_dir_file:
|
||||
cmd.extend(["-i", os.fspath(self._qml_dir_file)])
|
||||
for f in self.project.qml_files:
|
||||
cmd.append(os.fspath(f))
|
||||
run_command(cmd, cwd=self.project.project_file.parent, ignore_fail=True)
|
||||
|
||||
def qmllint(self):
|
||||
"""Run qmllint on .qml files."""
|
||||
self.build()
|
||||
for sub_project_file in self.project.sub_projects_files:
|
||||
Project(project_file=sub_project_file)._qmllint()
|
||||
self._qmllint()
|
||||
|
||||
def deploy(self):
|
||||
"""Deploys the application"""
|
||||
cmd = [DEPLOY_CMD]
|
||||
cmd.extend([str(self.project.main_file), "-f"])
|
||||
run_command(cmd, cwd=self.project.project_file.parent)
|
||||
|
||||
def lupdate(self):
|
||||
for sub_project_file in self.project.sub_projects_files:
|
||||
Project(project_file=sub_project_file).lupdate()
|
||||
|
||||
if not self.project.ts_files:
|
||||
print(f"{self.project.project_file.name}: No .ts file found.",
|
||||
file=sys.stderr)
|
||||
return
|
||||
|
||||
source_files = self.project.python_files + self.project.ui_files
|
||||
project_dir = self.project.project_file.parent
|
||||
cmd_prefix = [LUPDATE_CMD] + [os.fspath(p.relative_to(project_dir)) for p in source_files]
|
||||
cmd_prefix.append("-ts")
|
||||
for ts_file in self.project.ts_files:
|
||||
ts_dir = ts_file.parent
|
||||
if not ts_dir.exists():
|
||||
ts_dir.mkdir(parents=True, exist_ok=True)
|
||||
if requires_rebuild(source_files, ts_file):
|
||||
cmd = cmd_prefix
|
||||
cmd.append(os.fspath(ts_file))
|
||||
run_command(cmd, cwd=project_dir)
|
||||
|
||||
|
||||
def main(mode: str = None, dry_run: bool = False, quiet: bool = False, force: bool = False,
|
||||
qml_module: bool = None, project_dir: str = None, project_path: str = None,
|
||||
legacy_pyproject: bool = False):
|
||||
cl_options = ClOptions(dry_run=dry_run, quiet=quiet, # noqa: F841
|
||||
force=force, qml_module=qml_module)
|
||||
|
||||
if new_project_type := NewProjectTypes.find_by_command(mode):
|
||||
if not project_dir:
|
||||
print(f"Error creating new project: {mode} requires a directory name or path",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
project_dir = Path(project_dir)
|
||||
try:
|
||||
project_dir.resolve()
|
||||
project_dir.mkdir(parents=True, exist_ok=True)
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
print("Invalid project name", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sys.exit(new_project(project_dir, new_project_type, legacy_pyproject))
|
||||
|
||||
if mode == "migrate-pyproject":
|
||||
sys.exit(migrate_pyproject(project_path))
|
||||
|
||||
try:
|
||||
project_file = resolve_valid_project_file(project_path)
|
||||
except ValueError as e:
|
||||
print(f"Error: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
project = Project(project_file)
|
||||
if mode == "build":
|
||||
project.build()
|
||||
elif mode == "run":
|
||||
sys.exit(project.run())
|
||||
elif mode == "clean":
|
||||
project.clean()
|
||||
elif mode == "qmllint":
|
||||
project.qmllint()
|
||||
elif mode == "deploy":
|
||||
project.deploy()
|
||||
elif mode == "lupdate":
|
||||
project.lupdate()
|
||||
else:
|
||||
print(f"Invalid mode {mode}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser(description=DESCRIPTION, formatter_class=RawTextHelpFormatter)
|
||||
parser.add_argument("--quiet", "-q", action="store_true", help="Quiet")
|
||||
parser.add_argument("--dry-run", "-n", action="store_true", help="Only print commands")
|
||||
parser.add_argument("--force", "-f", action="store_true", help="Force rebuild")
|
||||
parser.add_argument("--qml-module", "-Q", action="store_true",
|
||||
help="Perform check for QML module")
|
||||
|
||||
# Create subparsers for the two different command branches
|
||||
subparsers = parser.add_subparsers(dest='mode', required=True)
|
||||
|
||||
# Add subparser for project creation commands
|
||||
for project_type in NewProjectTypes:
|
||||
new_parser = subparsers.add_parser(project_type.value.command,
|
||||
help=project_type.value.description)
|
||||
new_parser.add_argument(
|
||||
"project_dir", help="Name or location of the new project", nargs="?", type=str)
|
||||
|
||||
new_parser.add_argument(
|
||||
"--legacy-pyproject", action="store_true", help="Create a legacy *.pyproject file")
|
||||
|
||||
# Add subparser for project operation commands
|
||||
for op_mode, op_help in OPERATION_HELP.items():
|
||||
op_parser = subparsers.add_parser(op_mode, help=op_help)
|
||||
op_parser.add_argument("project_path", nargs="?", type=str, help="Path to the project file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
main(args.mode, args.dry_run, args.quiet, args.force, args.qml_module,
|
||||
getattr(args, "project_dir", None), getattr(args, "project_path", None),
|
||||
getattr(args, "legacy_pyproject", None))
|
||||
Reference in New Issue
Block a user