PyMiniRacer icon indicating copy to clipboard operation
PyMiniRacer copied to clipboard

working with pyinstaller

Open deli-c1ous opened this issue 1 year ago • 3 comments

Steps to reproduce

test.py:

from py_mini_racer import MiniRacer
mr = MiniRacer()

run pyinstaller test.py then generated test.exe, it happens that:

Traceback (most recent call last):
  File "test.py", line 2, in <module>
  File "py_mini_racer\_mini_racer.py", line 56, in __init__
  File "py_mini_racer\_dll.py", line 273, in init_mini_racer
  File "contextlib.py", line 137, in __enter__
  File "py_mini_racer\_dll.py", line 237, in _open_dll
  File "py_mini_racer\_dll.py", line 217, in _check_path
py_mini_racer._dll.LibNotFoundError: Native library or dependency not available at D:\test\dist\test\_internal\mini_racer.dll

Expected behavior

no error and no missing mini_racer.dll

Actual behavior

as above

System configuration

win11 PyMiniRacer version: 0.12.4 Python version: python 3.12

Current solution

when i use --collect-binaries py_mini_racer with pyinstaller, there is a mini_racer.dll in .\dist\test\_internal\py_mini_racer, but py_mini_racer will find in .\dist\test\_internal and therefore miss it. My current solution is using

--add-data D:\myfiles\softwares\miniconda\envs\tmp\Lib\site-packages\py_mini_racer\mini_racer.dll:.
--add-data D:\myfiles\softwares\miniconda\envs\tmp\Lib\site-packages\py_mini_racer\icudtl.dat:.
--add-data D:\myfiles\softwares\miniconda\envs\tmp\Lib\site-packages\py_mini_racer\snapshot_blob.bin:.

with pyinstaller and it will place these files under .\dist\test\_internal and everything will be ok.

deli-c1ous avatar Dec 17 '24 06:12 deli-c1ous

Hi!

I don't have a Windows environment to reproduce this issue in but if you want to send a PR I can take a look!

I wonder what the cause is... There is legacy logic in src/py_mini_racer/_dll.py in _open_dll for PyInstaller which I've never been able to test (see the logic related to _MEIPASS which is a PyInstaller thing). I wonder, if you simply remove all logic related to _MEIPASS, does it start working?

bpcreech avatar Feb 03 '25 03:02 bpcreech

Thanks for reply, I test it locally on my windows 11 and here is the result. test.py

from py_mini_racer import MiniRacer
mr = MiniRacer()

run pyinstaller test.py in terminal

Traceback (most recent call last):
  File "test.py", line 3, in <module>
  File "py_mini_racer\_mini_racer.py", line 56, in __init__
  File "py_mini_racer\_dll.py", line 278, in init_mini_racer
  File "contextlib.py", line 137, in __enter__
  File "py_mini_racer\_dll.py", line 242, in _open_dll
  File "py_mini_racer\_dll.py", line 217, in _check_path
py_mini_racer._dll.LibNotFoundError: Native library or dependency not available at D:\myfiles\projects\python\dist\test\_internal\py_mini_racer\mini_racer.dll
[PYI-13040:ERROR] Failed to execute script 'test' due to unhandled exception!

so pyinstaller won't bundle mini_racer.dll and other dependent data files such as icudtl.dat and snapshot_blob.bin.

so we should run pyinstaller test.py --collect-data py_mini_racer then it will place icudtl.dat, mini_racer.dll, py.typed(0 byte, probably no use) and snapshot_blob.bin in _internal/py_mini_racer directory. while the code snippet in src/py_mini_racer/_dll.py

@contextmanager
def _open_dll(flags: Iterable[str]) -> Iterator[ctypes.CDLL]:
    dll_filename = _get_lib_filename("mini_racer")

    with ExitStack() as exit_stack:
        # Find the dll and its external dependency files:
        meipass = getattr(sys, "_MEIPASS", None)
        if meipass is not None:
            # We are running under PyInstaller
            dll_path = pathjoin(meipass, dll_filename)
            icu_data_path = pathjoin(meipass, _ICU_DATA_FILENAME)
            snapshot_path = pathjoin(meipass, _SNAPSHOT_FILENAME)
        else:
            dll_path = _open_resource_file(dll_filename, exit_stack)
            icu_data_path = _open_resource_file(_ICU_DATA_FILENAME, exit_stack)
            snapshot_path = _open_resource_file(_SNAPSHOT_FILENAME, exit_stack)

will have meipass variable's value be _internal, so it misses py_mini_racer, which is the module name. So it's simple to resolve, just like this.

@contextmanager
def _open_dll(flags: Iterable[str]) -> Iterator[ctypes.CDLL]:
    dll_filename = _get_lib_filename("mini_racer")

    with ExitStack() as exit_stack:
        # Find the dll and its external dependency files:
        meipass = getattr(sys, "_MEIPASS", None)
        module_name = 'py_mini_racer'
        if meipass is not None:
            # We are running under PyInstaller
            dll_path = pathjoin(meipass, module_name, dll_filename)
            icu_data_path = pathjoin(meipass, module_name, _ICU_DATA_FILENAME)
            snapshot_path = pathjoin(meipass, module_name, _SNAPSHOT_FILENAME)
        else:
            dll_path = _open_resource_file(dll_filename, exit_stack)
            icu_data_path = _open_resource_file(_ICU_DATA_FILENAME, exit_stack)
            snapshot_path = _open_resource_file(_SNAPSHOT_FILENAME, exit_stack)

then it will find the dependent files in the right path. Just a small fix, hope to solve soon and notify me at once so I can test it for you.

deli-c1ous avatar Feb 12 '25 15:02 deli-c1ous

And also you can explain about how to work with pyinstaller in the documentation, thank you for your work!

deli-c1ous avatar Feb 12 '25 15:02 deli-c1ous