py2app подбирает .git subdir пакета во время сборки

Мы широко используем py2app на нашем объекте для создания автономных пакетов .app для легкого внутреннего развертывания без проблем с зависимостями. Что-то, что я заметил недавно, и понятия не имею, как это началось, заключается в том, что при создании .app py2app начал включать каталог .git нашей основной библиотеки.

commonLib, например, является нашим корневым пакетом библиотеки python, который является репозиторией git. Под этим пакетом находятся различные подпакеты, такие как база данных, утилита и т. Д.

commonLib/ |- .git/ # because commonLib is a git repo |- __init__.py |- database/ |- __init__.py |- utility/ |- __init__.py # ... etc 

В данном проекте, скажем, Foo, мы будем делать импорт, например, from commonLib import xyz чтобы использовать наши общие пакеты. Создание через py2app выглядит примерно так: python setup.py py2app

Поэтому недавняя проблема, которую я вижу, заключается в том, что при создании приложения для проекта Foo я увижу, что он включает все в commonLib / .git / в приложение, что является дополнительным вздутием. py2app имеет исключаемый вариант, но, похоже, это только для модулей python. Я не могу понять, что потребуется, чтобы исключить субтитры .git или, по сути, то, что заставляет его включать в первую очередь.

Кто-нибудь испытал это при использовании импорта пакета python, который является git-репо? Ничто не изменилось в наших файлах setup.py для каждого проекта, а commonLib всегда был git-репо. Поэтому единственное, что я могу представить как переменную, – это версия py2app и ее deps, которые, очевидно, были обновлены с течением времени.

редактировать

Я использую последний py2app 0.6.4 на данный момент. Кроме того, моя setup.py была сначала сгенерирована из py2applet некоторое время назад, но была настроена вручную и скопирована как шаблон для каждого нового проекта. Я использую PyQt4 / sip для каждого из этих проектов, так что это также заставляет меня задаться вопросом, есть ли проблема с одним из рецептов?

Обновить

С первого ответа я попытался исправить это, используя различные комбинации параметров exclude_package_data . Кажется, что ничто не заставляет каталог .git исключаться. Ниже приведен пример того, как выглядят мои файлы setup.py:

 from setuptools import setup from myApp import VERSION appname = 'MyApp' APP = ['myApp.py'] DATA_FILES = [] OPTIONS = { 'includes': 'atexit, sip, PyQt4.QtCore, PyQt4.QtGui', 'strip': True, 'iconfile':'ui/myApp.icns', 'resources':['src/myApp.png'], 'plist':{ 'CFBundleIconFile':'ui/myApp.icns', 'CFBundleIdentifier':'com.company.myApp', 'CFBundleGetInfoString': appname, 'CFBundleVersion' : VERSION, 'CFBundleShortVersionString' : VERSION } } setup( app=APP, data_files=DATA_FILES, options={'py2app': OPTIONS}, setup_requires=['py2app'], ) 

