レシピ

このページではpython-for-android(p4a)のコンパイルレシピがどのように機能するかとビルド方法について説明します。 APKを構築したいだけならばこの章を無視して 初めに に直接ジャンプしてください。

レシピはさまざまなプログラム(Pythonモジュールを含む)をコンパイルしてp4aディストリビューションにインストールするための特別なスクリプトです。 コンパイルされたコンポーネントは、適切なアーキテクチャでAndroid用にコンパイルする必要があるためにコンパイルされたコンポーネントのコンパイルには必要です。

python-for-androidには一般的なモジュールの多くのレシピが付属しています。 コンパイルされたコンポーネントを持たないPythonモジュールを使用するためのレシピは不要です。 これらはpipによって自動でインストールされます。

レシピを作成するのが初めての方は少なくともこのレシピのリファレンスドキュメントまでページ全体を読むことをお勧めします。 異なるレシピセクションにはレシピの作成方法や特定の目的のためのオーバーライドの方法についての例が多数含まれています。

独自のレシピを作成する

レシピ・クラスの正式なリファレンス・ドキュメントは、レシピクラス のセクションおよびその下にあります。

レシピテンプレート <recipe_template>`_ のセクションで、これらのアイデアをすべて組み合わせたテンプレートを確認してください。好きなコンポーネントを置き換えれます。

レシピの基本的な宣言は以下のとおりです。

class YourRecipe(Recipe):

    url = 'http://example.com/example-{version}.tar.gz'
    version = '2.0.3'
    md5sum = '4f3dc9a9d857734a488bcbefd9cd64ed'

    patches = ['some_fix.patch']  # Paths relative to the recipe dir

    depends = ['kivy', 'sdl2']  # These are just examples
    conflicts = ['pygame']

recipe = YourRecipe()

各パラメータの詳細については、レシピクラスのドキュメント を参照してください。

これらのコアオプションはすべてのレシピにとって不可欠ですが、ソースが何らかの形でロードされている場合はURLを省略できます。

recipe = YourRecipe() を含める必要があります。 この変数はレシピをインポートするときにアクセスされます

注釈

urlには {version} タグが含まれています。 versioned_url プロパティでURLにアクセスする必要があります。これはversion属性で置き換えられます。

実際のビルドプロセスは大きく分けて3つの方法で行われます:

def prebuild_arch(self, arch):
    super(YourRecipe, self).prebuild_arch(arch)
    # Do any pre-initialisation

def build_arch(self, arch):
    super(YourRecipe, self).build_arch(arch)
    # Do the main recipe build

def postbuild_arch(self, arch):
    super(YourRecipe, self).build_arch(arch)
    # Do any clearing up

これらのメソッドは常にリストされた順序で実行されます。 ビルドしてからビルドしてください。

レシピのURLを定義した場合は、手動でダウンロードする必要は ありません 。これは自動的に処理されます。

このレシピは、 self.get_build_dir(arch.arch) を使ってアクセスできる特別な隔離されたビルドディレクトリに自動的にビルドされます。 このディレクトリ内でのみ作業する必要があります。 toolchain.pyで定義されている current_directory コンテキストマネージャを使用すると便利です:

from pythonforandroid.toolchain import current_directory
def build_arch(self, arch):
    super(YourRecipe, self).build_arch(arch)
    with current_directory(self.get_build_dir(arch.arch)):
        with open('example_file.txt', 'w'):
            fileh.write('This is written to a file within the build dir')

各メソッドの引数 arch は、現在ビルドされているアーキテクチャに関連するオブジェクトです。 それをほとんど無視できますが、 arch.arch を使用する必要があるかもしれません。

注釈

それぞれのメソッドのアーチ固有のバージョンを実装することもできます。これらのバージョンは、スーパークラスによって呼び出されます(存在する場合は)。 例: def prebuild_armeabi(self, arch)

レシピを書くために必要なものの核ですが、実際にAndroidでコンパイルするためのコードを実際に書く方法の詳細については触れていません。 ビルドの一部として使用される 標準メカニズム 、Python、Cython、およびいくつかの一般的なコンパイルされたレシピの具体的なレシピクラスの詳細を含む説明については次のセクションで説明されています。 モジュールが後者のモジュールの1つである場合、最初から機能を再実装するのではなく、後のクラスを使用する必要があります。

コンパイルに役立つメソッドとツール

インストール前にモジュールにパッチを適用する

patches 宣言にパッチを追加すると簡単にレシピにパッチを適用できます。例:

patches = ['some_fix.patch',
           'another_fix.patch']

パスはレシピファイルに関連している必要があります。 パッチは自動的に1回だけ適用されます(つまり、Python-for-androidは2回目に再適用されません)。

pythonforandroid.patching のヘルパー関数を使用して、特定の条件に応じてパッチを適用することもできます。例:

from pythonforandroid.patching import will_build, is_arch

...

class YourRecipe(Recipe):

    patches = [('x86_patch.patch', is_arch('x86')),
               ('sdl2_compatibility.patch', will_build('sdl2'))]

    ...

任意の関数をタプルの2番目のエントリとして渡すことで独自の条件を含められます。 それはkwargsとして arch (例えばx86、armeabi)とレ recipe (すなわちレシピオブジェクト)を受け取ります。 パッチは、関数がTrueを返す場合にのみ適用されます。

libsのインストール

いくつかのレシピは.soファイルを生成してアンドロイドプロジェクトに手動でコピーする必要があります。 正しいlibキャッシュディレクトリにコピーするには、次のようなコードを使用します。

def build_arch(self, arch):
    do_the_build()  # e.g. running ./configure and make

    import shutil
    shutil.copyfile('a_generated_binary.so',
                    self.ctx.get_libs_dir(arch.arch))

このディレクトリにコピーされたlibsは自動的に生成されたアンドロイドプロジェクトの適切なlibsディレクトリに含まれます。

Androidアーキテクチャ用にコンパイルする

コンパイルを実行するには適切な環境変数を設定してAndroidライブラリが適切にリンクされ、コンパイルターゲットが正しいアーキテクチャであることを確認することが重要です。

get_recipe_env メソッドを使用して適切な環境変数の辞書を取得できます。 この環境は、呼び出したすべてのプロセスに設定する必要があります。 次のように sh モジュールを使用すると便利です:

def build_arch(self, arch):
    super(YourRecipe, self).build_arch(arch)
    env = self.get_recipe_env(arch)
    sh.echo('$PATH', _env=env)  # Will print the PATH entry from the
                                # env dict

またp4aツールチェーンモジュールの shprint ヘルパー機能を使用すると、プロセスとその現在のステータスに関する情報が表示されます:

from pythonforandroid.toolchain import shprint
shprint(sh.echo, '$PATH', _env=env)

また、 get_recipe_env メソッドをオーバーライドして、新しいenv変数を追加してレシピの使用もできます。 たとえば、Kivyのレシピは、SDL2用にコンパイルするときに、Kivyにバックエンドに使用することを伝えるために以下のことを行います:

def get_recipe_env(self, arch):
    env = super(KivySDL2Recipe, self).get_recipe_env(arch)
    env['USE_SDL2'] = '1'

    env['KIVY_SDL2_PATH'] = ':'.join([
        join(self.ctx.bootstrap.build_dir, 'jni', 'SDL', 'include'),
        join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_image'),
        join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_mixer'),
        join(self.ctx.bootstrap.build_dir, 'jni', 'SDL2_ttf'),
        ])
    return env

警告

このようなshモジュールを使用する場合は新しいenvは通常の環境に完全に置き換わるので、アクセスしたい環境変数を定義する必要があります。

レシピにファイルを含める

should_buildメソッド

Recipeクラスにはbooleanを返す should_build メソッドがあります。 これは build_arch を実行する前に各アーキテクチャに対して呼び出されてFalseを返すとビルドはスキップされます。 これは異なるdistに対して複数回レシピを作成するのを避けるのに便利です。

デフォルトでは、should_buildはTrueを返しますが、好きなようにオーバーライドできます。 例えば、PythonRecipeとそのサブクラスはすべて、レシピが既にPythonディストリビューションにインストールされているかどうかのチェックに置き換えられます:

def should_build(self, arch):
    name = self.site_packages_name
    if name is None:
        name = self.name
    if self.ctx.has_package(name):
        info('Python package already exists in site-packages')
        return False
    info('{} apparently isn\'t already in site-packages'.format(name))
    return True

PythonRecipeの使用

レシピが、コンパイルされたコンポーネントなしでPythonモジュールをインストールするのであれば、PythonRecipeを使うべきです。 これは build_arch をオーバーライドして通常の python setup.py install を適切な環境で自動的に呼び出すようにします

たとえば、Vispyモジュールのレシピを作成するために必要なことは以下のとおりです:

from pythonforandroid.toolchain import PythonRecipe
class VispyRecipe(PythonRecipe):
    version = 'master'
    url = 'https://github.com/vispy/vispy/archive/{version}.zip'

    depends = ['python2', 'numpy']

    site_packages_name = 'vispy'

recipe = VispyRecipe()

site_packages_name は、モジュールがPythonパッケージにインストールされるフォルダを識別する新しい属性です。 名前がレシピ名と異なる場合はを追加するだけです。 レシピのインストールをスキップできるかどうかを確認するために使用されます。これはフォルダがすでにPythonインストールに存在する場合です。

参考までにこれを実現するコードは次のとおりです:

def build_arch(self, arch):
    super(PythonRecipe, self).build_arch(arch)
    self.install_python_package()

def install_python_package(self):
    '''Automate the installation of a Python package (or a cython
    package where the cython components are pre-built).'''
    arch = self.filtered_archs[0]
    env = self.get_recipe_env(arch)

    info('Installing {} into site-packages'.format(self.name))

    with current_directory(self.get_build_dir(arch.arch)):
        hostpython = sh.Command(self.ctx.hostpython)

        shprint(hostpython, 'setup.py', 'install', '-O2', _env=env)

これは上記のドキュメンテーションの手法とツールを組み合わせて、すべてのPythonモジュール用の汎用メカニズムを作成します。

注釈

hostpythonはPythonバイナリへのパスであり、どのような種類のインストールにも使用する必要があります。 独自のレシピでPythonを実行する必要がある場合は同様の方法でPythonを実行する必要があります。

CythonRecipeの使用

レシピがCythonを使用するPythonモジュールをインストールする場合はCythonRecipeを使用する必要があります。これは通常のPythonRecipeと同じように、cythonコンポーネントをビルドし、Pythonモジュールをインストールする build_arch をオーバーライドします

たとえばKivyのレシピを作成するには以下のすべてが必要です(この場合、SDL2ではなくPygameによって異なります):

class KivyRecipe(CythonRecipe):
     version = 'stable'
     url = 'https://github.com/kivy/kivy/archive/{version}.zip'
     name = 'kivy'

     depends = ['pygame', 'pyjnius', 'android']

recipe = KivyRecipe()

参考までにこれを実現するコードは次のとおりです:

def build_arch(self, arch):
    Recipe.build_arch(self, arch)  # a hack to avoid calling
                                   # PythonRecipe.build_arch
    self.build_cython_components(arch)
    self.install_python_package()  # this is the same as in a PythonRecipe

def build_cython_components(self, arch):
    env = self.get_recipe_env(arch)
    with current_directory(self.get_build_dir(arch.arch)):
        hostpython = sh.Command(self.ctx.hostpython)

        # This first attempt *will* fail, because cython isn't
        # installed in the hostpython
        try:
            shprint(hostpython, 'setup.py', 'build_ext', _env=env)
        except sh.ErrorReturnCode_1:
            pass

        # ...so we manually run cython from the user's system
        shprint(sh.find, self.get_build_dir('armeabi'), '-iname', '*.pyx', '-exec',
                self.ctx.cython, '{}', ';', _env=env)

        # now cython has already been run so the build works
        shprint(hostpython, 'setup.py', 'build_ext', '-v', _env=env)

        # stripping debug symbols lowers the file size a lot
        build_lib = glob.glob('./build/lib*')
        shprint(sh.find, build_lib[0], '-name', '*.o', '-exec',
                env['STRIP'], '{}', ';', _env=env)

最初に.pyxファイルがsetup.pyによって生成されたことを確認するために、その次にcythonがhostpythonビルドにインストールされていないために失敗したビルドと手動のcython化が必要です

実際にはsetup.pyがpyxファイルを作成する前にcythonをインポートしようとすると失敗する可能性があります。これが起こった場合は、パッチを適用してこのインポートを削除するか、静かに失敗させてください。

これ以外にもこれらのメソッドは上記のドキュメントのテクニックに従って、ほとんどのcythonベースのモジュールの汎用レシピを作成します。

CompiledComponentsPythonRecipeの使用

これはCythonRecipeに似ていますがnumpyのようなコンパイルされた非cythonコンポーネントを含むモジュール用です。同様のメカニズムを使用して適切な環境でコンパイルします。

おそらく、(コードの重複を避けるために)CythonRecipeがそれを継承するように変更されるためでこれは文書化されていません。

NDKRecipeの使用

Pythonモジュール用ではなく、AndroidプロジェクトのJNIディレクトリ(つまり、Androidビルドシステムで使用できる Application.mkAndroid.mk がある)のためにレシピを作成している場合は、 NDKRecipeを自動的に設定します。 NDKRecipeは通常の get_build_dir メソッドをオーバーライドしてAndroidプロジェクトに配置します。

警告

現在、NDKRecipeは実際にはndk-buildを呼び出すのではなくて手動でbuild_archメソッドを作成してこの呼び出しを(モジュール用に)追加する必要があります。 これは後に修正される可能性があります。

たとえばSDL2_ttfをjniディレクトリに配置するには次のレシピが必要です。 これは後でSDL2のレシピによってビルドされます。このレシピは依存関係としてndk-buildを呼び出します:

class LibSDL2TTF(NDKRecipe):
    version = '2.0.12'
    url = 'https://www.libsdl.org/projects/SDL_ttf/release/SDL2_ttf-{version}.tar.gz'
    dir_name = 'SDL2_ttf'

recipe = LibSDL2TTF()

dir_name引数は新しいクラス属性で、jniディレクトリのフォルダ名をレシピに伝えます。 省略した場合はレシピ名が使用されます。 注意してください。特にこのフォルダが他のものの依存関係である場合はフォルダ名が重要な場合があります。

レシピテンプレート

次のテンプレートには使用するすべてのレシピセクションが含まれています。 いずれも強制的なものではありません。あなたがそれらを使用しない場合、メソッドのオーバーライドを自由に削除してください:

from pythonforandroid.toolchain import Recipe, shprint, current_directory
from os.path import exists, join
import sh
import glob


class YourRecipe(Recipe):
    # This could also inherit from PythonRecipe etc. if you want to
    # use their pre-written build processes

    version = 'some_version_string'
    url = 'http://example.com/example-{version}.tar.gz'
    # {version} will be replaced with self.version when downloading

    depends = ['python2', 'numpy']  # A list of any other recipe names
                                    # that must be built before this
                                    # one

    conflicts = []  # A list of any recipe names that cannot be built
                    # alongside this one

    def get_recipe_env(self, arch):
        env = super(YourRecipe, self).get_recipe_env()
        # Manipulate the env here if you want
        return env

    def should_build(self):
        # Add a check for whether the recipe is already built if you
        # want, and return False if it is.
        return True

    def prebuild_arch(self, arch):
        super(YourRecipe, self).prebuild_arch(self)
        # Do any extra prebuilding you want, e.g.:
        self.apply_patch('path/to/patch.patch')

    def build_arch(self, arch):
        super(YourRecipe, self).build_arch(self)
        # Build the code. Make sure to use the right build dir, e.g.
        with current_directory(self.get_build_dir(arch.arch)):
            sh.ls('-lathr')  # Or run some commands that actually do
                             # something

    def postbuild_arch(self, arch):
        super(YourRecipe, self).prebuild_arch(self)
        # Do anything you want after the build, e.g. deleting
        # unnecessary files such as documentation


recipe = YourRecipe()

レシピの例

この文書ではレシピを作成するために必要なことのほとんどをカバーしています。 さらなる例としてはpython-for-androidには人気のあるモジュールのための多くのレシピが含まれています。これは独自のモジュールを追加する方法を見つけるためには優れたリソースです。 これらは python-for-android Github ページ で見つけることができます。

Recipe クラス

Recipe は、すべてのp4aレシピの基本クラスです。 このクラスの中核となる文書を下に示し、独自のRecipeサブクラスを作成する方法について説明します