Repository: pyinstaller/pyinstaller

Issue:
Missing PyQt5 dll break printing
Using PyInstaller with Python3 on a PyQt5 application.
When bundling a needed library is not included in the bundle, which breaks printing. When in the program (OpenLP) the native printer dialog doesn't open. On windows it is this dll: qt5_plugins\printsupport\windowsprintersupport.dll
I believe this is also an issue on Mac OS X, and probably Linux too.



Discussion:

Resolving patch:
diff --git a/PyInstaller/hooks/hook-PyQt5.QtPrintSupport.py b/PyInstaller/hooks/hook-PyQt5.QtPrintSupport.py
new file mode 100644
--- /dev/null
+++ b/PyInstaller/hooks/hook-PyQt5.QtPrintSupport.py
@@ -0,0 +1,17 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2013-2016, PyInstaller Development Team.
+#
+# Distributed under the terms of the GNU General Public License with exception
+# for distributing bootloader.
+#
+# The full license is in the file COPYING.txt, distributed with this software.
+#-----------------------------------------------------------------------------
+
+
+hiddenimports = ['sip', 'PyQt5.QtCore', 'PyQt5.QtGui', 'PyQt5.QtWidgets']
+
+from PyInstaller.utils.hooks import qt_plugins_binaries
+
+
+binaries = []
+binaries.extend(qt_plugins_binaries('printsupport', namespace='PyQt5'))
diff --git a/PyInstaller/utils/hooks/__init__.py b/PyInstaller/utils/hooks/__init__.py
--- a/PyInstaller/utils/hooks/__init__.py
+++ b/PyInstaller/utils/hooks/__init__.py
@@ -260,6 +260,7 @@ def qt_menu_nib_dir(namespace):
     """
     if namespace not in ['PyQt4', 'PyQt5', 'PySide']:
         raise Exception('Invalid namespace: {0}'.format(namespace))
+    menu_dir = None
 
     path = exec_statement("""
     from {0}.QtCore import QLibraryInfo
@@ -267,14 +268,14 @@ def qt_menu_nib_dir(namespace):
     str = getattr(__builtins__, 'unicode', str)  # for Python 2
     print(str(path))
     """.format(namespace))
-    path = os.path.join(path, 'Resources')
-
-    # Check directory existence
-    path = os.path.join(path, 'qt_menu.nib')
-    if os.path.exists(path):
-        menu_dir = path
-        logger.debug('Found qt_menu.nib for {0} at {1}'.format(namespace, path))
-    else:
+    for location in [os.path.join(path, 'Resources'), os.path.join(path, 'QtGui.framework', 'Resources')]:
+        # Check directory existence
+        path = os.path.join(location, 'qt_menu.nib')
+        if os.path.exists(path):
+            menu_dir = path
+            logger.debug('Found qt_menu.nib for {0} at {1}'.format(namespace, path))
+            break
+    if not menu_dir:
         raise Exception("""
             Cannot find qt_menu.nib for {0}
             Path checked: {1}


Test patch:
diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py
--- a/tests/functional/conftest.py
+++ b/tests/functional/conftest.py
@@ -62,6 +62,20 @@
 # ====
 # Fixtures
 # --------
