Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--sources/pyside-tools/deploy.py1
-rw-r--r--sources/pyside-tools/deploy_lib/config.py28
-rw-r--r--sources/pyside-tools/deploy_lib/default.spec3
-rw-r--r--sources/pyside-tools/deploy_lib/dependency_util.py64
-rw-r--r--sources/pyside-tools/deploy_lib/nuitka_helper.py33
-rw-r--r--sources/pyside6/doc/_tags/android.rst21
-rw-r--r--sources/pyside6/doc/_tags/tagsindex.rst12
-rw-r--r--sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py68
8 files changed, 188 insertions, 42 deletions
diff --git a/sources/pyside-tools/deploy.py b/sources/pyside-tools/deploy.py
index 6cb6d4d9c..b54943ddf 100644
--- a/sources/pyside-tools/deploy.py
+++ b/sources/pyside-tools/deploy.py
@@ -120,6 +120,7 @@ def main(main_file: Path = None, name: str = None, config_file: Path = None, ini
command_str = nuitka.create_executable(source_file=config.source_file,
extra_args=config.extra_args,
qml_files=config.qml_files,
+ qt_plugins=config.qt_plugins,
excluded_qml_plugins=config.excluded_qml_plugins,
icon=config.icon,
dry_run=dry_run)
diff --git a/sources/pyside-tools/deploy_lib/config.py b/sources/pyside-tools/deploy_lib/config.py
index 44b4ded06..f1c877cac 100644
--- a/sources/pyside-tools/deploy_lib/config.py
+++ b/sources/pyside-tools/deploy_lib/config.py
@@ -365,7 +365,7 @@ class DesktopConfig(Config):
existing_config_file: bool = False, extra_ignore_dirs: List[str] = None):
super().__init__(config_file, source_file, python_exe, dry_run, existing_config_file,
extra_ignore_dirs)
-
+ self.dependency_reader = QtDependencyReader(dry_run=self.dry_run)
if self.get_value("qt", "modules"):
self.modules = self.get_value("qt", "modules").split(",")
else:
@@ -373,20 +373,34 @@ class DesktopConfig(Config):
self._find_and_set_qtquick_modules()
self._find_dependent_qt_modules()
+ self._qt_plugins = []
+ if self.get_value("qt", "plugins"):
+ self._qt_plugins = self.get_value("qt", "plugins").split(",")
+ else:
+ self.qt_plugins = self.dependency_reader.find_plugin_dependencies(self.modules)
+
+ @property
+ def qt_plugins(self):
+ return self._qt_plugins
+
+ @qt_plugins.setter
+ def qt_plugins(self, qt_plugins):
+ self._qt_plugins = qt_plugins
+ self.set_value("qt", "plugins", ",".join(qt_plugins))
+
def _find_dependent_qt_modules(self):
"""
Given pysidedeploy_config.modules, find all the other dependent Qt modules.
"""
- dependency_reader = QtDependencyReader(dry_run=self.dry_run)
all_modules = set(self.modules)
- if not dependency_reader.lib_reader:
- warnings.warn(f"[DEPLOY] Unable to find {dependency_reader.lib_reader_name}. This tool"
- " helps to find the Qt module dependencies of the application. Skipping "
- " checking for dependencies.", category=RuntimeWarning)
+ if not self.dependency_reader.lib_reader:
+ warnings.warn(f"[DEPLOY] Unable to find {self.dependency_reader.lib_reader_name}. This "
+ "tool helps to find the Qt module dependencies of the application. "
+ "Skipping checking for dependencies.", category=RuntimeWarning)
return
for module_name in self.modules:
- dependency_reader.find_dependencies(module=module_name, used_modules=all_modules)
+ self.dependency_reader.find_dependencies(module=module_name, used_modules=all_modules)
self.modules = list(all_modules)
diff --git a/sources/pyside-tools/deploy_lib/default.spec b/sources/pyside-tools/deploy_lib/default.spec
index 2276fa496..8c0697afd 100644
--- a/sources/pyside-tools/deploy_lib/default.spec
+++ b/sources/pyside-tools/deploy_lib/default.spec
@@ -44,6 +44,9 @@ excluded_qml_plugins =
# Qt modules used. Comma separated
modules =
+# Qt plugins used by the application
+plugins =
+
[android]
# path to PySide wheel
diff --git a/sources/pyside-tools/deploy_lib/dependency_util.py b/sources/pyside-tools/deploy_lib/dependency_util.py
index c7821794f..53c12ad92 100644
--- a/sources/pyside-tools/deploy_lib/dependency_util.py
+++ b/sources/pyside-tools/deploy_lib/dependency_util.py
@@ -5,6 +5,7 @@ import ast
import re
import os
import site
+import json
import warnings
import logging
import shutil
@@ -15,25 +16,6 @@ from typing import List, Set
from . import IMPORT_WARNING_PYSIDE, run_command
-def get_qt_libs_dir():
- """
- Finds the path to the Qt libs directory inside PySide6 package installation
- """
- pyside_install_dir = None
- for possible_site_package in site.getsitepackages():
- if possible_site_package.endswith("site-packages"):
- pyside_install_dir = Path(possible_site_package) / "PySide6"
-
- if not pyside_install_dir:
- print("Unable to find site-packages. Exiting ...")
- sys.exit(-1)
-
- if sys.platform == "win32":
- return pyside_install_dir
-
- return pyside_install_dir / "Qt" / "lib" # for linux and macOS
-
-
def find_pyside_modules(project_dir: Path, extra_ignore_dirs: List[Path] = None,
project_data=None):
"""
@@ -164,9 +146,27 @@ class QtDependencyReader:
print(f"[DEPLOY] Deployment on unsupported platfrom {sys.platform}")
sys.exit(1)
- self.qt_libs_dir = get_qt_libs_dir()
+ self.pyside_install_dir = None
+ self.qt_libs_dir = self.get_qt_libs_dir()
self._lib_reader = shutil.which(self.lib_reader_name)
+ def get_qt_libs_dir(self):
+ """
+ Finds the path to the Qt libs directory inside PySide6 package installation
+ """
+ for possible_site_package in site.getsitepackages():
+ if possible_site_package.endswith("site-packages"):
+ self.pyside_install_dir = Path(possible_site_package) / "PySide6"
+
+ if not self.pyside_install_dir:
+ print("Unable to find site-packages. Exiting ...")
+ sys.exit(-1)
+
+ if sys.platform == "win32":
+ return self.pyside_install_dir
+
+ return self.pyside_install_dir / "Qt" / "lib" # for linux and macOS
+
@property
def lib_reader(self):
return self._lib_reader
@@ -216,3 +216,27 @@ class QtDependencyReader:
logging.info(f"[DEPLOY] Following dependencies found for {module}: {dependent_modules}")
else:
logging.info(f"[DEPLOY] No Qt dependencies found for {module}")
+
+ def find_plugin_dependencies(self, used_modules: List[str]) -> List[str]:
+ """
+ Given the modules used by the application, returns all the required plugins
+ """
+ plugins = set()
+ pyside_mod_plugin_jsons = ["PySide6_Essentials.json", "PySide6_Addons.json"]
+ for pyside_mod_plugin_json_name in pyside_mod_plugin_jsons:
+ pyside_mod_plugin_json_file = self.pyside_install_dir / pyside_mod_plugin_json_name
+ if not pyside_mod_plugin_json_file.exists():
+ warnings.warn(f"[DEPLOY] Unable to find {pyside_mod_plugin_json_file}.",
+ category=RuntimeWarning)
+ continue
+
+ # convert the json to dict
+ pyside_mod_dict = {}
+ with open(pyside_mod_plugin_json_file) as pyside_json:
+ pyside_mod_dict = json.load(pyside_json)
+
+ # find all the plugins in the modules
+ for module in used_modules:
+ plugins.update(pyside_mod_dict.get(module, []))
+
+ return list(plugins)
diff --git a/sources/pyside-tools/deploy_lib/nuitka_helper.py b/sources/pyside-tools/deploy_lib/nuitka_helper.py
index ae5834b6b..721701f70 100644
--- a/sources/pyside-tools/deploy_lib/nuitka_helper.py
+++ b/sources/pyside-tools/deploy_lib/nuitka_helper.py
@@ -17,6 +17,23 @@ class Nuitka:
def __init__(self, nuitka):
self.nuitka = nuitka
+ # plugins to ignore. The sensible plugins are include by default by Nuitka for PySide6
+ # application deployment
+ self.qt_plugins_to_ignore = ["imageformats", # being Nuitka `sensible`` plugins
+ "iconengines",
+ "mediaservice",
+ "printsupport",
+ "platforms",
+ "platformthemes",
+ "styles",
+ "wayland-shell-integration",
+ "wayland-decoration-client",
+ "wayland-graphics-integration-client",
+ "egldeviceintegrations",
+ "xcbglintegrations",
+ "tls", # end Nuitka `sensible` plugins
+ "generic" # plugins that error with Nuitka
+ ]
@staticmethod
def icon_option():
@@ -28,11 +45,12 @@ class Nuitka:
return "--macos-app-icon"
def create_executable(self, source_file: Path, extra_args: str, qml_files: List[Path],
- excluded_qml_plugins: List[str], icon: str, dry_run: bool):
+ qt_plugins: List[str], excluded_qml_plugins: List[str], icon: str,
+ dry_run: bool):
+ qt_plugins = [plugin for plugin in qt_plugins if plugin not in self.qt_plugins_to_ignore]
extra_args = extra_args.split()
qml_args = []
if qml_files:
- qml_args.append("--include-qt-plugins=all")
# This will generate options for each file using:
# --include-data-files=ABSOLUTE_PATH_TO_FILE=RELATIVE_PATH_TO ROOT
# for each file. This will preserve the directory structure of QML resources.
@@ -41,6 +59,11 @@ class Nuitka:
f"./{qml_file.resolve().relative_to(source_file.parent)}"
for qml_file in qml_files]
)
+ # add qml plugin. The `qml`` plugin name is not present in the module json files shipped
+ # with Qt and hence not in `qt_plugins``. However, Nuitka uses the 'qml' plugin name to
+ # include the necessary qml plugins. There we have to add it explicitly for a qml
+ # application
+ qt_plugins.append("qml")
if excluded_qml_plugins:
prefix = "lib" if sys.platform != "win32" else ""
@@ -59,8 +82,14 @@ class Nuitka:
"--enable-plugin=pyside6",
f"--output-dir={output_dir}",
]
+
command.extend(extra_args + qml_args)
command.append(f"{self.__class__.icon_option()}={icon}")
+ if qt_plugins:
+ # sort qt_plugins so that the result is definitive when testing
+ qt_plugins.sort()
+ qt_plugins_str = ",".join(qt_plugins)
+ command.append(f"--include-qt-plugins={qt_plugins_str}")
command_str, _ = run_command(command=command, dry_run=dry_run)
return command_str
diff --git a/sources/pyside6/doc/_tags/android.rst b/sources/pyside6/doc/_tags/android.rst
new file mode 100644
index 000000000..08a30fc0f
--- /dev/null
+++ b/sources/pyside6/doc/_tags/android.rst
@@ -0,0 +1,21 @@
+My tags: Android
+################
+
+.. toctree::
+ :maxdepth: 1
+ :caption: With this tag
+
+ ../examples/example_bluetooth_heartrate_game.rst
+ ../examples/example_bluetooth_lowenergyscanner.rst
+ ../examples/example_location_mapviewer.rst
+ ../examples/example_multimedia_audiooutput.rst
+ ../examples/example_multimedia_audiosource.rst
+ ../examples/example_multimedia_camera.rst
+ ../examples/example_qml_editingmodel.rst
+ ../examples/example_qml_usingmodel.rst
+ ../examples/example_quick_models_objectlistmodel.rst
+ ../examples/example_quick_models_stringlistmodel.rst
+ ../examples/example_quick_painteditem.rst
+ ../examples/example_quickcontrols_contactslist.rst
+ ../examples/example_quickcontrols_gallery.rst
+ ../examples/example_widgets_widgets_digitalclock.rst
diff --git a/sources/pyside6/doc/_tags/tagsindex.rst b/sources/pyside6/doc/_tags/tagsindex.rst
new file mode 100644
index 000000000..be29337ec
--- /dev/null
+++ b/sources/pyside6/doc/_tags/tagsindex.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+.. _tagoverview:
+
+Tags overview
+#############
+
+.. toctree::
+ :caption: Tags
+ :maxdepth: 1
+
+ Android (14) <android.rst>
diff --git a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
index c79a633e1..db8813ccf 100644
--- a/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
+++ b/sources/pyside6/tests/tools/pyside6-deploy/test_pyside6_deploy.py
@@ -81,6 +81,7 @@ class DeployTestBase(LongSortedOptionTest):
@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
"Test only works on macOS version 12+")
+@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
class TestPySide6DeployWidgets(DeployTestBase):
@classmethod
def setUpClass(cls):
@@ -94,10 +95,18 @@ class TestPySide6DeployWidgets(DeployTestBase):
os.chdir(self.temp_example_widgets)
self.main_file = self.temp_example_widgets / "tetrix.py"
self.deployment_files = self.temp_example_widgets / "deployment"
+ # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles
+ # some plugins by default
+ self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "platforminputcontexts", "platforms",
+ "platforms/darwin", "platformthemes", "styles", "xcbglintegrations"]
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,platforminputcontexts,platforms/darwin")
self.expected_run_cmd = (
f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile"
f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
f" --noinclude-qt-translations"
+ f" --include-qt-plugins={plugins_nuitka}"
)
if sys.platform.startswith("linux"):
self.expected_run_cmd += f" --linux-icon={str(self.linux_icon)}"
@@ -110,16 +119,18 @@ class TestPySide6DeployWidgets(DeployTestBase):
self.expected_run_cmd += " --static-libpython=no"
self.config_file = self.temp_example_widgets / "pysidedeploy.spec"
- def testWidgetDryRun(self):
+ def testWidgetDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
# Checking for dry run commands is equivalent to mocking the
# subprocess.check_call() in commands.py as the the dry run command
# is the command being run.
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
self.assertEqual(original_output, self.expected_run_cmd)
- @patch("deploy_lib.dependency_util.get_qt_libs_dir")
- def testWidgetConfigFile(self, mock_sitepackages):
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testWidgetConfigFile(self, mock_sitepackages, mock_plugins):
mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ mock_plugins.return_value = self.all_plugins
# includes both dry run and config_file tests
# init
init_result = self.deploy.main(self.main_file, init=True, force=True)
@@ -146,9 +157,12 @@ class TestPySide6DeployWidgets(DeployTestBase):
expected_modules.add("DBus")
obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
self.assertEqual(obtained_modules, expected_modules)
+ obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",")
+ self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort())
self.config_file.unlink()
- def testErrorReturns(self):
+ def testErrorReturns(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
# main file and config file does not exists
fake_main_file = self.main_file.parent / "main.py"
with self.assertRaises(RuntimeError) as context:
@@ -158,6 +172,7 @@ class TestPySide6DeployWidgets(DeployTestBase):
@unittest.skipIf(sys.platform == "darwin" and int(platform.mac_ver()[0].split('.')[0]) <= 11,
"Test only works on macOS version 12+")
+@patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
class TestPySide6DeployQml(DeployTestBase):
@classmethod
def setUpClass(cls):
@@ -173,13 +188,24 @@ class TestPySide6DeployQml(DeployTestBase):
self.deployment_files = self.temp_example_qml / "deployment"
self.first_qml_file = "main.qml"
self.second_qml_file = "MovingRectangle.qml"
+ # All the plugins included. This is different from plugins_nuitka, because Nuitka bundles
+ # some plugins by default
+ self.all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "networkaccess", "networkinformation",
+ "platforminputcontexts", "platforms", "platforms/darwin",
+ "platformthemes", "qmltooling", "scenegraph", "tls",
+ "xcbglintegrations"]
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts,"
+ "platforms/darwin,qml,qmltooling,scenegraph")
self.expected_run_cmd = (
f"{sys.executable} -m nuitka {str(self.main_file)} --follow-imports --onefile"
f" --enable-plugin=pyside6 --output-dir={str(self.deployment_files)} --quiet"
- f" --noinclude-qt-translations --include-qt-plugins=all"
+ f" --noinclude-qt-translations"
+ f" --include-qt-plugins={plugins_nuitka}"
f" --include-data-files={str(self.temp_example_qml / self.first_qml_file)}="
f"./main.qml --include-data-files="
- f"{str(self.temp_example_qml /self.second_qml_file)}=./MovingRectangle.qml"
+ f"{str(self.temp_example_qml / self.second_qml_file)}=./MovingRectangle.qml"
)
if sys.platform != "win32":
@@ -206,9 +232,10 @@ class TestPySide6DeployQml(DeployTestBase):
self.expected_run_cmd += " --static-libpython=no"
self.config_file = self.temp_example_qml / "pysidedeploy.spec"
- @patch("deploy_lib.dependency_util.get_qt_libs_dir")
- def testQmlConfigFile(self, mock_sitepackages):
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testQmlConfigFile(self, mock_sitepackages, mock_plugins):
mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ mock_plugins.return_value = self.all_plugins
# create config file
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
@@ -235,16 +262,20 @@ class TestPySide6DeployQml(DeployTestBase):
expected_modules.add("DBus")
obtained_modules = set(config_obj.get_value("qt", "modules").split(","))
self.assertEqual(obtained_modules, expected_modules)
+ obtained_qt_plugins = config_obj.get_value("qt", "plugins").split(",")
+ self.assertEqual(obtained_qt_plugins.sort(), self.all_plugins.sort())
self.config_file.unlink()
- def testQmlDryRun(self):
+ def testQmlDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
original_output = self.deploy.main(self.main_file, dry_run=True, force=True)
self.assertEqual(original_output, self.expected_run_cmd)
self.assertEqual(mock_qmlimportscanner.call_count, 1)
- def testMainFileDryRun(self):
+ def testMainFileDryRun(self, mock_plugins):
+ mock_plugins.return_value = self.all_plugins
with patch("deploy_lib.config.run_qmlimportscanner") as mock_qmlimportscanner:
mock_qmlimportscanner.return_value = ["QtQuick"]
original_output = self.deploy.main(Path.cwd() / "main.py", dry_run=True, force=True)
@@ -263,14 +294,24 @@ class TestPySide6DeployWebEngine(DeployTestBase):
shutil.copytree(example_webenginequick, Path(cls.temp_dir) / "nanobrowser")
).resolve()
- @patch("deploy_lib.dependency_util.get_qt_libs_dir")
- def testWebEngineQuickDryRun(self, mock_sitepackages):
+ @patch("deploy_lib.config.QtDependencyReader.find_plugin_dependencies")
+ @patch("deploy_lib.dependency_util.QtDependencyReader.get_qt_libs_dir")
+ def testWebEngineQuickDryRun(self, mock_sitepackages, mock_plugins):
mock_sitepackages.return_value = Path(_get_qt_lib_dir())
+ all_plugins = ["accessiblebridge", "egldeviceintegrations", "generic", "iconengines",
+ "imageformats", "networkaccess", "networkinformation",
+ "platforminputcontexts", "platforms", "platforms/darwin",
+ "platformthemes", "qmltooling", "scenegraph", "tls",
+ "xcbglintegrations"]
+ mock_plugins.return_value = all_plugins
# this test case retains the QtWebEngine dlls
# setup
os.chdir(self.temp_example_webenginequick)
main_file = self.temp_example_webenginequick / "quicknanobrowser.py"
deployment_files = self.temp_example_webenginequick / "deployment"
+ # Plugins that needs to be passed to Nuitka
+ plugins_nuitka = ("accessiblebridge,networkaccess,networkinformation,platforminputcontexts,"
+ "platforms/darwin,qml,qmltooling,scenegraph")
qml_files = [
"ApplicationRoot.qml",
"BrowserDialog.qml",
@@ -281,7 +322,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
]
data_files_cmd = " ".join(
[
- f"--include-data-files={str(self.temp_example_webenginequick/file)}=./{file}"
+ f"--include-data-files={str(self.temp_example_webenginequick / file)}=./{file}"
for file in qml_files
]
)
@@ -290,6 +331,7 @@ class TestPySide6DeployWebEngine(DeployTestBase):
f" --enable-plugin=pyside6 --output-dir={str(deployment_files)} --quiet"
f" --noinclude-qt-translations --include-qt-plugins=all"
f" {data_files_cmd}"
+ f" --include-qt-plugins={plugins_nuitka}"
)
if sys.platform != "win32":