indygreg / PyOxidizer

A modern Python application packaging and distribution tool
Mozilla Public License 2.0
5.44k stars 234 forks source link

PyQT won't work #74

Open jss-cmyk opened 5 years ago

jss-cmyk commented 5 years ago

import PyQt5 from PyQt5.QtCore import from PyQt5.QtGui import from PyQt5.QtWidgets import *

while PyQt5 has been successfully imported. the following lines will through errors as follow

Traceback (most recent call last): File "importlib.util", line 94, in find_spec File "dpt_installer", line 17, in ModuleNotFoundError: No module named 'PyQt5.QtCore' error: cargo run failed

jss-cmyk commented 5 years ago

BTW: PyQt5.QtCore is a so file: QtCore.so works well on MacOS10.3.6, but pyoxidizer won't find the module.

when test code in pyoxide myself, it says:

Traceback (most recent call last): File "", line 1, in ImportError: cannot import name 'QtCore' from 'PyQt5' (unknown location)

jss-cmyk commented 5 years ago

I used pyinstaller, there is a hook mechanism and pyqt works. but pyinstaller doesn't work with SHA256.so library load, seems to causing issues when dealing with c-extension libraries.

kristofmulier commented 4 years ago

@indygreg I'm a bit confused right now. I'm not yet a PyOxidizer user. I'd like to use it if it supports PyQt5. Does it support PyQt5?

bryab commented 3 years ago

If PyQt5 works, I cannot figure it out. I get this error when attempting to use it.

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "myscript", line 1, in <module>
  File "PyQt5", line 49, in <module>
  File "PyQt5", line 32, in find_qt
NameError: name '__file__' is not defined

It seems it depends on file, which doesn't exist in the PyOxidizer environment.

ryancinsight commented 3 years ago

@bryab for packages with file use this configuration of setting fallback location, $Origin, and where to pip install:

def make_exe(dist): policy = dist.make_python_packaging_policy()

# Use in-memory location for adding resources by default.
policy.resources_location = "in-memory"

# Attempt to add resources relative to the built binary when
# `resources_location` fails.
policy.resources_location_fallback = "filesystem-relative:lib"

# This variable defines the configuration of the embedded Python
# interpreter. By default, the interpreter will run a Python REPL
# using settings that are appropriate for an "isolated" run-time
# environment.
#
# The configuration of the embedded Python interpreter can be modified
# by setting attributes on the instance. Some of these are
# documented below.
python_config = dist.make_python_interpreter_config()

# Make the embedded interpreter behave like a `python` process.
# python_config.config_profile = "python"

# Set initial value for `sys.path`. If the string `$ORIGIN` exists in
# a value, it will be expanded to the directory of the built executable.
python_config.module_search_paths = ["$ORIGIN/lib"]

#python_config.run_command = "<>"

# Run a Python module as __main__ when the interpreter starts.
# python_config.run_module = "<module>"

# Run a Python file when the interpreter starts.
# python_config.run_filename = "/path/to/file"

# Produce a PythonExecutable from a Python distribution, embedded
# resources, and other options. The returned object represents the
# standalone executable that will be built.
exe = dist.to_python_executable(
    name="pyqt",

    # If no argument passed, the default `PythonPackagingPolicy` for the
    # distribution is used.
    packaging_policy=policy,

    # If no argument passed, the default `PythonInterpreterConfig` is used.
    config=python_config,
)

# Invoke `pip install` using a requirements file and add the collected resources
# to our binary.
for resource in exe.pip_install(["PyQt5"]):
    resource.add_location = "filesystem-relative:lib"
    exe.add_python_resource(resource)

return exe

then:

import PyQt5 from PyQt5.QtCore import from PyQt5.QtGui import from PyQt5.QtWidgets import *

will have no errors

Nonta777 commented 3 years ago

Copy the python3.dll in your working directory. python 3.dll is in the base environment of conda. I worked with it.

matkuki commented 2 years ago

Hi everyone,

System specs: Windows 10 x64, Python 3.10, PyOxidizer 0.22.0

I'm trying to get a simple PyQt5 or PyQt6 application up and running. Now the compilation process goes through without any problems when running pyoxidizer run, but then with PyQt5 I get this error:

Traceback (most recent call last):
  File "<string>", line 2, in <module>
