Repository: pyinstaller/pyinstaller

Issue:
AttributeError: 'str' object has no attribute '_MEIPASS'
## Description of the issue
I've used pyinstaller to convert my py to exe. App is working fine, all of the features are working ok except "pyperclip.copy". 
Even I have no issue with "pyperclip.paste", only copying to clipboard.
 

## Context information (for bug reports)
Whenever I use the command "pyperclip.copy(copy_clipboard)" to copy string to clipboard, it doesn't work instead I get following error:

Exception in Tkinter callback
Traceback (most recent call last):
  File "tkinter\__init__.py", line 1884, in __call__
  File "xxxxx.py", line 5107, in ctrl_c
  File "pyperclip\__init__.py", line 658, in lazy_load_stub_copy
  File "pyperclip\__init__.py", line 541, in determine_clipboard
  File "pyperclip\__init__.py", line 371, in init_windows_clipboard
  File "PyInstaller\loader\pyiboot01_bootstrap.py", line 142, in __init__
  File "PyInstaller\loader\pyiboot01_bootstrap.py", line 128, in _frozen_name
AttributeError: 'str' object has no attribute '_MEIPASS'


* Output of `pyinstaller --version`: ```4.3```
* Version of Python: 3.9.1
* Platform: Windows 10
* Did you also try this on another platform? No 


* try the latest development version, using the following command: I've tried but it failed to create exe file.


I've tried all suggested workarounds but couldn't fix this issue. I'm not an advanced programmer so it might be silly issue although I appreciate any help in this regard. Thanks!


Discussion:

Resolving patch:
diff --git a/PyInstaller/depend/analysis.py b/PyInstaller/depend/analysis.py
--- a/PyInstaller/depend/analysis.py
+++ b/PyInstaller/depend/analysis.py
@@ -847,6 +847,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'),  # noqa: E501
         ('pyiboot01_bootstrap', os.path.join(loaderpath, 'pyiboot01_bootstrap.py'), 'PYSOURCE'),
     ]
     return loader_mods
diff --git a/PyInstaller/loader/pyiboot01_bootstrap.py b/PyInstaller/loader/pyiboot01_bootstrap.py
--- a/PyInstaller/loader/pyiboot01_bootstrap.py
+++ b/PyInstaller/loader/pyiboot01_bootstrap.py
@@ -118,86 +118,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) and not os.path.isdir(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) from base_error
-
-    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) from base_error
-
-    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) from base_error
-
-        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) from base_error
-
-        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  # noqa: E402
+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
--- /dev/null
+++ b/PyInstaller/loader/pyimod04_ctypes.py
@@ -0,0 +1,105 @@
+#-----------------------------------------------------------------------------
+# Copyright (c) 2005-2021, 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 must be done from a function as opposed to at module-level,
+    because when the module is imported/executed, the import machinery
+    is not completely set up yet.
+    """
+
+    import os
+
+    try:
+        import ctypes
+    except ImportError:
+        # ctypes is not included in the frozen application
+        return
+
+    def _frozen_name(name):
+        if name:
+            frozen_name = os.path.join(sys._MEIPASS, os.path.basename(name))
+            if os.path.exists(frozen_name) and not os.path.isdir(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().__init__(name, *args, **kwargs)
+            except Exception as base_error:
+                raise PyInstallerImportError(name) from base_error
+
+    ctypes.CDLL = PyInstallerCDLL
+    ctypes.cdll = ctypes.LibraryLoader(PyInstallerCDLL)
+
+    class PyInstallerPyDLL(ctypes.PyDLL):
+        def __init__(self, name, *args, **kwargs):
+            name = _frozen_name(name)
+            try:
+                super().__init__(name, *args, **kwargs)
+            except Exception as base_error:
+                raise PyInstallerImportError(name) from base_error
+
+    ctypes.PyDLL = PyInstallerPyDLL
+    ctypes.pydll = ctypes.LibraryLoader(PyInstallerPyDLL)
+
+    if sys.platform.startswith('win'):
+        class PyInstallerWinDLL(ctypes.WinDLL):
+            def __init__(self, name, *args, **kwargs):
+                name = _frozen_name(name)
+                try:
+                    super().__init__(name, *args, **kwargs)
+                except Exception as base_error:
+                    raise PyInstallerImportError(name) from base_error
+
+        ctypes.WinDLL = PyInstallerWinDLL
+        ctypes.windll = ctypes.LibraryLoader(PyInstallerWinDLL)
+
+        class PyInstallerOleDLL(ctypes.OleDLL):
+            def __init__(self, name, *args, **kwargs):
+                name = _frozen_name(name)
+                try:
+                    super().__init__(name, *args, **kwargs)
+                except Exception as base_error:
+                    raise PyInstallerImportError(name) from base_error
+
+        ctypes.OleDLL = PyInstallerOleDLL
+        ctypes.oledll = ctypes.LibraryLoader(PyInstallerOleDLL)
+
+
+# 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


Test patch:
diff --git a/tests/functional/test_basic.py b/tests/functional/test_basic.py
--- a/tests/functional/test_basic.py
+++ b/tests/functional/test_basic.py
@@ -224,6 +224,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):
diff --git a/tests/functional/test_regression.py b/tests/functional/test_regression.py
--- a/tests/functional/test_regression.py
+++ b/tests/functional/test_regression.py
@@ -102,3 +102,29 @@ def test_5734():
     """
     from PyInstaller.depend.utils import _resolveCtypesImports
     _resolveCtypesImports(["libc"])
+
+
+def test_5797(pyi_builder):
+    """
+    Ensure that user overriding os and/or sys (using them as variables)
+    in the global namespace does not affect PyInstaller's ctype hooks,
+    installed during bootstrap, which is scenario of the issue #5797.
+    """
+    pyi_builder.test_source(
+        """
+        import ctypes
+
+        # Overwrite sys and os in global namespace
+        sys = 'watever'
+        os = 'something'
+
+        # Use a ctypes.CDLL, which in turn calls _frozen_name()
+        # from PyInstaller's ctypes bootstrap hooks.
+        try:
+            ctypes.CDLL('nonexistent')
+        except Exception as e:
+            # We expect PyInstallerImportError to be raised
+            exception_name = type(e).__name__
+            assert exception_name == 'PyInstallerImportError', \
+                f"Unexpected execption type: {exception_name}"
+        """)