web-dev-qa-db-ja.com

Cythonでコンパイルされたpythonライブラリのバイナリコンパイルされた.soファイルのみをパッケージ化します

mypackという名前のパッケージがあり、その中にモジュールmymod.py__init__.pyがあります。議論されていない何らかの理由で、このモジュールをコンパイルしてパッケージ化する必要があります(.pyまたは.pycファイルも許可されていません)。つまり、__init__.pyは、分散圧縮ファイルで許可されている唯一のソースファイルです。

フォルダ構造は次のとおりです。

. 
│  
├── mypack
│   ├── __init__.py
│   └── mymod.py
├── setup.py

Cythonは、Pythonで直接インポートできる.soライブラリ内の各.pyファイルを変換することでこれを実行できることがわかりました。

問題は、パッケージ化とインストールを簡単にするために、setup.pyファイルをどのようにする必要があるかということです。

ターゲットシステムにはvirtualenvがあり、簡単にインストールおよびアンインストールできる方法でパッケージをインストールする必要があります(easy_install、pipなどはすべて歓迎されます)。

私は手の届くところにあるすべてを試しました。 setuptoolsdistutilsのドキュメント、すべてのstackoverflow関連の質問を読み、setup.cfgとMANIFESTの多くの組み合わせを使用して、あらゆる種類のコマンド(sdist、bdist、bdist_Eggなど)を試しました。ファイルエントリ内。

私が得た最も近いものは、.pycファイルも削除するためにbdist_Eggコマンドをサブクラス化する以下のセットアップファイルでしたが、それはインストールを壊しています。

適切なインストールに含まれるすべての補助ファイルがカバーされている場合は、venvにファイルを「手動で」インストールするソリューションも適しています(venvでpip freezeを実行し、mymod==0.0.1を参照する必要があります)。 )。

それを実行します:

python setup.py bdist_Egg --exclude-source-files

そして(しようと)それをインストールします

easy_install mymod-0.0.1-py2.7-linux-x86_64.Egg

お気づきかもしれませんが、ターゲットはLinux64ビットでpython 2.7です。

from Cython.Distutils import build_ext
from setuptools import setup, find_packages
from setuptools.extension import Extension
from setuptools.command import bdist_Egg
from setuptools.command.bdist_Egg import  walk_Egg, log 
import os

class my_bdist_Egg(bdist_Egg.bdist_Egg):

    def zap_pyfiles(self):
        log.info("Removing .py files from temporary directory")
        for base, dirs, files in walk_Egg(self.bdist_dir):
            for name in files:
                if not name.endswith('__init__.py'):
                    if name.endswith('.py') or name.endswith('.pyc'):
                        # original 'if' only has name.endswith('.py')
                        path = os.path.join(base, name)
                        log.info("Deleting %s",path)
                        os.unlink(path)

ext_modules=[
    Extension("mypack.mymod", ["mypack/mymod.py"]),
]

setup(
  name = 'mypack',
  cmdclass = {'build_ext': build_ext, 
              'bdist_Egg': my_bdist_Egg },
  ext_modules = ext_modules,
  version='0.0.1',
  description='This is mypack compiled lib',
  author='Myself',
  packages=['mypack'],
)

更新。 @Teyrasの回答に続いて、回答で要求されたとおりにホイールを作成することができました。 setup.pyファイルの内容は次のとおりです。

import os
import shutil
from setuptools.extension import Extension
from setuptools import setup
from Cython.Build import cythonize
from Cython.Distutils import build_ext

class MyBuildExt(build_ext):
    def run(self):
        build_ext.run(self)
        build_dir = os.path.realpath(self.build_lib)
        root_dir = os.path.dirname(os.path.realpath(__file__))
        target_dir = build_dir if not self.inplace else root_dir
        self.copy_file('mypack/__init__.py', root_dir, target_dir)

    def copy_file(self, path, source_dir, destination_dir):
        if os.path.exists(os.path.join(source_dir, path)):
            shutil.copyfile(os.path.join(source_dir, path), 
                            os.path.join(destination_dir, path))


setup(
  name = 'mypack',
  cmdclass = {'build_ext': MyBuildExt},
  ext_modules = cythonize([Extension("mypack.*", ["mypack/*.py"])]),
  version='0.0.1',
  description='This is mypack compiled lib',
  author='Myself',
  packages=[],
  include_package_data=True )

重要な点は、packages=[],を設定することでした。ホイール内にbuild_extファイルを取得するには、__init__.pyクラスrunメソッドの上書きが必要でした。

11
eguaio

残念ながら、_packages=[]_を設定して受け入れられた回答は間違っており、多くのものを壊す可能性があります。 この質問 で見られます。使用しないでください。 distからすべてのパッケージを除外するのではなく、cythonizedされて共有オブジェクトにコンパイルされるpythonファイルのみを除外する必要があります。

以下は実際の例です。 私のレシピ 質問から 単一のソースファイルをpython bdist_Eggまたはbdist_wheel から除外します。サンプルプロジェクトには、2つのモジュールを持つパッケージspamが含まれています。 _spam.eggs_と_spam.bacon_、および1つのモジュールを含むサブパッケージ_spam.fizz_ _spam.fizz.buzz_:

_root
├── setup.py
└── spam
    ├── __init__.py
    ├── bacon.py
    ├── eggs.py
    └── fizz
        ├── __init__.py
        └── buzz.py
_

モジュールルックアップは_build_py_コマンドで実行されるため、カスタム動作でサブクラス化する必要があります。

単純なケース:すべてのソースコードをコンパイルし、例外を作成しない

すべての_.py_ファイル(___init__.py_ sを含む)をコンパイルしようとしている場合は、_build_py.build_packages_メソッドをオーバーライドするだけで十分です。 _build_packages_は何もしないため、_.py_ファイルはまったく収集されず、distには暗号化された拡張子のみが含まれます。