ImportError: DLL load failed while importing QtCore: The specified module could not be found.
error: cargo run failed

and when switching to PyQt6 with the same code just adjusting the import module names, I get this:

qt.qpa.plugin: Could not find the Qt platform plugin "windows" in ""
This application failed to start because no Qt platform plugin could be initialized. Reinstalling th
e application may fix this problem.

error: cargo run failed

Here is pyoxidizer.bzl file contents:

# This file defines how PyOxidizer application building and packaging is
# performed. See PyOxidizer's documentation at
# https://pyoxidizer.readthedocs.io/en/stable/ for details of this
# configuration file format.

# Configuration files consist of functions which define build "targets."
# This function creates a Python executable and installs it in a destination
# directory.
def make_exe():
    # Obtain the default PythonDistribution for our build target. We link
    # this distribution into our produced executable and extract the Python
    # standard library from it.
    dist = default_python_distribution()

    # This function creates a `PythonPackagingPolicy` instance, which
    # influences how executables are built and how resources are added to
    # the executable. You can customize the default behavior by assigning
    # to attributes and calling functions.
    policy = dist.make_python_packaging_policy()

    # Enable support for non-classified "file" resources to be added to
    # resource collections.
    # policy.allow_files = True

    # Control support for loading Python extensions and other shared libraries
    # from memory. This is only supported on Windows and is ignored on other
    # platforms.
    policy.allow_in_memory_shared_library_loading = True

    # Use filesystem-relative location for adding resources by default.
    policy.resources_location = "filesystem-relative:lib"

    # Attempt to add resources relative to the built binary when
    # `resources_location` fails.
    policy.resources_location_fallback = "filesystem-relative:lib"

    # The configuration of the embedded Python interpreter can be modified
    # by setting attributes on the instance. Some of these are
    # documented below.
    python_config = dist.make_python_interpreter_config()

    # Make the embedded interpreter behave like a `python` process.
    # python_config.config_profile = "python"

    # Set initial value for `sys.path`. If the string `$ORIGIN` exists in
    # a value, it will be expanded to the directory of the built executable.
    python_config.module_search_paths = ["$ORIGIN/lib"]

    # Evaluate a string as Python code when the interpreter starts.
    python_config.run_command = """
import PyQt6.QtCore
import PyQt6.QtWidgets
import PyQt6.Qsci
import sys

application = PyQt6.QtWidgets.QApplication(sys.argv)
editor = PyQt6.Qsci.QsciScintilla()
editor.show()
application.exec()
"""

    # Produce a PythonExecutable from a Python distribution, embedded
    # resources, and other options. The returned object represents the
    # standalone executable that will be built.
    exe = dist.to_python_executable(
        name="py310scintillatest",

        # If no argument passed, the default `PythonPackagingPolicy` for the
        # distribution is used.
        packaging_policy=policy,

        # If no argument passed, the default `PythonInterpreterConfig` is used.
        config=python_config,
    )

    policy.resources_location_fallback = "filesystem-relative:lib"
    for resource in exe.pip_download(["PyQt6"]):
        resource.add_location = "filesystem-relative:lib"
        exe.add_python_resource(resource)
    for resource in exe.pip_download(["PyQt6-QScintilla"]):
        resource.add_location = "filesystem-relative:lib"
        exe.add_python_resource(resource)
#    for resource in exe.pip_download(["PyQt5==5.15.5"]):
#        resource.add_location = "filesystem-relative:lib"
#        exe.add_python_resource(resource)
#    for resource in exe.pip_download(["QScintilla==2.13.3"]):
#        resource.add_location = "filesystem-relative:lib"
#        exe.add_python_resource(resource)

    # Copy Windows runtime DLLs next to the build executable and error if this
    # cannot be done.
    exe.windows_runtime_dlls_mode = "always"

    # Return our `PythonExecutable` instance so it can be built and
    # referenced by other consumers of this target.
    return exe

def make_embedded_resources(exe):
    return exe.to_embedded_resources()

def make_install(exe):
    # Create an object that represents our installed application file layout.
    files = FileManifest()

    # Add the generated executable to our install layout in the root directory.
    files.add_python_resource(".", exe)

    return files