Я пробовал такие вещи, как:

 setup( ... exclude_package_data = { 'commonLib': ['.git'] }, #exclude_package_data = { '': ['.git'] }, #exclude_package_data = { 'commonLib/.git/': ['*'] }, #exclude_package_data = { '.git': ['*'] }, ... ) 

Обновление # 2

Я опубликовал свой собственный ответ, который делает monkeypatch на distutils. Его уродливые и не предпочтительные, но пока кто-то не может предложить мне лучшее решение, я думаю, это то, что у меня есть.

4 Solutions collect form web for “py2app подбирает .git subdir пакета во время сборки”

Я добавляю ответ на свой вопрос, чтобы документировать единственное, что я нашел, чтобы работать до сих пор. Мой подход состоял в том, чтобы обезопасить distutils, чтобы игнорировать определенные шаблоны при создании каталога или копировании файла. Это действительно не то, что я хотел сделать, но, как я уже сказал, это единственное, что работает до сих пор.

 ## setup.py ## import re # file_util has to come first because dir_util uses it from distutils import file_util, dir_util def wrapper(fn): def wrapped(src, *args, **kwargs): if not re.search(r'/\.git/?', src): fn(src, *args, **kwargs) return wrapped file_util.copy_file = wrapper(file_util.copy_file) dir_util.mkpath = wrapper(dir_util.mkpath) # now import setuptools so it uses the monkeypatched methods from setuptools import setup 

Надеюсь, кто-то прокомментирует это и скажет мне более высокий уровень, чтобы избежать этого. Но на данный момент я, вероятно, включу это в метод утилиты, такой как exclude_data_patterns(re_pattern) который будет использоваться повторно в моих проектах.

Я вижу два варианта исключения каталога .git.

  1. Создайте приложение из «чистой» проверки кода. При развертывании новой версии мы всегда строим свежий svn export на основе тега, чтобы гарантировать, что мы не поднимем ложные изменения / файлы. Вы могли бы попробовать эквивалент здесь – хотя эквивалент git кажется несколько более привлекательным .

  2. Измените файл setup.py чтобы массировать файлы, включенные в приложение. Это можно сделать с помощью функции exclude_package_data как описано в документации , или создать список data_files и передать его в setup .

Что касается того, почему это внезапно началось, знание версии py2app, которую вы используете, может помочь, так как будет знать содержимое вашего setup.py и, возможно, как это было сделано (вручную или с помощью py2applet).

У меня есть аналогичный опыт с Pyinstaller, поэтому я не уверен, что он применяется напрямую.

Pyinstaller создает «манифест» всех файлов, которые должны быть включены в дистрибутив, перед запуском процесса экспорта. Вы можете «массировать» этот манифест, как и второе предложение Марка, исключить любые файлы, которые вы хотите. Включая что-либо внутри .git или .git.

В конце концов, я застрял с проверкой моего кода перед созданием двоичного файла, поскольку было больше, чем просто. Git bloat (например, UML-документы и исходные файлы ресурсов для Qt). Касса гарантировала чистый результат, и я не испытывал проблем с автоматизацией этого процесса вместе с процессом создания установщика для двоичного файла.

На это есть хороший ответ, но у меня есть более сложный ответ, чтобы решить проблему, упомянутую здесь, с использованием подхода «белого списка». Чтобы патч обезьяны также работал для пакетов за пределами site-packages.zip мне приходилось использовать патч monkey также copy_tree (поскольку он импортирует copy_file внутри его функции), это помогает сделать автономное приложение.

Кроме того, я создаю рецепт белого списка для маркировки определенных пакетов zip-unsafe. Подход упрощает добавление фильтров, отличных от белого списка.

 import pkgutil from os.path import join, dirname, realpath from distutils import log # file_util has to come first because dir_util uses it from distutils import file_util, dir_util # noinspection PyUnresolvedReferences from py2app import util def keep_only_filter(base_mod, sub_mods): prefix = join(realpath(dirname(base_mod.filename)), '') all_prefix = [join(prefix, sm) for sm in sub_mods] log.info("Set filter for prefix %s" % prefix) def wrapped(mod): name = getattr(mod, 'filename', None) if name is None: # ignore anything that does not have file name return True name = join(realpath(dirname(name)), '') if not name.startswith(prefix): # ignore those that are not in this prefix return True for p in all_prefix: if name.startswith(p): return True # log.info('ignoring %s' % name) return False return wrapped # define all the filters we need all_filts = { 'mypackage': (keep_only_filter, [ 'subpackage1', 'subpackage2', ]), } def keep_only_wrapper(fn, is_dir=False): filts = [(f, k[1]) for (f, k) in all_filts.iteritems() if k[0] == keep_only_filter] prefixes = {} for f, sms in filts: pkg = pkgutil.get_loader(f) assert pkg, '{f} package not found'.format(f=f) p = join(pkg.filename, '') sp = [join(p, sm, '') for sm in sms] prefixes[p] = sp def wrapped(src, *args, **kwargs): name = src if not is_dir: name = dirname(src) name = join(realpath(name), '') keep = True for prefix, sub_prefixes in prefixes.iteritems(): if name == prefix: # let the root pass continue # if it is a package we have a filter for if name.startswith(prefix): keep = False for sub_prefix in sub_prefixes: if name.startswith(sub_prefix): keep = True break if keep: return fn(src, *args, **kwargs) return [] return wrapped file_util.copy_file = keep_only_wrapper(file_util.copy_file) dir_util.mkpath = keep_only_wrapper(dir_util.mkpath, is_dir=True) util.copy_tree = keep_only_wrapper(util.copy_tree, is_dir=True) class ZipUnsafe(object): def __init__(self, _module, _filt): self.module = _module self.filt = _filt def check(self, dist, mf): m = mf.findNode(self.module) if m is None: return None # Do not put this package in site-packages.zip if self.filt: return dict( packages=[self.module], filters=[self.filt[0](m, self.filt[1])], ) return dict( packages=[self.module] ) # Any package that is zip-unsafe (uses __file__ ,... ) should be added here # noinspection PyUnresolvedReferences import py2app.recipes for module in [ 'sklearn', 'mypackage', ]: filt = all_filts.get(module) setattr(py2app.recipes, module, ZipUnsafe(module, filt)) 
  • Как добавить сценарии после установки в easy_install / setuptools / distutils?
  • Как включить статические файлы в setuptools - пакет python
  • Как передать флаги расширению distutils?
  • python setuptool Как я могу добавить зависимость для libxml2-dev и libxslt1-dev?
  • Если py2exe включает мои файлы данных (например, include_package_data)
  • не setup.py разработать использовать колесо для install_requires?
  • Нет модуля с именем pkg_resources
  • Как упаковать демона Python с помощью setuptools
  • Python - лучший язык программирования в мире.