_import fnmatch
from setuptools import find_packages, setup, Extension
from setuptools.command.build_py import build_py as build_py_orig
from Cython.Build import cythonize


extensions = [
    # example of extensions with regex
    Extension('spam.*', ['spam/*.py']),
    # example of extension with single source file
    Extension('spam.fizz.buzz', ['spam/fizz/buzz.py']),
]


class build_py(build_py_orig):
    def build_packages(self):
        pass


setup(
    name='...',
    version='...',
    packages=find_packages(),
    ext_modules=cythonize(extensions),
    cmdclass={'build_py': build_py},
)
_

複雑なケース:cythonized拡張機能とソースモジュールを組み合わせる

選択したモジュールのみをコンパイルし、残りはそのままにしておきたい場合は、もう少し複雑なロジックが必要になります。この場合、モジュールルックアップをオーバーライドする必要があります。以下の例では、_spam.bacon_、_spam.eggs_、および_spam.fizz.buzz_を共有オブジェクトにコンパイルしますが、___init__.py_ファイルはそのままにして、ソースモジュールとして含まれるようにします。

_import fnmatch
from setuptools import find_packages, setup, Extension
from setuptools.command.build_py import build_py as build_py_orig
from Cython.Build import cythonize


extensions = [
    Extension('spam.*', ['spam/*.py']),
    Extension('spam.fizz.buzz', ['spam/fizz/buzz.py']),
]
cython_excludes = ['**/__init__.py']


def not_cythonized(tup):
    (package, module, filepath) = tup
    return any(
        fnmatch.fnmatchcase(filepath, pat=pattern) for pattern in cython_excludes
    ) or not any(
        fnmatch.fnmatchcase(filepath, pat=pattern)
        for ext in extensions
        for pattern in ext.sources
    )


class build_py(build_py_orig):
    def find_modules(self):
        modules = super().find_modules()
        return list(filter(not_cythonized, modules))

    def find_package_modules(self, package, package_dir):
        modules = super().find_package_modules(package, package_dir)
        return list(filter(not_cythonized, modules))


setup(
    name='...',
    version='...',
    packages=find_packages(),
    ext_modules=cythonize(extensions, exclude=cython_excludes),
    cmdclass={'build_py': build_py},
)
_
5
hoefling

ホイールとしてパッケージ化することは間違いなくあなたが望むものですが、元の質問はパッケージから。pyソースファイルを除外することについてでした。これは Cythonを使用してPython codebase を@Teyrasで保護しますが、彼のソリューションはハックを使用します:を削除します) setup()の呼び出しからのpackages引数。これにより、build_pyステップが実行されなくなります。 、確かに、。pyファイルを除外しますが、パッケージに含めたいデータファイルも除外します(たとえば、私のパッケージには、というデータファイルがあります。パッケージのバージョン番号を含むVERSION。)より良い解決策は、build_pyセットアップコマンドを、データファイルのみをコピーするカスタムコマンドに置き換えることです。

上記のように、__init__.pyファイルも必要です。したがって、カスタムbuild_pyコマンドで__init_.pyファイルを作成する必要があります。コンパイルされた__init__.soはパッケージのインポート時に実行されるため、必要なのは空の__init__.pyファイルだけで、ディレクトリがモジュールであることをPythonインポートしても大丈夫です。

カスタムbuild_pyクラスは次のようになります。

import os
from setuptools.command.build_py import build_py

class CustomBuildPyCommand(build_py):
    def run(self):
        # package data files but not .py files
        build_py.build_package_data(self)
        # create empty __init__.py in target dirs
        for pdir in self.packages:
            open(os.path.join(self.build_lib, pdir, '__init__.py'), 'a').close()

そして、元のbuild_pyコマンドをオーバーライドするようにsetupを構成します。

setup(
   ...
   cmdclass={'build_py': CustomBuildPyCommand},
)
7
nmgeek

(fish2000で提案されているように)ホイール形式を使用することをお勧めします。次に、setup.pyで、packages引数を[]に設定します。 Cython拡張機能は引き続きビルドされ、結果の.soファイルは結果のwheelパッケージに含まれます。

__init__.pyがホイールに含まれていない場合は、Cythonから出荷されたbuild_extクラスのrunメソッドをオーバーライドして、ファイルをソースツリーからビルドフォルダー(パス)にコピーできます。 self.build_libにあります)。

3
Teyras

これはまさに一種の問題でした PythonホイールフォーマットPEP 427で説明 –対処するために開発されました。

ホイールはPython卵(さまざまな理由で問題があった/問題があった)の代わりになります– pipでサポートされています 、アーキテクチャを含めることができます-特定のプライベートバイナリ(ここに そのような配置の一例 )があり、これらの種類のものに利害関係を持つPythonコミュニティによって一般的に受け入れられています。

これが1つですsetup.pyaforelinkedPython on Wheelsの記事の抜粋で、バイナリ分布を設定する方法を示しています。

import os
from setuptools import setup
from setuptools.dist import Distribution

class BinaryDistribution(Distribution):
    def is_pure(self):
        return False

setup(
    ...,
    include_package_data=True,
    distclass=BinaryDistribution,
)

…使用している古い(しかし、おそらくまだ正規にサポートされている)setuptoolsクラスのleuで。概説したように、配布目的でWheelsを作成するのは非常に簡単です。経験から思い出すと、wheelモジュールのビルドプロセスはvirtualenvをある程度認識しているか、非常に簡単に使用できます。他の中で。

いずれにせよ、setuptools EggベースのAPIをホイールベースのツールと交換することで、深刻な苦痛を軽減できると思います。

2
fish2000