py2app собирает подкаталог .git пакета во время сборки - PullRequest
7 голосов
/ 23 марта 2012

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

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

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 есть опция exclude, но это только для модулей python.Я не могу понять, что нужно сделать, чтобы исключить .git subdir или фактически, что заставляет его быть включенным в первую очередь.

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

Edit

Я используюпоследний 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 ]

3 голосов
/ 30 марта 2012

Я добавляю ответ на свой вопрос, чтобы задокументировать единственное, что я нашел, чтобы работать до сих пор.Мой подход состоял в том, чтобы использовать 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), для повторного использования в моих проектах.

1 голос
/ 28 сентября 2015

На это есть хороший ответ, но у меня есть более сложный ответ, чтобы решить проблему, упомянутую здесь, с помощью подхода из белого списка. Для того, чтобы патч обезьяны также работал для пакетов вне site-packages.zip Мне пришлось также патч обезьяны 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))
1 голос
/ 09 января 2014

У меня похожий опыт работы с Pyinstaller, поэтому я не уверен, что он применяется напрямую.

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

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

1 голос
/ 29 марта 2012

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

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

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

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

...