Как сделать исполняемый файл для скрипта Python, который использует weasyprint?

Я хотел сделать исполняемый файл для своего небольшого проекта, который я сделал. Но вообще не запускается.

Я пробовал PyInstaller не включает файл зависимостей, но выдает

Traceback (most recent call last):
  File "C:\Program Files\Python37\Scripts\pyinstaller-script.py", line 11, in <module>
    load_entry_point('PyInstaller==3.4', 'console_scripts', 'pyinstaller')()
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\__main__.py", line 111, in run
    run_build(pyi_config, spec_file, **vars(args))
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\__main__.py", line 63, in run_build
    PyInstaller.building.build_main.main(pyi_config, spec_file, **kwargs)
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\building\build_main.py", line 838, in main
    build(specfile, kw.get('distpath'), kw.get('workpath'), kw.get('clean_build'))
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\building\build_main.py", line 784, in build
    exec(text, spec_namespace)
  File "<string>", line 8, in <module>
IndexError: list index out of range

И если я просто делаю "pyinstaller --clean --onefile Start.py", он говорит:

  File "Start.py", line 5, in <module>
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "Modules\HTMLToPDF.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\weasyprint\__init__.py", line 20, in <module>
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\cssselect2\__init__.py", line 20, in <module>
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\cssselect2\compiler.py", line 7, in <module>
  File "<frozen importlib._bootstrap>", line 983, in _find_and_load
  File "<frozen importlib._bootstrap>", line 967, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 677, in _load_unlocked
  File "C:\Program Files\Python37\lib\site-packages\PyInstaller\loader\pyimod03_importers.py", line 627, in exec_module
    exec(bytecode, module.__dict__)
  File "site-packages\tinycss2\__init__.py", line 10, in <module>
  File "pathlib.py", line 1189, in read_text
  File "pathlib.py", line 1176, in open
  File "pathlib.py", line 1030, in _opener
FileNotFoundError: [Errno 2] No such file or directory: 'C:\\Users\\Daniel\\AppData\\Local\\Temp\\_MEI100202\\tinycss2\\VERSION'
[12796] Failed to execute script Start

Как я могу это исправить? Мне просто нужен хороший способ распространения моего кода.


person Daniel K    schedule 26.05.2019    source источник
comment
Можете ли вы предоставить свой сценарий? Ваш скрипт работает нормально при прямом запуске?   -  person Masoud Rahimi    schedule 26.05.2019
comment
Он отлично работает, если я запускаю его из IDE (IntelliJ) github.com/kasprzakdanielt/SOAPUIReportingTool/ дерево/мастер/мыло   -  person Daniel K    schedule 27.05.2019


Ответы (2)


Честно говоря, ваш проект огромен и имеет много сложных библиотек, поэтому PyInstaller не может справиться со всеми ими самостоятельно. Здесь я дам вам несколько советов, как это сделать.

Во-первых, когда PyInstaller жалуется на отсутствие файла, это означает, что вам нужно загрузить этот файл вручную. Например, в вашей ошибке он жалуется на отсутствие файла с именем VERSION, который должен существовать в C:\\Users\\Daniel\\AppData\\Local\\Temp\\_MEI100202\\tinycss2, но это не так. Вам нужно добавить его как файл данных самостоятельно.

Во-вторых, если вам нужно добавить некоторые внешние зависимости, такие как cairo, вам нужно добавить весь каталог зависимости с помощью функции Tree. Подробнее об этом можно узнать здесь.

Я построил проект и исправил некоторые из этих зависимостей, чтобы он больше не отображал ошибку об отсутствии этих файлов, я также добавил try/except ко всему проекту, чтобы иметь возможность отслеживать отсутствующие файлы, что может быть полезно. Я только что добавил недостающие файлы и некоторые hiddenimports в спецификационный файл, так что вам нужно позаботиться об этом.

Start.py:

import traceback
try:
    """
    Whole Start.py script
    """
except Exception:
    traceback.print_exc()
    while(True):
        pass

Start.spec:

# -*- mode: python -*-

block_cipher = None


a = Analysis(['Start.py'],
             pathex=['C:\\Users\\Daniel\\Desktop\\SOAPUIReportingTool-master\\soap'],
             binaries=[],
             datas=[
                 ("C:\\Program Files\\Python37\\Lib\\site-packages\\tinycss2\\VERSION", "tinycss2"),
                 ("C:\\Program Files\\Python37\\Lib\\site-packages\\cairocffi\\VERSION", "cairocffi"),
                 ("C:\\Program Files\\Python37\\Lib\\site-packages\\\weasyprint\\VERSION", ".")
                     ],
             hiddenimports=["tinycss2", "matplotlib", "weasyprint"],
             hookspath=[],
             runtime_hooks=[],
             excludes=[],
             win_no_prefer_redirects=False,
             win_private_assemblies=False,
             cipher=block_cipher,
             noarchive=False)