+
+
+@pytest.hookimpl(tryfirst=True, hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+    # execute all other hooks to obtain the report object
+    outcome = yield
+    rep = outcome.get_result()
+
+    # set an report attribute for each phase of a call, which can
+    # be "setup", "call", "teardown"
+
+    setattr(item, "rep_" + rep.when, rep)
+
+
 @pytest.fixture
 def script_dir():
     return py.path.local(_SCRIPT_DIR)
@@ -418,6 +432,14 @@ def pyi_builder(tmpdir, monkeypatch, request, pyi_modgraph):
     # The value is same as the original value.
     monkeypatch.setattr('PyInstaller.config.CONF', {'pathex': []})
 
+    def del_temp_dir():
+        if request.node.rep_setup.passed:
+            if request.node.rep_call.passed:
+                if os.path.exists(tmp):
+                    shutil.rmtree(tmp)
+
+    if is_darwin:
+        request.addfinalizer(del_temp_dir)
     return AppBuilder(tmp, request.param, pyi_modgraph)
 
 
diff --git a/tests/functional/test_libraries.py b/tests/functional/test_libraries.py
--- a/tests/functional/test_libraries.py
+++ b/tests/functional/test_libraries.py
@@ -18,6 +18,7 @@
 import sys
 
 from PyInstaller.compat import is_win, is_py3, is_darwin
+from PyInstaller.utils.hooks import get_module_attribute, is_module_satisfies
 from PyInstaller.utils.tests import importorskip, xfail
 
 # :todo: find a way to get this from `conftest` or such
@@ -193,7 +194,6 @@ def __init__(self, *args, **kwargs):
 
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('sphinx')
 def test_sphinx(tmpdir, pyi_builder, data_dir):
     # Note that including the data_dir fixture copies files needed by this test.
@@ -217,7 +217,6 @@ def test_pylint(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('pygments')
 def test_pygments(pyi_builder):
     pyi_builder.test_source(
@@ -232,7 +231,6 @@ def test_pygments(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('markdown')
 def test_markdown(pyi_builder):
     # Markdown uses __import__ed extensions. Make sure these work by
@@ -267,10 +265,16 @@ def test_PyQt4_uic(tmpdir, pyi_builder, data_dir):
     # Note that including the data_dir fixture copies files needed by this test.
     pyi_builder.test_script('pyi_lib_PyQt4-uic.py')
 
+
+@pytest.mark.skipif(is_module_satisfies('Qt >= 5.6', get_module_attribute('PyQt5.QtCore', 'QT_VERSION_STR')),
+                    reason='QtWebKit is depreciated in Qt 5.6+')
 @importorskip('PyQt5')
 def test_PyQt5_QtWebKit(pyi_builder):
     pyi_builder.test_script('pyi_lib_PyQt5-QtWebKit.py')
 
+
+@pytest.mark.skipif(is_module_satisfies('Qt >= 5.6', get_module_attribute('PyQt5.QtCore', 'QT_VERSION_STR')),
+                    reason='QtWebKit is depreciated in Qt 5.6+')
 @importorskip('PyQt5')
 def test_PyQt5_uic(tmpdir, pyi_builder, data_dir):
     # Note that including the data_dir fixture copies files needed by this test.
@@ -298,7 +302,6 @@ def test_idlelib(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('keyring')
 def test_keyring(pyi_builder):
     pyi_builder.test_source("import keyring")
@@ -314,7 +317,6 @@ def test_lxml_isoschematron(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('numpy')
 def test_numpy(pyi_builder):
     pyi_builder.test_source(
@@ -338,7 +340,6 @@ def test_pyodbc(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('pytz')
 def test_pytz(pyi_builder):
     pyi_builder.test_source(
@@ -501,7 +502,6 @@ def test_scipy(pyi_builder):
         """)
 
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('sqlalchemy')
 def test_sqlalchemy(pyi_builder):
     pyi_builder.test_source(
@@ -545,7 +545,6 @@ def test_twisted(pyi_builder):
     p = importorskip(pkg)(p)
     excludes.append(p)
 
-@xfail(is_darwin, reason='Issue #1895.')
 @importorskip('matplotlib')
 @pytest.mark.parametrize("excludes", excludes, ids=all_qt_pkgs)
 def test_matplotlib(pyi_builder, excludes):
diff --git a/tests/requirements-mac.txt b/tests/requirements-mac.txt
new file mode 100644
--- /dev/null
+++ b/tests/requirements-mac.txt
@@ -0,0 +1,5 @@
+# gevent wheels for mac os x cause pytest to crash on travis
+--no-binary gevent
+-r requirements.txt
+
+pyenchant
diff --git a/tests/requirements.txt b/tests/requirements.txt
--- a/tests/requirements.txt
+++ b/tests/requirements.txt
@@ -26,11 +26,9 @@ pycmd  # Contains 'py.cleanup' that removes all .pyc files and similar.
 # Test libraries
 # Url for downloading PyCrypto prebuilt Windows binaries:
 # http://www.voidspace.org.uk/python/pycrypto-2.6.1/
-pycrypto==2.6.1  # Required for crypto feature - encrypting bytecode.
-Django==1.8.3
-# Latest babel 1.3.1 fails with Python 3.3/3.4 on Windows:
-# https://github.com/mitsuhiko/babel/issues/174
-babel==1.3  # Required by sphinx. This version works on Windows.
+pycrypto  # Required for crypto feature - encrypting bytecode.
+Django<1.8.11 # PyInstaller doesn't yet work with Django 1.9
+babel>2.0  # Required by sphinx. Version 2.0 doesn't work on Windows.
 boto
 boto3
 botocore
@@ -53,8 +51,8 @@ twisted
 pyexcelerate
 Pillow
 
-# dateutil.tz in a package in 2.5.0 and does not play nice with PyInstaller.
-python-dateutil==2.4.2
+# dateutil.tz is a package in 2.5.0 and does not play nice with PyInstaller.
+python-dateutil>2.5.0
 pandas
 
 # matplotlib 1.5+ does not provide binaries for python 3.3