From 8f5c214f7bcda9cc51293642c92ef57a46f4e2c2 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 13:11:30 +0100 Subject: [PATCH 01/10] Tests: Add test-cases for module '__main__'. --- .../specs/several-scripts/check___name__.py | 1 + tests/functional/specs/several-scripts3.spec | 28 +++++++++++++++++++ tests/functional/test_basic.py | 12 ++++++++ 3 files changed, 41 insertions(+) create mode 100644 tests/functional/specs/several-scripts/check___name__.py create mode 100644 tests/functional/specs/several-scripts3.spec diff --git a/tests/functional/specs/several-scripts/check___name__.py b/tests/functional/specs/several-scripts/check___name__.py new file mode 100644 index 0000000000..3fa7827e69 --- /dev/null +++ b/tests/functional/specs/several-scripts/check___name__.py @@ -0,0 +1 @@ +assert __name__ == '__main__' diff --git a/tests/functional/specs/several-scripts3.spec b/tests/functional/specs/several-scripts3.spec new file mode 100644 index 0000000000..2ca7750953 --- /dev/null +++ b/tests/functional/specs/several-scripts3.spec @@ -0,0 +1,28 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2005-2017, 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. +#----------------------------------------------------------------------------- + +# Verify each __name__ == '__main_' in all scripts + +app_name = "several-scripts3" + +a = Analysis(['several-scripts/check___name__.py', + 'several-scripts/check___name__.py', + 'several-scripts/check___name__.py']) +pyz = PYZ(a.pure, a.zipped_data) +exe = EXE(pyz, + a.scripts, + exclude_binaries=True, + name=app_name, + debug=False, + console=True) +coll = COLLECT(exe, + a.binaries, + a.zipfiles, + a.datas, + name=app_name) diff --git a/tests/functional/test_basic.py b/tests/functional/test_basic.py index 792d972ef1..0e58de5e7f 100644 --- a/tests/functional/test_basic.py +++ b/tests/functional/test_basic.py @@ -568,3 +568,15 @@ def test_several_scripts2(pyi_builder_spec): Verify each script has it's own global vars (basic test). """ pyi_builder_spec.test_spec('several-scripts2.spec') + + +def test_main_exists(pyi_builder): + pyi_builder.test_source("import sys ; sys.modules['__main__']") + + +def test_name_is_main(pyi_builder): + pyi_builder.test_source("assert __name__ == '__main__'") + + +def test_name_is_main_in_all_scripts(pyi_builder_spec): + pyi_builder_spec.test_spec('several-scripts3.spec') From e183b3e3b94166cfe67d430418bb963e5ae3f2dd Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 13:07:43 +0100 Subject: [PATCH 02/10] WIP Bootloader: Create a new module '__main__' for each script. This will ensure each scripts will get its own global variables. - Move the creation of the '__main__' module into the loop and remove this module from sys.modules after the script has been executed. - Use PyImport_ExecCodeModule() for executing the scripts. - No longer set the __file__ attribute (now done by PyImport_ExecCodeModule). Using PyImport_AddModule within the loop did not work: For the second script, '__builtins__' have not been set, so even a simple import failed. (See pull-request #3038 for details.) --- bootloader/src/pyi_launch.c | 47 +++++++++++++++++-------------------- bootloader/src/pyi_python.c | 4 ++++ bootloader/src/pyi_python.h | 2 ++ 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/bootloader/src/pyi_launch.c b/bootloader/src/pyi_launch.c index 3abcf927d9..8cb78d0585 100644 --- a/bootloader/src/pyi_launch.c +++ b/bootloader/src/pyi_launch.c @@ -366,22 +366,19 @@ pyi_launch_run_scripts(ARCHIVE_STATUS *status) char buf[PATH_MAX]; size_t namelen; TOC * ptoc = status->tocbuff; - PyObject *__main__; - PyObject *__file__; - PyObject *main_dict; - PyObject *code, *retval; + PyObject *__main__name; + PyObject *sys_modules; + PyObject *code, *module; - __main__ = PI_PyImport_AddModule("__main__"); - - if (!__main__) { - FATALERROR("Could not get __main__ module."); - return -1; + sys_modules = PI_PyImport_GetModuleDict(); /* borrowed reference */ + if (is_py2) { + __main__name = PI_PyString_FromString("__main__"); } - - main_dict = PI_PyModule_GetDict(__main__); - - if (!main_dict) { - FATALERROR("Could not get __main__ module's dict."); + else { + __main__name = PI_PyUnicode_FromString("__main__"); + } + if (__main__name == NULL) { + FATALERROR("Could not get object for string '__main__'."); return -1; } @@ -402,41 +399,41 @@ pyi_launch_run_scripts(ARCHIVE_STATUS *status) strcat(buf, ".py"); VS("LOADER: Running %s\n", buf); - if (is_py2) { - __file__ = PI_PyString_FromString(buf); - } - else { - __file__ = PI_PyUnicode_FromString(buf); - }; - PI_PyObject_SetAttrString(__main__, "__file__", __file__); - Py_DECREF(__file__); /* Unmarshall code object */ code = PI_PyMarshal_ReadObjectFromString((const char *) data, ntohl(ptoc->ulen)); - if (!code) { FATALERROR("Failed to unmarshal code object for %s\n", ptoc->name); PI_PyErr_Print(); return -1; } /* Run it */ - retval = PI_PyEval_EvalCode(code, main_dict, main_dict); + module = PI_PyImport_ExecCodeModule("__main__", code); /* If retval is NULL, an error occured. Otherwise, it is a Python object. * (Since we evaluate module-level code, which is not allowed to return an * object, the Python object returned is always None.) */ - if (!retval) { + if (!module) { PI_PyErr_Print(); /* If the error was SystemExit, PyErr_Print calls exit() without * returning. So don't print "Failed to execute" on SystemExit. */ FATALERROR("Failed to execute script %s\n", ptoc->name); return -1; } + Py_DECREF(module); + free(data); + + /* remove '__main__' from sys.modules */ + if (PI_PyObject_DelItem(sys_modules, __main__name) != 0) { + FATALERROR("Failed to remove '__main__' from sys.modules\n"); + return -1; + } } ptoc = pyi_arch_increment_toc_ptr(status, ptoc); } + Py_DECREF(__main__name); return 0; } diff --git a/bootloader/src/pyi_python.c b/bootloader/src/pyi_python.c index 6eff3d6774..c5a227b46a 100644 --- a/bootloader/src/pyi_python.c +++ b/bootloader/src/pyi_python.c @@ -57,12 +57,14 @@ DECLPROC(PyErr_Print); DECLPROC(PyImport_AddModule); DECLPROC(PyImport_ExecCodeModule); +DECLPROC(PyImport_GetModuleDict); DECLPROC(PyImport_ImportModule); DECLPROC(PyList_Append); DECLPROC(PyList_New); DECLPROC(PyLong_AsLong); DECLPROC(PyModule_GetDict); DECLPROC(PyObject_CallFunction); +DECLPROC(PyObject_DelItem); DECLPROC(PyObject_SetAttrString); DECLPROC(PyRun_SimpleString); DECLPROC(PyString_FromString); @@ -121,12 +123,14 @@ pyi_python_map_names(HMODULE dll, int pyvers) GETPROC(dll, PyErr_Print); GETPROC(dll, PyImport_AddModule); GETPROC(dll, PyImport_ExecCodeModule); + GETPROC(dll, PyImport_GetModuleDict); GETPROC(dll, PyImport_ImportModule); GETPROC(dll, PyList_Append); GETPROC(dll, PyList_New); GETPROC(dll, PyLong_AsLong); GETPROC(dll, PyModule_GetDict); GETPROC(dll, PyObject_CallFunction); + GETPROC(dll, PyObject_DelItem); GETPROC(dll, PyObject_SetAttrString); GETPROC(dll, PyRun_SimpleString); diff --git a/bootloader/src/pyi_python.h b/bootloader/src/pyi_python.h index 2151fd81bc..ee98d29978 100644 --- a/bootloader/src/pyi_python.h +++ b/bootloader/src/pyi_python.h @@ -108,11 +108,13 @@ EXTDECLPROC(void, PySys_SetPath, (wchar_t *)); EXTDECLPROC(int, PySys_SetArgvEx, (int, wchar_t **, int)); EXTDECLPROC(int, PyRun_SimpleString, (char *)); /* Py3: UTF-8 encoded string */ +EXTDECLPROC(PyObject *, PyImport_GetModuleDict, (void)); /* get sys.modules */ /* In Python 3 for these the first argument has to be a UTF-8 encoded string: */ EXTDECLPROC(PyObject *, PyImport_ExecCodeModule, (char *, PyObject *)); EXTDECLPROC(PyObject *, PyImport_ImportModule, (char *)); EXTDECLPROC(PyObject *, PyImport_AddModule, (char *)); +EXTDECLPROC(int, PyObject_DelItem, (PyObject *, PyObject *)); EXTDECLPROC(int, PyObject_SetAttrString, (PyObject *, char *, PyObject *)); EXTDECLPROC(PyObject *, PyList_New, (int)); EXTDECLPROC(int, PyList_Append, (PyObject *, PyObject *)); From d649976101b62e84259f0191c3a555620ca18100 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 15:03:07 +0100 Subject: [PATCH 03/10] Tests: Add a comment. --- tests/functional/scripts/pyi_filename.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/functional/scripts/pyi_filename.py b/tests/functional/scripts/pyi_filename.py index f5e54f8bca..d10827e1da 100644 --- a/tests/functional/scripts/pyi_filename.py +++ b/tests/functional/scripts/pyi_filename.py @@ -7,6 +7,6 @@ # The full license is in the file COPYING.txt, distributed with this software. #----------------------------------------------------------------------------- - +# Filename nust not contain a directory if __file__ != 'pyi_filename.py': raise ValueError(__file__) From 6af0012cc72ef5c69f282d46197f639317895f70 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 15:04:57 +0100 Subject: [PATCH 04/10] Tests: Make some tests more verbose in case of an error. --- tests/functional/specs/several-scripts1.spec | 2 +- tests/functional/specs/several-scripts2.spec | 2 +- tests/functional/specs/several-scripts3.spec | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/functional/specs/several-scripts1.spec b/tests/functional/specs/several-scripts1.spec index 06ab6ff223..2470b3445b 100644 --- a/tests/functional/specs/several-scripts1.spec +++ b/tests/functional/specs/several-scripts1.spec @@ -19,7 +19,7 @@ exe = EXE(pyz, a.scripts, exclude_binaries=True, name=app_name, - debug=False, + debug=True, console=True) coll = COLLECT(exe, a.binaries, diff --git a/tests/functional/specs/several-scripts2.spec b/tests/functional/specs/several-scripts2.spec index 57f84ba9a8..08361c4f1c 100644 --- a/tests/functional/specs/several-scripts2.spec +++ b/tests/functional/specs/several-scripts2.spec @@ -18,7 +18,7 @@ exe = EXE(pyz, a.scripts, exclude_binaries=True, name=app_name, - debug=False, + debug=True, console=True) coll = COLLECT(exe, a.binaries, diff --git a/tests/functional/specs/several-scripts3.spec b/tests/functional/specs/several-scripts3.spec index 2ca7750953..9e64b46a4b 100644 --- a/tests/functional/specs/several-scripts3.spec +++ b/tests/functional/specs/several-scripts3.spec @@ -19,7 +19,7 @@ exe = EXE(pyz, a.scripts, exclude_binaries=True, name=app_name, - debug=False, + debug=True, console=True) coll = COLLECT(exe, a.binaries, From 8f5fc8c38502218caf5e9fbbd3e6fc32153b076b Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 15:09:57 +0100 Subject: [PATCH 05/10] CArchiveWriter: For scripts set filename to basename. This is required since now the bootloader doesn't set the `__file__` attribute from the archive name anymore, but leaves this to `PyImport_ExecCodeModule`, which looks at code.co_filename. Hopefully this will not give us another unicode error. --- PyInstaller/archive/writers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PyInstaller/archive/writers.py b/PyInstaller/archive/writers.py index 7d20225ede..ef83ca49f8 100644 --- a/PyInstaller/archive/writers.py +++ b/PyInstaller/archive/writers.py @@ -371,7 +371,7 @@ def add(self, entry): # the object so it can be unmarshalled by the bootloader. code = get_code_object(nm, pathnm) - code = strip_paths_in_code(code) + code = strip_paths_in_code(code, os.path.basename(pathnm)) code_data = marshal.dumps(code) ulen = len(code_data) From e982de9d9bafdb17eb179057d3a48983f4aae19f Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 15:26:53 +0100 Subject: [PATCH 06/10] Bootloader: Small cleanup. Given the latest changes, calculating the filename is no longer necessary. Remove it and change the `VS()` call, so the output keeps the same. --- bootloader/src/pyi_launch.c | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/bootloader/src/pyi_launch.c b/bootloader/src/pyi_launch.c index 8cb78d0585..b243b0e754 100644 --- a/bootloader/src/pyi_launch.c +++ b/bootloader/src/pyi_launch.c @@ -363,8 +363,6 @@ int pyi_launch_run_scripts(ARCHIVE_STATUS *status) { unsigned char *data; - char buf[PATH_MAX]; - size_t namelen; TOC * ptoc = status->tocbuff; PyObject *__main__name; PyObject *sys_modules; @@ -389,16 +387,7 @@ pyi_launch_run_scripts(ARCHIVE_STATUS *status) data = pyi_arch_extract(status, ptoc); /* Set the __file__ attribute within the __main__ module, * for full compatibility with normal execution. */ - namelen = strnlen(ptoc->name, PATH_MAX); - if (namelen >= PATH_MAX-strlen(".py")-1) { - FATALERROR("Name exceeds PATH_MAX\n"); - return -1; - } - - strcpy(buf, ptoc->name); - strcat(buf, ".py"); - VS("LOADER: Running %s\n", buf); - + VS("LOADER: Running %s.py\n", ptoc->name); /* Unmarshall code object */ code = PI_PyMarshal_ReadObjectFromString((const char *) data, ntohl(ptoc->ulen)); From a71313611e74edf28ba5a511e6e5b6dd078bda2c Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 15:31:14 +0100 Subject: [PATCH 07/10] Bootloader: Update comments. Update wording and move to better place. --- bootloader/src/pyi_launch.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bootloader/src/pyi_launch.c b/bootloader/src/pyi_launch.c index b243b0e754..7b1f208cdb 100644 --- a/bootloader/src/pyi_launch.c +++ b/bootloader/src/pyi_launch.c @@ -385,8 +385,6 @@ pyi_launch_run_scripts(ARCHIVE_STATUS *status) if (ptoc->typcd == ARCHIVE_ITEM_PYSOURCE) { /* Get data out of the archive. */ data = pyi_arch_extract(status, ptoc); - /* Set the __file__ attribute within the __main__ module, - * for full compatibility with normal execution. */ VS("LOADER: Running %s.py\n", ptoc->name); /* Unmarshall code object */ @@ -396,7 +394,13 @@ pyi_launch_run_scripts(ARCHIVE_STATUS *status) PI_PyErr_Print(); return -1; } - /* Run it */ + + /* Run the code */ + /* For full compatibility with normal execution, the __file__ + * attribute within the __main__ module needs to be set. + * PyImport_ExecCodeModule() will set it based on + * code.co_filename. PyInstaller takes care that co_filename is + * correct. */ module = PI_PyImport_ExecCodeModule("__main__", code); /* If retval is NULL, an error occured. Otherwise, it is a Python object. From e8f07733b162c80124e842ed685ee909c2ff8885 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 17:11:38 +0100 Subject: [PATCH 08/10] Tests: Fix missing import in a test-case. This was unnoticed since while all scripts shared the same global variables since the import was done by some bootstrap script running before. --- tests/functional/scripts/pyi_module_attributes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/functional/scripts/pyi_module_attributes.py b/tests/functional/scripts/pyi_module_attributes.py index 8b0fcb68c7..e102f7534f 100644 --- a/tests/functional/scripts/pyi_module_attributes.py +++ b/tests/functional/scripts/pyi_module_attributes.py @@ -14,6 +14,7 @@ import copy import os +import sys import subprocess import xml.etree.ElementTree as ET import xml.etree.cElementTree as cET From daf60a6d684f54bcc2bc2aae1bb7b1a6387deb28 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 17:27:29 +0100 Subject: [PATCH 09/10] Loader: Move ctypes hooks into a module. Given the latest changes, pyiboot01_bootstrap's globals are gone (more precisely None) after the script has finished. But the hooked classes still reference to them (including 'sys' and 'os'). I decided to make this into a module instead of more complicated code like mix-in-classes and lot of import statements. --- PyInstaller/depend/analysis.py | 1 + PyInstaller/loader/pyiboot01_bootstrap.py | 83 +---------------- PyInstaller/loader/pyimod04_ctypes.py | 103 ++++++++++++++++++++++ 3 files changed, 107 insertions(+), 80 deletions(-) create mode 100644 PyInstaller/loader/pyimod04_ctypes.py diff --git a/PyInstaller/depend/analysis.py b/PyInstaller/depend/analysis.py index f922e82733..a01a52016e 100644 --- a/PyInstaller/depend/analysis.py +++ b/PyInstaller/depend/analysis.py @@ -631,6 +631,7 @@ def get_bootstrap_modules(): ('pyimod01_os_path', os.path.join(loaderpath, 'pyimod01_os_path.pyc'), 'PYMODULE'), ('pyimod02_archive', os.path.join(loaderpath, 'pyimod02_archive.pyc'), 'PYMODULE'), ('pyimod03_importers', os.path.join(loaderpath, 'pyimod03_importers.pyc'), 'PYMODULE'), + ('pyimod04_ctypes', os.path.join(loaderpath, 'pyimod04_ctypes.pyc'), 'PYMODULE'), ('pyiboot01_bootstrap', os.path.join(loaderpath, 'pyiboot01_bootstrap.py'), 'PYSOURCE'), ] # TODO Why is here the call to TOC()? diff --git a/PyInstaller/loader/pyiboot01_bootstrap.py b/PyInstaller/loader/pyiboot01_bootstrap.py index bc053b3311..be6f48ca93 100644 --- a/PyInstaller/loader/pyiboot01_bootstrap.py +++ b/PyInstaller/loader/pyiboot01_bootstrap.py @@ -123,86 +123,9 @@ def isatty(self): if sys.warnoptions: import warnings -try: - import ctypes - import os - from ctypes import LibraryLoader, DEFAULT_MODE - - def _frozen_name(name): - if name: - frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name)) - if os.path.exists(frozen_name): - name = frozen_name - return name - - class PyInstallerImportError(OSError): - def __init__(self, name): - self.msg = ("Failed to load dynlib/dll %r. " - "Most probably this dynlib/dll was not found " - "when the application was frozen.") % name - self.args = (self.msg,) - - class PyInstallerCDLL(ctypes.CDLL): - def __init__(self, name, *args, **kwargs): - name = _frozen_name(name) - try: - super(PyInstallerCDLL, self).__init__(name, *args, **kwargs) - except Exception as base_error: - raise PyInstallerImportError(name) - - ctypes.CDLL = PyInstallerCDLL - ctypes.cdll = LibraryLoader(PyInstallerCDLL) - - class PyInstallerPyDLL(ctypes.PyDLL): - def __init__(self, name, *args, **kwargs): - name = _frozen_name(name) - try: - super(PyInstallerPyDLL, self).__init__(name, *args, **kwargs) - except Exception as base_error: - raise PyInstallerImportError(name) - - ctypes.PyDLL = PyInstallerPyDLL - ctypes.pydll = LibraryLoader(PyInstallerPyDLL) - - if sys.platform.startswith('win'): - class PyInstallerWinDLL(ctypes.WinDLL): - def __init__(self, name,*args, **kwargs): - name = _frozen_name(name) - try: - super(PyInstallerWinDLL, self).__init__(name, *args, **kwargs) - except Exception as base_error: - raise PyInstallerImportError(name) - - ctypes.WinDLL = PyInstallerWinDLL - ctypes.windll = LibraryLoader(PyInstallerWinDLL) - - class PyInstallerOleDLL(ctypes.OleDLL): - def __init__(self, name,*args, **kwargs): - name = _frozen_name(name) - try: - super(PyInstallerOleDLL, self).__init__(name, *args, **kwargs) - except Exception as base_error: - raise PyInstallerImportError(name) - - ctypes.OleDLL = PyInstallerOleDLL - ctypes.oledll = LibraryLoader(PyInstallerOleDLL) -except ImportError: - pass - -# On Mac OS X insert sys._MEIPASS in the first position of the list of paths -# that ctypes uses to search for libraries. -# -# Note: 'ctypes' module will NOT be bundled with every app because code in this -# module is not scanned for module dependencies. It is safe to wrap -# 'ctypes' module into 'try/except ImportError' block. -if sys.platform.startswith('darwin'): - try: - from ctypes.macholib import dyld - dyld.DEFAULT_LIBRARY_FALLBACK.insert(0, sys._MEIPASS) - except ImportError: - # Do nothing when module 'ctypes' is not available. - pass - +# Install the hooks for ctypes +import pyimod04_ctypes +pyimod04_ctypes.install() # Make .eggs and zipfiles available at runtime d = "eggs" diff --git a/PyInstaller/loader/pyimod04_ctypes.py b/PyInstaller/loader/pyimod04_ctypes.py new file mode 100644 index 0000000000..05eb6ad1a9 --- /dev/null +++ b/PyInstaller/loader/pyimod04_ctypes.py @@ -0,0 +1,103 @@ +#----------------------------------------------------------------------------- +# Copyright (c) 2005-2017, 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. +#----------------------------------------------------------------------------- + +""" +Hooks to make ctypes.CDLL, .PyDLL, etc. look in sys._MEIPASS first. +""" + +import sys + +def install(): + """Install the hooks. + + This can not be done at module-level, since the import machinery is not + setup completely when this module is executed. + """ + + import os + try: + import ctypes + from ctypes import LibraryLoader, DEFAULT_MODE + + def _frozen_name(name): + if name: + frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name)) + if os.path.exists(frozen_name): + name = frozen_name + return name + + class PyInstallerImportError(OSError): + def __init__(self, name): + self.msg = ("Failed to load dynlib/dll %r. " + "Most probably this dynlib/dll was not found " + "when the application was frozen.") % name + self.args = (self.msg,) + + class PyInstallerCDLL(ctypes.CDLL): + def __init__(self, name, *args, **kwargs): + name = _frozen_name(name) + try: + super(PyInstallerCDLL, self).__init__(name, *args, **kwargs) + except Exception as base_error: + raise PyInstallerImportError(name) + + ctypes.CDLL = PyInstallerCDLL + ctypes.cdll = LibraryLoader(PyInstallerCDLL) + + class PyInstallerPyDLL(ctypes.PyDLL): + def __init__(self, name, *args, **kwargs): + name = _frozen_name(name) + try: + super(PyInstallerPyDLL, self).__init__(name, *args, **kwargs) + except Exception as base_error: + raise PyInstallerImportError(name) + + ctypes.PyDLL = PyInstallerPyDLL + ctypes.pydll = LibraryLoader(PyInstallerPyDLL) + + if sys.platform.startswith('win'): + class PyInstallerWinDLL(ctypes.WinDLL): + def __init__(self, name,*args, **kwargs): + name = _frozen_name(name) + try: + super(PyInstallerWinDLL, self).__init__(name, *args, **kwargs) + except Exception as base_error: + raise PyInstallerImportError(name) + + ctypes.WinDLL = PyInstallerWinDLL + ctypes.windll = LibraryLoader(PyInstallerWinDLL) + + class PyInstallerOleDLL(ctypes.OleDLL): + def __init__(self, name,*args, **kwargs): + name = _frozen_name(name) + try: + super(PyInstallerOleDLL, self).__init__(name, *args, **kwargs) + except Exception as base_error: + raise PyInstallerImportError(name) + + ctypes.OleDLL = PyInstallerOleDLL + ctypes.oledll = LibraryLoader(PyInstallerOleDLL) + + except ImportError: + # ctypes is not frozen in this application + pass + +# On Mac OS X insert sys._MEIPASS in the first position of the list of paths +# that ctypes uses to search for libraries. +# +# Note: 'ctypes' module will NOT be bundled with every app because code in this +# module is not scanned for module dependencies. It is safe to wrap +# 'ctypes' module into 'try/except ImportError' block. +if sys.platform.startswith('darwin'): + try: + from ctypes.macholib import dyld + dyld.DEFAULT_LIBRARY_FALLBACK.insert(0, sys._MEIPASS) + except ImportError: + # Do nothing when module 'ctypes' is not available. + pass From 97e200c78ceef4c2ff25ef45fce7306352146670 Mon Sep 17 00:00:00 2001 From: Hartmut Goebel Date: Sat, 25 Nov 2017 17:51:55 +0100 Subject: [PATCH 10/10] Tests: Add test-case if ctypes-hooks are in place. --- tests/functional/test_basic.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/functional/test_basic.py b/tests/functional/test_basic.py index 0e58de5e7f..c8cda6b932 100644 --- a/tests/functional/test_basic.py +++ b/tests/functional/test_basic.py @@ -196,6 +196,14 @@ def test_module_reload(pyi_builder): pyi_builder.test_script('pyi_module_reload.py') +def test_ctypes_hooks_are_in_place(pyi_builder): + pyi_builder.test_source( + """ + import ctypes + assert ctypes.CDLL.__name__ == 'PyInstallerCDLL', ctypes.CDLL + """) + + # TODO test it on OS X. @skipif_no_compiler def test_load_dll_using_ctypes(monkeypatch, pyi_builder, compiled_dylib):