Repository: pyinstaller/pyinstaller

Issue:
Wrong python3.dll is collected if more than one Python version is installed
After packaging using the PyInstaller version 5.4.1, the application cannot be executed.
Only a couple of generated dlls are different packaging the application with PyInstaller 5.3 and PyInstaller 5.4.1.
One of them is python3.dll.

Replacing the python3.dll with the generated one using PyInstaller 5.3 for the packaging, solves the problem.


Discussion:
Can you open `build/<name>/Analysis-00.toc` file when building with 5.4.1 and when building 5.3, and check from where `python3.dll` was collected in each case?
Different versions are collected!

PyInstaller 5.3: ('python3.dll', 'C:\\Python39\\python3.dll', 'BINARY')
PyInstaller 5.4.1: ('python3.dll', 'C:\\Python310\\python3.dll', 'BINARY')
Which python version are you actually using? And, are both python versions in `PATH`?
My application use a virtual environment with Python 3.9 but both versions are listed within PATH in following order:
...
C:\Python310\
C:\Python39\
....

The first to clear would be the different behavior of PyInstaller in relation with this.
The second question would be why is Python 3.10 packaged if my application does not need it... Is there any way to avoid it?
I can find two Python dlls in the dist folder:
python3.dll
python39.dll
Additional information:
Python39 is in the local user PATH
Python310 is in the system PATH
Hmmm, well, I think the most straightforward solution to the problem here would be to remove python 3.10 from the PATH; temporarily, in the environment that you are running PyInstaller in.

With PATH order
```
C:\Python310
C:\Python39
```

running `where python3.dll` in terminal with your virtual environment probably resolves the `python3.dll` in both installations, in that order. So by chance (or due to ordering. if the order within PyInstaller's internals is the same), we end up picking the wrong one. I'm not sure why 5.3 picks the correct one, but I wouldn't be surprised if it was up to the chance as well (off my head, I can't think of any change made between the both versions that would affect this).

(As a side note, I think `python3.dll` is usually required by extensions in some 3rd party package that you are using...)
Thanks rokm! 
I have been able to independently and consistently reproduce this behavior with PyInstaller 5.4.1 on GitHub Actions -- where multiple python3-versions are installed and on the PATH. I _cannot_ reproduce it with PyInstaller 5.3, however, _or_ under environments where only one version of python3 is installed.

Two additional things I've noticed (which seems to support @rokm's hypothesis above of this being an ordering issue) is that, when this happens, the resultant PyInstaller bundle (i.e., the set of files generated by PyInstaller under the `dist/` directory) will a) contain more than one `python3*.dll` file (e.g., a `python310.dll` _and_ a `python39.dll`) and b) the version(s) of `python3*.dll` files included seems somewhat indeterminate/arbitrary (e.g., sometimes I get a `python310.dll` and a `python39.dll`, other times I get a `python310.dll` and a `python37.dll`). The latter of these is especially surprising since my project doesn't even support python3.7 (and I build my PyInstaller bundles inside tox/virtualenv where, presumably, only the specified interpreter should be used).
Now that I think about it, this is very likely a side effect of #6925 - because it extends the search path for shared libraries with paths found in `PATH` (among other things).

What happens is that if we collect a binary extension that is linked against `python3.dll` (the "unversioned" one, i.e., not `python3X.dll`, but `python3.dll` that forwards its symbols to the versioned `python3X.dll`), we need to find and collect that DLL. And due to multiple python installations being in `PATH`, we pick the wrong one (the one belonging to different python version). And because `python3.dll` forwards its symbols to versioned `python3X.dll`, we also collect that one (in addition to the correct versioned python DLL that the interpreter executable is linked against).

So we'll need to refine the DLL search path extension approach... Maybe consider only paths that are added to `PATH` during the package imports, which is the core idea behind #6925... or maybe handle `python3.dll` as a special case.
Perhaps always adding `sys.base_prefix` as a first search path on Windows (instead of just for MS store python) would also ensure that we pick correct `python3.dll` (and `python3X.dll` in case there are multiple installations of same version in `PATH`):
https://github.com/pyinstaller/pyinstaller/blob/7b2b93a186de0d47b67eed0077d748c8feb6d190/PyInstaller/building/build_main.py#L154-L155

Resolving patch:
diff --git a/PyInstaller/building/build_main.py b/PyInstaller/building/build_main.py
--- a/PyInstaller/building/build_main.py
+++ b/PyInstaller/building/build_main.py
@@ -146,12 +146,13 @@ def find_binary_dependencies(binaries, binding_redirects, import_packages):
     from PyInstaller.building.build_main import logger
     from PyInstaller import compat
 
-    # In the case of MS App Store python, add compat.base_prefix to extra library search paths. In addition to
-    # python38.dll (that we manage to resolve by other means, if necessary), this directory also contains
-    # python3.dll that might be required by some 3rd-party extension modules, and would otherwise end up missing
-    # during the dependency analysis.
+    # Extra library search paths (used on Windows to resolve DLL paths).
+    #
+    # Always search `sys.base_prefix`, and search it first. This ensures that we resolve the correct version of
+    # `python3X.dll` and `python3.dll` (a PEP-0384 stable ABI stub that forwards symbols to the fully versioned
+    # `python3X.dll`), regardless of other python installations that might be present in the PATH.
     extra_libdirs = []
-    if compat.is_ms_app_store:
+    if compat.is_win:
         extra_libdirs.append(compat.base_prefix)
 
     # On Windows with python >= 3.8, attempt to track calls to os.add_dll_directory() to obtain DLL search paths that


Test patch: