Создание py2exe сборок с модулями Python, содержащими сторонние файлы

    Для краткости, введем обозначение «нестандартные» — под этим термином будем далее подразумевать такие модули, которые содержат в себе файлы, отличные от *.py. К примеру это могут быть библиотеки (*.pyd), картинки, иконки, и т.д.

    Первая проблема состоит в том, что практически все сборщики бинарных «дистрибутивов» python-приложений, такие как py2exe, bbfreeze, cx_Freeze, и другие, забирают из таких модулей только *.py файлы. Вторая проблема возникает со сложными namespace-модулями, такими как ETS — часто сборщик не может правильно разобрать все их внутренние зависимости.

    Конкретно в моем случае камнями преткновения оказались все модули ETS (mayavi, chaco, и т.д.), m2crypto, vtk, h5py, matplotlib и несколько других (вообще, как выяснилось, таких модулей очень много).

    Я попробовал протестировать разные сборщики и поначалу остановился на cx_Freeze, т.к. он единственный умеет более-менее правильно импортировать ETS «из коробки». Однако, его оказалось недостаточно: он не смог справиться с другими нестандартными модулями, а также по ряду других причин (к примеру, мне так и не удалось скрыть окно консоли, поставить кастомную иконку, и пр.). Конечно, там есть механизм «рецептов» (совсем не документированный), который даже работает, к примеру, для matplotlib, но хотелось более универсального и простого решения, чем писать подобный рецепт под каждый модуль.

    В итоге я остановился на py2exe, т.к. с ним удалось решить все вышеназванные проблемы. Поскольку на это ушло довольно-таки значительное время, то хочу с вами поделиться — может кому тоже понадобится.

    Пока я еще разбирался с cx_Freeze возникла очевидная идея — скопировать содержимое модуля в папку с дистрибутивом. Конкретно для cx_Freeze я пробовал это сделать копированием как в саму папку, так и добавлением нужных модулей в library.zip, в который он собирает все зависимые модули, однако, все это прошло безрезультатно.

    Когда начал разбираться с py2exe, оказалось, что при определенной комбинации параметров, при которых сборка создается в виде папки (compressed: 0, bundle_files: 3), такой трюк начинает работать. Похоже, py2exe в этом случае добавляет текущую папку приложения в PYTHONPATH встроенного интерпретатора, после чего все, скопированное в нее, успешно импортируется.

    Копирование модуля проще всего сделать через функцию copy_tree из пакета distutils.dir_util, а получать путь к модулю — через переменную module.__path__.

    Ниже представлен работающий пример для ETS, vtk, m5crypto и h5py:

    Copy Source | Copy HTML
    1. from distutils.core import setup
    2. from distutils.dir_util import copy_tree
    3. from py2exe.build_exe import py2exe
    4. import glob
    5. import os
    6. import zlib
    7. import shutil
    8. import time
    9. import shutil
    10. import enthought.tvtk
    11. import enthought.mayavi
    12. import vtk
    13. import sys
    14. import M2Crypto
    15. import h5py
    16.  
    17. ##############
    18.  
    19. distDit = "my_app_builded_folder"
    20.  
    21. ##############
    22.  
    23. class Target(object):
    24.     """ A simple class that holds information on our executable file. """
    25.     def __init__(self, **kw):
    26.         """ Default class constructor. Update as you need. """
    27.         self.__dict__.update(kw)
    28.  
    29. # ETS and VTK
    30.  
    31. tvtkPath = os.path.join( os.path.join(distDir, "enthought"), "tvtk")
    32. copy_tree(enthought.tvtk.__path__[ 0], tvtkPath )
    33.  
    34. mayaviPath = os.path.join( os.path.join(distDir, "enthought"), "mayavi")
    35. copy_tree(enthought.mayavi.__path__[ 0], mayaviPath )
    36.  
    37. vtkPath = os.path.join( distDir, "vtk")
    38. copy_tree(vtk.__path__[ 0], vtkPath )
    39.  
    40. # M2Crypto
    41.  
    42. m2CryptoPath = os.path.join( distDir, "M2Crypto")
    43. copy_tree(M2Crypto.__path__[ 0], m2CryptoPath )
    44.  
    45. # h5Py
    46.  
    47. h5pyPath = os.path.join( distDir, "h5py")
    48. copy_tree(h5py.__path__[ 0], h5pyPath )
    49.  
    50.  
    51. includes = ['enthought', 'vtk', 'M2Crypto', 'h5py']
    52. excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
    53.             'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
    54.             'Tkconstants', 'Tkinter']
    55.  
    56. packages = ['enthought', 'vtk']
    57.  
    58. dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
    59.                 'tk84.dll']
    60.  
    61. data_files = []
    62. icon_resources = []
    63. bitmap_resources = []
    64. other_resources = []
    65.  
    66.  
    67. MyApp_Target = Target(
    68.     # what to build
    69.     script = "run.py",
    70.     icon_resources = icon_resources,
    71.     bitmap_resources = bitmap_resources,
    72.     other_resources = other_resources,
    73.     dest_base = "my_app",
    74.     version = "0.1.0",
    75.     company_name = "My Company",
    76.     copyright = "My Company",
    77.     name = "My App",
    78.  
    79.     )
    80.  
    81.  
    82. setup(
    83.     data_files = data_files,
    84.     options = {"py2exe": {"compressed":  0,
    85.                           "optimize": 1,
    86.                           "includes": includes,
    87.                           "excludes": excludes,
    88.                           "packages": packages,
    89.                           "dll_excludes": dll_excludes,
    90.                           "bundle_files": 3,
    91.                           "dist_dir": distDir,
    92.                           "xref": False,
    93.                           "skip_archive": True,
    94.                           "ascii": False,
    95.                           "custom_boot_script": '',
    96.                          }
    97.               },
    98.  
    99.     zipfile = r'library.zip',
    100.     console = [],
    101.     windows = [MyApp_Target],
    102.     service = [],
    103.     com_server = [],
    104.     ctypes_com_server = []
    105.     )
    106.  


    Буду рад услышать ваши варианты решения подобной проблемы, возможно, есть более элегантный метод.

    P.S.
    Слышал, что некоторые смогли добиться того же самого, когда все модули доступны в виде egg-файлов, путем некоторой кастомизации py2exe.

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 8

      0
      Вопрос уровня «пользуюсь питоном на „прикладном“ уровне». Не в курсе, когда py2exe, или другая тулза, будут поддерживать 3.2 «из коробки». cx_Freeze для 3.1 не смог проглотить argparse, например.
      Я понимаю, что при должном упорстве и навыках, можно это ограничение обойти. Но меня интересует питон как инструмент, быстрый и удобный — написал скрипт, сваял exe-шник, пользуешься.
        0
        блин, задал вопрос, без "?", в общем, поймете по смыслу )
          0
          Трудный вопрос, у py2exe похоже даже в планах пока нет поддержки Python 3.X. Единственный возможный вариант cx_Freeze — если он не справился, значит пока такого инструмента нет.
        0
        Невероятно полезный потс, в избранное однозначно.

        /ушёл пробовать способ на своих сорцах/
          +5
          Когда то нужно было сделать так, как в этой статье но только с Django (в клиента не было Python, а сайт нужно показать рабочий, да-да с админкой). py2exe справился на «ура». Ох сколько я там со статикой намучался! Если кому интересно могу рассказать.
            +1
            Еще бы!
            0
            Еще есть кстати pyinstaller, собирал им пару лет назад бинарники с PyQt, matplotlib, sympy и pyplot, все работало нормально…
              0
              Его я тоже пробовал, но с ETS-пакетами подружить не удалось, поэтому пришлось искать дальше.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое