18 April 2011

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

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. distDit = "my_app_builded_folder"
  18. ##############
  19. class Target(object):
  20. """ A simple class that holds information on our executable file. """
  21. def __init__(self, **kw):
  22. """ Default class constructor. Update as you need. """
  23. self.__dict__.update(kw)
  24. # ETS and VTK
  25. tvtkPath = os.path.join( os.path.join(distDir, "enthought"), "tvtk")
  26. copy_tree(enthought.tvtk.__path__[ 0], tvtkPath )
  27. mayaviPath = os.path.join( os.path.join(distDir, "enthought"), "mayavi")
  28. copy_tree(enthought.mayavi.__path__[ 0], mayaviPath )
  29. vtkPath = os.path.join( distDir, "vtk")
  30. copy_tree(vtk.__path__[ 0], vtkPath )
  31. # M2Crypto
  32. m2CryptoPath = os.path.join( distDir, "M2Crypto")
  33. copy_tree(M2Crypto.__path__[ 0], m2CryptoPath )
  34. # h5Py
  35. h5pyPath = os.path.join( distDir, "h5py")
  36. copy_tree(h5py.__path__[ 0], h5pyPath )
  37. includes = ['enthought', 'vtk', 'M2Crypto', 'h5py']
  38. excludes = ['_gtkagg', '_tkagg', 'bsddb', 'curses', 'email', 'pywin.debugger',
  39. 'pywin.debugger.dbgcon', 'pywin.dialogs', 'tcl',
  40. 'Tkconstants', 'Tkinter']
  41. packages = ['enthought', 'vtk']
  42. dll_excludes = ['libgdk-win32-2.0-0.dll', 'libgobject-2.0-0.dll', 'tcl84.dll',
  43. 'tk84.dll']
  44. data_files = []
  45. icon_resources = []
  46. bitmap_resources = []
  47. other_resources = []
  48. MyApp_Target = Target(
  49. # what to build
  50. script = "run.py",
  51. icon_resources = icon_resources,
  52. bitmap_resources = bitmap_resources,
  53. other_resources = other_resources,
  54. dest_base = "my_app",
  55. version = "0.1.0",
  56. company_name = "My Company",
  57. copyright = "My Company",
  58. name = "My App",
  59. )
  60. setup(
  61. data_files = data_files,
  62. options = {"py2exe": {"compressed": 0,
  63. "optimize": 1,
  64. "includes": includes,
  65. "excludes": excludes,
  66. "packages": packages,
  67. "dll_excludes": dll_excludes,
  68. "bundle_files": 3,
  69. "dist_dir": distDir,
  70. "xref": False,
  71. "skip_archive": True,
  72. "ascii": False,
  73. "custom_boot_script": '',
  74. }
  75. },
  76. zipfile = r'library.zip',
  77. console = [],
  78. windows = [MyApp_Target],
  79. service = [],
  80. com_server = [],
  81. ctypes_com_server = []
  82. )


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

P.S.
Слышал, что некоторые смогли добиться того же самого, когда все модули доступны в виде egg-файлов, путем некоторой кастомизации py2exe.
Tags:pythonpy2exemayavichacom2cryptovtkenthought
Hubs: Python
+28
9.4k 110
Comments 8
Ads
Top of the last 24 hours