pyz = PYZ(a.pure, a.zipped_data,
             cipher=block_cipher)
exe = EXE(pyz,
          a.scripts,
          a.binaries,
          a.zipfiles,
          a.datas,
          [],
          name='Start',
          debug=False,
          bootloader_ignore_signals=False,
          strip=False,
          upx=False,
          runtime_tmpdir=None,
          console=True )
person Masoud Rahimi    schedule 27.05.2019
comment
Мне пришлось добавить свой файл конфигурации и некоторые дополнительные библиотеки, но теперь он работает. (C:\\Program Files\\Python37\\Lib\\site-packages\\weasyprint\\css\\html5_ua.css, css), (C:\\Program Files\\Python37\\Lib\\site- пакеты\\weasyprint\\css\\html5_ph.css, css) Shutil.copyfile('config.cfg', '{0}/config.cfg'.format(DISTPATH)) - person Daniel K; 27.05.2019

Для тех, кто в будущем попытается заставить WeasyPrint работать с PyInstaller в WINDOWS 10 СПЕЦИАЛЬНО; Я нашел следующие методы очень полезными:

  • Создайте сценарий отладки, который вы создадите с помощью pyinstaller.
import sys,traceback
try:
    import weasyprint
    print(weasyprint.__version__)
except:
    print(sys.exc_info())
    print(traceback.format_exc())
finally:
    input('Press enter to exit the program: ')

Теперь попробуйте построить с помощью:

pyinstaller main.py --name=test --log-level ERROR --clean

Вероятно, он будет жаловаться на отсутствующие модули. Именно здесь я как бы смешиваю и сопоставляю методы в течение многих часов проб и ошибок, я предоставлю оба решения о том, как включить эти отсутствующие модули, а затем мой окончательный лист спецификации и метод.

  • hooks: чтобы включить эти отсутствующие модули, создайте следующую папку «hooks»:
\main.py
\hooks

в этой папке вы поместите файлы python со следующим скриптом, который вы назовете как «hook-{NAMEOFMISSINGMODULE}.py».

from PyInstaller.utils.hooks import collect_data_files


def hook(hook_api):
    hook_api.add_datas(collect_data_files(hook_api.__name__))

поэтому:

\main.py
\hooks
  \hook-cssselect2.py
  \...

Вы включите эти файлы в свою команду PyInstaller:

pyinstaller main.py --name=test--log-level ERROR --clean --additional-hooks-dir=hooks
  • дополнительные данные: я обнаружил, что когда он жаловался на ВЕРСИИ или определенные файлы, которые не были найдены в определенных папках в weasyprint; Я бы проложил свой путь к данным, отсутствующим в моей установке Python, а затем скопировал бы к нему адрес и реализовал бы его в команде PyInstaller с точкой с запятой, за которой следует имя папки:
pyinstaller main.py --name=test--log-level ERROR --clean --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\weasyprint\css\html5_ph.css;css --additional-hooks-dir=hooks

Если вы столкнетесь с жалобой на то, что не можете найти файл VERSION в папке _MEIXXXXX; он ищет файл VERSION в вашем пакете weasyprint. Правильная строка --add-data выглядит следующим образом:

--add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\weasyprint\VERSION;.

Обратите внимание на точку с запятой, за которой следует «.». Это говорит PyInstaller включить этот файл во временную папку, созданную PyInstaller, когда вы открываете исполняемый файл, который всегда называется _MEI{somerandomnumbers}.

Используя эти методы, вы должны в конечном итоге добиться успеха и получить номер версии weasyprint. Моя последняя рабочая командная строка выглядит следующим образом:

pyinstaller main.py --name=test--log-level ERROR --clean --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\pyphen;pyphen\dictionaries --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\weasyprint\css\html5_ph.css;css --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\weasyprint\css\html5_ua.css;css --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\weasyprint\VERSION;. --add-data C:\Users\PC\AppData\Local\Programs\Python\Python38-32\Lib\site-packages\cairocffi;cairocffi  --additional-hooks-dir=hooks

со следующими крючками:

\main.py
\hooks
  \hook-cairocffi.py
  \hook-cssselect2.py
  \hook-tinycss2.py

Это также работает для однофайлового режима.

Я точно знаю, что, вероятно, делаю то же самое с хуками и add-data; как я уже сказал, я смешивал и сопоставлял методы, пока что-то в конце концов не сработало. Я совершенно уверен, что такого же эффекта можно добиться, используя только один из этих методов, но я не думаю, что стоит тратить время на исследования, когда я уже потратил на это слишком много часов.

Спасибо всем другим пользователям stackoverflow и создателю weasyprint за ответы на несколько сообщений за эти годы, которые я в конечном итоге смог собрать в рабочую версию для себя.

Если это поможет, дайте +1 или оставьте комментарий, так как это первый ответ, который появляется при поиске в Google «Pyinstaller weasyprint».

person cabboose    schedule 28.08.2020