def make_msi(exe):
    # See the full docs for more. But this will convert your Python executable
    # into a `WiXMSIBuilder` Starlark type, which will be converted to a Windows
    # .msi installer when it is built.
    return exe.to_wix_msi_builder(
        # Simple identifier of your app.
        "myapp",
        # The name of your application.
        "My Application",
        # The version of your application.
        "1.0",
        # The author/manufacturer of your application.
        "Alice Jones"
    )

# Dynamically enable automatic code signing.
def register_code_signers():
    # You will need to run with `pyoxidizer build --var ENABLE_CODE_SIGNING 1` for
    # this if block to be evaluated.
    if not VARS.get("ENABLE_CODE_SIGNING"):
        return

    # Use a code signing certificate in a .pfx/.p12 file, prompting the
    # user for its path and password to open.
    # pfx_path = prompt_input("path to code signing certificate file")
    # pfx_password = prompt_password(
    #     "password for code signing certificate file",
    #     confirm = True
    # )
    # signer = code_signer_from_pfx_file(pfx_path, pfx_password)

    # Use a code signing certificate in the Windows certificate store, specified
    # by its SHA-1 thumbprint. (This allows you to use YubiKeys and other
    # hardware tokens if they speak to the Windows certificate APIs.)
    # sha1_thumbprint = prompt_input(
    #     "SHA-1 thumbprint of code signing certificate in Windows store"
    # )
    # signer = code_signer_from_windows_store_sha1_thumbprint(sha1_thumbprint)

    # Choose a code signing certificate automatically from the Windows
    # certificate store.
    # signer = code_signer_from_windows_store_auto()

    # Activate your signer so it gets called automatically.
    # signer.activate()

# Call our function to set up automatic code signers.
register_code_signers()

# Tell PyOxidizer about the build targets defined above.
register_target("exe", make_exe)
register_target("resources", make_embedded_resources, depends=["exe"], default_build_script=True)
register_target("install", make_install, depends=["exe"], default=True)
register_target("msi_installer", make_msi, depends=["exe"])

# Resolve whatever targets the invoker of this configuration file is requesting
# be resolved.
resolve_targets()

Any ideas would be greatly appreciated, thanks.

matkuki commented 2 years ago

Found out that PyOxidizer doesn't install the necessary .dll files for PyQt5. Here on Windows at least, the Python installation PyQt5 package directory has a subdirectory Qt5 that contains a bin subdirectory, so it's: C:\Python310\Lib\site-packages\PyQt5\Qt5\bin. This is where all of the extra needed .dll files are located.

In the PyOxidizer compiled version with the .bzl file from above, this directory is missing! Just copying the Qt5 into the lib/PyQt5 directory of the PyOxidizer build directory make the application work!

matkuki commented 2 years ago

... tried also with the PyQt6 version and it's the same problem. If you just copy the .dll files from the Python installation directory, it starts working.

Any ideas why PyQt5/6 isn't installed completely when building with PyOxidizer? @indygreg @ryancinsight ?

ryancinsight commented 2 years ago

@matkuki turn off allow shared memory loading it only works well for Linux/ edit at least in my experience for so files guess this behavior changed. Dll files can't be opened from zipped packages which in a way is how they are stored in the exe. Same with the pyd files but the filtering seems to sometimes work better for those. Personally, I use a script to copy all of the dll files in the packages next to the exe removing the duplicates and recursively searching for required system dll files to make completely standalone. Than an executable that installs the program in hidden folder and subsequently used symbolically if you are looking to make compact.

matkuki commented 2 years ago

Ah, thank you for the explanation, @ryancinsight 👍 But even if policy.allow_in_memory_shared_library_loading is set to False, why is there a need to manually copy the .dll files? Shouldn't the procedure:

    for resource in exe.pip_download(["PyQt6"]):
        resource.add_location = "filesystem-relative:lib"
        exe.add_python_resource(resource)
    for resource in exe.pip_download(["PyQt6-QScintilla"]):
        resource.add_location = "filesystem-relative:lib"
        exe.add_python_resource(resource)

... install the entire PyQt6 package into the lib directory, including the .dll files?

Addendum: To fix this problem change the code in the above .bzl script from:

for resource in exe.pip_download...

to:

for resource in exe.pip_install...

and it should work.

ryancinsight commented 2 years ago

Very interesting, I would consider that a bug unless I misinterpreted the operator manual @indygreg