Для краткости, введем обозначение «нестандартные» — под этим термином будем далее подразумевать такие модули, которые содержат в себе файлы, отличные от *.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:
Буду рад услышать ваши варианты решения подобной проблемы, возможно, есть более элегантный метод.
P.S.
Слышал, что некоторые смогли добиться того же самого, когда все модули доступны в виде egg-файлов, путем некоторой кастомизации py2exe.
Первая проблема состоит в том, что практически все сборщики бинарных «дистрибутивов» 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
- from distutils.core import setup
- from distutils.dir_util import copy_tree
- from py2exe.build_exe import py2exe
- import glob
- import os
- import zlib
- import shutil
- import time
- import shutil
- import enthought.tvtk
- import enthought.mayavi
- import vtk
- import sys
- import M2Crypto
- import h5py
-
- ##############
-
- distDit = "my_app_builded_folder"
-
- ##############
-
- class Target(object):
- """ A simple class that holds information on our executable file. """
- def __init__(self, **kw):
- """ Default class constructor. Update as you need. """
- self.__dict__.update(kw)
-
- # ETS and VTK
-
- tvtkPath = os.path.join( os.path.join(distDir, "enthought"), "tvtk")
- copy_tree(enthought.tvtk.__path__[ 0], tvtkPath )
-
- mayaviPath = os.path.join( os.path.join(distDir, "enthought"), "mayavi")
- copy_tree(enthought.mayavi.__path__[ 0], mayaviPath )
-
- vtkPath = os.path.join( distDir, "vtk")
- copy_tree(vtk.__path__[ 0], vtkPath )
-
- # M2Crypto
-
- m2CryptoPath = os.path.join( distDir, "M2Crypto")
- copy_tree(M2Crypto.__path__[ 0], m2CryptoPath )
-
- # h5Py
-
- h5pyPath = os.path.join( distDir, "h5py")
- copy_tree(h5py.__path__[ 0], h5pyPath )
-
-
- includes = ['enthought', 'vtk', 'M2Crypto', 'h5py']
- excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
- 'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
- 'Tkconstants', 'Tkinter']
-
- packages = ['enthought', 'vtk']
-
- dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
- 'tk84.dll']
-
- data_files = []
- icon_resources = []
- bitmap_resources = []
- other_resources = []
-
-
- MyApp_Target = Target(
- # what to build
- script = "run.py",
- icon_resources = icon_resources,
- bitmap_resources = bitmap_resources,
- other_resources = other_resources,
- dest_base = "my_app",
- version = "0.1.0",
- company_name = "My Company",
- copyright = "My Company",
- name = "My App",
-
- )
-
-
- setup(
- data_files = data_files,
- options = {"py2exe": {"compressed": 0,
- "optimize": 1,
- "includes": includes,
- "excludes": excludes,
- "packages": packages,
- "dll_excludes": dll_excludes,
- "bundle_files": 3,
- "dist_dir": distDir,
- "xref": False,
- "skip_archive": True,
- "ascii": False,
- "custom_boot_script": '',
- }
- },
-
- zipfile = r'library.zip',
- console = [],
- windows = [MyApp_Target],
- service = [],
- com_server = [],
- ctypes_com_server = []
- )
-
Буду рад услышать ваши варианты решения подобной проблемы, возможно, есть более элегантный метод.
P.S.
Слышал, что некоторые смогли добиться того же самого, когда все модули доступны в виде egg-файлов, путем некоторой кастомизации py2exe.