Repository: pyinstaller/pyinstaller

Issue:
Windows Programm created by pyinstaller 4.7 creates popup window not finding "GetDaylightFlag" in ucrtbase.dll
## Description of the issue

I created a Docker container with wine and I used this setup to create a Windows executable.
It worked very well with Python 2.7, but now I had to switch to Python 3.7.4 and pyinstaller 4.7, built with wine 6.0.2 on a Ubuntu 20.04 base container.

The single EXE is created (I am using --onefile), but when I start it a pop up window is displayed with this text:
```
The procedure entry point GetDaylightFlag could not be located in the dynamic link library
C:\Users\<uname>\AppData\Local\Temp\_MEI172162\ucrtbase.dll
```
I am using:
``` 
Edition	Windows 10 Enterprise
Version	21H2
Installed on	‎05.‎05.‎2021
OS build	19044.1526
Experience	Windows Feature Experience Pack 120.2212.4170.0
```
I checked this DLL with nm and I found the symbol
```
00000000709bd8f8 T GetDaylightFlag
```
When I build with *--onedir*, I can start the EXE without this popup.

I already found #4230, which has been fixed at Oct 19, 2019. So I am quite sure it is already in pyinstaller 4.7.
I don't believe it is a wine issue, because it works with the *--onedir* option.

### Debug output
I created the EXE with "--debug". Here the log until the popup comes:
```
[17216] LOADER: Executing self as child
[17216] LOADER: set _MEIPASS2 to C:\Users\<uname>\AppData\Local\Temp\_MEI172162
[17216] LOADER: Setting up to run child
[17216] LOADER: Creating child process
[17216] LOADER: Waiting for child process to finish...
[14084] PyInstaller Bootloader 3.x
[14084] LOADER: executable is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653.exe
[14084] LOADER: homepath is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist
[14084] LOADER: _MEIPASS2 is C:\Users\<uname>\AppData\Local\Temp\_MEI172162
[14084] LOADER: archivename is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653.exe
[14084] LOADER: Cookie found at offset 0x9DDB0D
[14084] LOADER: SetDllDirectory(C:\Users\<uname>\AppData\Local\Temp\_MEI172162)
[14084] LOADER: Already in the child - running user's code.
[14084] LOADER: ucrtbase.dll found: C:\Users\<uname>\AppData\Local\Temp\_MEI172162\ucrtbase.dll
```
It seems the loader is finding *ucrtbase.dll*, but then it is not really used, I guess.

Here the run with the *--onedir* option:
```
[13528] PyInstaller Bootloader 3.x
[13528] LOADER: executable is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653\sys_conf653.exe
[13528] LOADER: homepath is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653
[13528] LOADER: _MEIPASS2 is NULL
[13528] LOADER: archivename is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653\sys_conf653.exe
[13528] LOADER: Cookie found at offset 0x59FA7
[13528] LOADER: No need to extract files to run; setting extractionpath to homepath
[13528] LOADER: SetDllDirectory(C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653)
[13528] LOADER: Already in the child - running user's code.
[13528] LOADER: Python library: C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653\python37.dll
[13528] LOADER: Loaded functions from Python library.
[13528] LOADER: Manipulating environment (sys.path, sys.prefix)
[13528] LOADER: sys.prefix is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653
[13528] LOADER: Pre-init sys.path is C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653\base_library.zip;C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653\lib-dynload;C:\workspaces\develop\git\IP_1_8\sys_conf653_tool_win\dist\sys_conf653
[13528] LOADER: Setting runtime options
[13528] LOADER: Runtime option: v
[13528] LOADER: Initializing python
import _frozen_importlib # frozen
```



Discussion:

Resolving patch:
diff --git a/PyInstaller/compat.py b/PyInstaller/compat.py
--- a/PyInstaller/compat.py
+++ b/PyInstaller/compat.py
@@ -39,6 +39,7 @@
 
 is_win = sys.platform.startswith('win')
 is_win_10 = is_win and (platform.win32_ver()[0] == '10')
+is_win_wine = False  # Running under Wine; determined later on.
 is_cygwin = sys.platform == 'cygwin'
 is_darwin = sys.platform == 'darwin'  # Mac OS X
 
@@ -244,6 +245,40 @@
 # Machine suffix for bootloader.
 machine = _pyi_machine(platform.machine(), platform.system())
 
+
+# Wine detection and support
+def is_wine_dll(filename):
+    """
+    Check if the given PE file is a Wine DLL (PE-converted built-in, or fake/placeholder one).
+
+    Returns True if the given file is a Wine DLL, False if not (or if file cannot be analyzed or does not exist).
+    """
+    _WINE_SIGNATURES = (
+        b'Wine builtin DLL',  # PE-converted Wine DLL
+        b'Wine placeholder DLL',  # Fake/placeholder Wine DLL
+    )
+    _MAX_LEN = max([len(sig) for sig in _WINE_SIGNATURES])
+
+    # Wine places their DLL signature in the padding area between the IMAGE_DOS_HEADER and IMAGE_NT_HEADERS. So we need
+    # to compare the bytes that come right after IMAGE_DOS_HEADER, i.e., after initial 64 bytes. We can read the file
+    # directly and avoid using the pefile library to avoid performance penalty associated with full header parsing.
+    try:
+        with open(filename, 'rb') as fp:
+            fp.seek(64)
+            signature = fp.read(_MAX_LEN)
+        return signature.startswith(_WINE_SIGNATURES)
+    except Exception:
+        pass
+    return False
+
+
+if is_win:
+    try:
+        import ctypes.util  # noqa: E402
+        is_win_wine = is_wine_dll(ctypes.util.find_library('kernel32'))
+    except Exception:
+        pass
+
 # Set and get environment variables does not handle unicode strings correctly on Windows.
 
 # Acting on os.environ instead of using getenv()/setenv()/unsetenv(), as suggested in
diff --git a/PyInstaller/depend/dylib.py b/PyInstaller/depend/dylib.py
--- a/PyInstaller/depend/dylib.py
+++ b/PyInstaller/depend/dylib.py
@@ -225,23 +225,35 @@ def search(self, libname):
 
     exclude_list = WinExcludeList(exclude_list)
 
+_seen_wine_dlls = set()  # Used for warning tracking in include_library()
+
 
 def include_library(libname):
     """
     Check if the dynamic library should be included with application or not.
     """
-    # For configuration phase we need to have exclude / include lists None so these checking is skipped and library gets
-    # included.
     if exclude_list:
         if exclude_list.search(libname) and not include_list.search(libname):
-            # Library is excluded and is not overriden by include list. It should be then excluded.
+            # Library is excluded and is not overriden by include list. It should be excluded.
             return False
-        else:
-            # Include library.
-            return True
-    else:
-        # By default include library.
-        return True
+
+    # If we are running under Wine and the library is a Wine built-in DLL, ensure that it is always excluded. Typically,
+    # excluding a DLL leads to an incomplete bundle and run-time errors when the said DLL is not installed on the target
+    # system. However, having Wine built-in DLLs collected is even more detrimental, as they usually provide Wine's
+    # implementation of low-level functionality, and therefore cannot be used on actual Windows (i.e., system libraries
+    # from the C:\Windows\system32 directory that might end up collected due to ``_win_includes`` list; a prominent
+    # example are VC runtime DLLs, for which Wine provides their own implementation, unless user explicitly installs
+    # Microsoft's VC redistributable package in their Wine environment). Therefore, excluding the Wine built-in DLLs
+    # actually improves the chances of the bundle running on Windows, or at least makes the issue easier to debug by
+    # turning it into the "standard" missing DLL problem. Exclusion should not affect the bundle's ability to run under
+    # Wine itself, as the excluded DLLs are available there.
+    if compat.is_win_wine and compat.is_wine_dll(libname):
+        if libname not in _seen_wine_dlls:
+            logger.warning("Excluding Wine built-in DLL: %s", libname)  # displayed only if DLL would have been included
+            _seen_wine_dlls.add(libname)  # display only once for each DLL
+        return False
+
+    return True
 
 
 # Patterns for suppressing warnings about missing dynamically linked libraries


Test patch: