7. iOS で Python を使う¶
- 著者:
Russell Keith-Magee (2024-03)
iOS における Pythonは、デスクトッププラットフォームにおける Python とは異なります。 デスクトッププラットフォームでは、 Python は一般的にコンピューターのどのユーザーでも使えるシステムリソースとしてインストールされます。そして、ユーザーは python 実行可能ファイルを実行して対話型プロンプトにコマンドを入力したり、 Python スクリプトを実行したりして、 Python を使用することができるのです。
iOS においては、システムリソースとしてのインストールという概念はありません。ソフトウェア配布が可能なのは、 "アプリ" だけです。また、 python 実行可能ファイルの実行したり、 Python の REPL を使用したりする、コンソールも存在しません。
このため、 Python を iOS 上で使うただ一つの方法は、埋め込みモード、つまり、ネイティブ iOS アプリケーションを書き、 libPython
を使用して Python インタープリターを埋め込み、そして Python 埋め込み API を使用して Python コードを呼び出すことです。 それにより、完全な Python インタープリター、標準ライブラリ、 及び Python のコードが、 iOS App Store を経由して配布可能なスタンドアローンなバンドルとしてパッケージ化されます。
もし、初めて iOS アプリを Python で書くことを試みているなら、 BeeWare や Kivy といったプロジェクトは、よりわかりやすいユーザー体験を提供するでしょう。これらのプロジェクトは iOS プロジェクトを実行することに関連する複雑なことを管理するので、あなたは Python のコードに集中するだけで良くなります。
7.1. iOS ランタイムでの Python¶
7.1.1. iOS のバージョン互換性¶
サポートされる最小の iOS バージョンは、コンパイル時に configure
の --host
オプションを使用して指定できます。デフォルトでは、 iOS 用にコンパイルする場合、 Python は最小で 13.0 の iOS バージョンをサポートするようにコンパイルされます。異なる最小 iOS バージョンを使用するには、 --host
引数の一部としてバージョン番号を提供します。例えば --host=arm64-apple-ios15.4-simulator
とすると、 ARM64 シミュレーター用のビルドを Deployment Target 15.4 でコンパイルします。
7.1.2. プラットフォームの識別¶
iOS 上で実行している場合、 sys.platform
は ios
となります。アプリがシミュレーターで実行されているか、物理デバイスで実行されているかにかかわらず、 iPhone または iPad ではこの値となります。
Information about the specific runtime environment, including the iOS version,
device model, and whether the device is a simulator, can be obtained using
platform.ios_ver()
. platform.system()
will report iOS
or
iPadOS
, depending on the device.
os.uname()
reports kernel-level details; it will report a name of
Darwin
.
7.1.3. 標準ライブラリの利用可能性¶
The Python standard library has some notable omissions and restrictions on iOS. See the API availability guide for iOS for details.
7.1.4. バイナリ拡張モジュール¶
プラットフォームとしての iOS についての重要な違いの一つは、 App Store での配布がアプリケーションのパッケージングに厳しい条件を課すということです。 これらの条件の一つは、バイナリ拡張モジュールの配布方法を規定します。
iOS App Store では、 iOS アプリの全てのバイナリモジュールが、パッケージ化されたアプリの Frameworks
フォルダに保存された、適切なメタデータ付きのフレームワークに含まれる動的ライブラリである必要があります。フレームワークごとにバイナリは一つだけで、 Frameworks
フォルダの外に実行可能バイナリデータを設置することはできません。
これは、バイナリ拡張モジュールが sys.path
上のどの場所からでも読み込み可能な、通常の Python のバイナリ配布のアプローチと衝突します。 確実に App Store ポリシーに従うために、 iOS プロジェクトはいずれの Python パッケージにも、 .so
バイナリモジュールを、個別の、スタンドアローンで、適切なメタデータと署名付きのフレームワークに変換する後処理を行わなければなりません。どのように後処理を行うかの詳細は、 プロジェクトに Python を追加する のガイドを参照してください。
Python が新しい場所にあるバイナリを見つけることを助けるために、 sys.path
にある元の .so
ファイルは .fwork
ファイルに置き換えられます。 このファイルは、アプリバンドルからのレームワークバイナリの相対パスを含むテキストファイルです。フレームワークが元の場所に解決できるようにするためには、フレームワークは、アプリバンドルからの .fwork
ファイルの相対パスが含まれた、 .origin
ファイルを含む必要があります。
例えば、from foo.bar import _whiz
をインポートする場合を考えてみましょう。 _whiz
がバイナリモジュール sources/foo/bar/_whiz.abi3.so
で実装されており、 sources
のアプリケーションバンドルからの相対パスが sys.path
に登録されています。このモジュールは Frameworks/foo.bar._whiz.framework/foo.bar._whiz
(フレームワーク名はモジュールの完全なインポートパスから命名されています) として、バイナリをフレームワークとして識別する Info.plist
ファイルを .framework
ディレクトリ内に設置して配布しなければなりません。 foo.bar._whiz
モジュールは、元の場所で、 Frameworks/foo.bar._whiz/foo.bar._whiz
のパスを含む sources/foo/bar/_whiz.abi3.fwork
マーカーファイルに記述されます。 また、フレームワークは、 .fwork
へのパスを含む Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin
も含まなければなりません。
iOS 上で実行している場合、 Python インタープリターは .fwork
ファイルを読み込んでインポートすることができる AppleFrameworkLoader
をインストールします。インポートされると、バイナリモジュールの __file__
属性は .fwork
ファイルの場所を返します。一方、読み込まれたモジュールの ModuleSpec
はフレームワークフォルダのバイナリの場所として origin
を返します。
7.1.5. コンパイラスタブバイナリ¶
Xcode は、 iOS 用の明示的なコンパイラーを提供していません。代わりに、完全なコンパイラーのパスを解決する xcrun
スクリプトを使用します (たとえば xcrun --sdk iphoneos clang
は iPhone デバイス用の clang
を取得します) 。しかし、これは2つの問題を引き起こします:
xcrun
の出力はマシン固有のパスを含み、ユーザー間で共有できない sysconfig モジュールとなります。これにより、
CC
/CPP
/LD
/AR
定義にスペースが含まれることになります。多くの C エコシステムツールが、最初のスペースでコマンドラインを分割し、コンパイラー実行ファイルを取得できることを前提としています。しかし、xcrun
を使用する場合はそうではありません。
これらの問題を避けるため、 Python はこれらのツール用のスタブを提供しました。これらのスタブは、コンパイルされた iOS フレームワークとともに配布される bin
フォルダで配布される、基礎の xcrun
ツールのシェルスクリプトラッパーです。これらのスクリプトは再配置可能で、常に適切なローカルシステムパスに解決されます。これらのスクリプトをフレームワークを伴う bin フォルダーに含めることで、 sysconfig
モジュールはエンドユーザーが自身のモジュールをコンパイルするのに有用になります。iOS 用の サードパーティの Python モジュールをコンパイルするときは、これらのスタブバイナリがパス上にあることを確認するべきです。
7.2. iOS での Python のインストール¶
7.2.1. iOS アプリビルド用のツール¶
iOS 向けのビルドには、 Apple の Xcode のツールを使用します。Xcode の最新の安定リリースを使用することを強く推奨します。Apple は古い macOS のバージョン向けには Xcode をメンテナンスしないため、これには最も (または二番目に) 最近にリリースされた macOS のバージョンが必要です。 Xcode コマンドラインツールは iOS 開発には不十分であり、完全な Xcode のインストールが必要です。
iOS シミュレーター上でコードを実行したい場合は、 iOS Simulator プラットフォームもインストールする必要があります。 Xcode を初めて実行したとき、 iOS Simulator プラットフォームを選択するプロンプトが表示されるはずです。代わりに、 Xcode の Settings パネルの Platforms タブから iOS Simulator プラットフォームを選択して追加することもできます。
7.2.2. iOS プロジェクトに Python を追加する¶
Python は、 Swift または Objective-C を使って、どの iOS プロジェクトにでも追加できます。以下の例では、 Objective-C を使用しています。 Swift を使う場合は、 PythonKit のようなライブラリが役に立つかもしれません。
Python を iOS Xcode プロジェクトに追加するには:
Python の
XCFramework
をビルドまたは取得します。 Python のXCFramework
をビルドする方法の詳細は、 iOS/README.rst (CPython のソースコードにあります) の説明を参照してください。最低でも、arm64-apple-ios
に加えてarm64-apple-ios-simulator
またはx86_64-apple-ios-simulator
のどちらかをサポートするビルドが必要です。XCframework
を iOS プロジェクトにドラッグします。以降の説明では、 プロジェクトのルートにXCframework
を設置したものと仮定しますが、パスを必要に応じて調整することで、他の場所を使用することも出来ます。iOS/Resources/dylib-Info-template.plist
ファイルをプロジェクトにドラッグし、それがアプリのターゲットに関連付けられていることを確認します。アプリケーションのコードを、 Xcode プロジェクトにフォルダーとして追加します。以降の説明では、 プロジェクトのルートに
app
という名前のユーザーコードが入ったフォルダを設置したものと仮定しますが、パスを必要に応じて調整することで、他の場所を使用することも出来ます。フォルダがアプリのターゲットに関連付けられていることを確認してください。Xcode プロジェクトのルートノードを選択することで、アプリのターゲットを選択してください。すると、ターゲット名がサイドバーに現れるはずです。
"General" の設定の "Frameworks, Libraries and Embedded Content" に、 "Embed & Sign" を選択して
Python.xcframework
を追加してください。"Build Settings" タブで、次の項目を修正してください:
Build Options
User Script Sandboxing: No
Enable Testability: Yes
Search Paths
Framework Search Paths:
$(PROJECT_DIR)
Header Search Paths:
"$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"
Apple Clang - Warnings - All languages
Quoted Include In Framework Header: No
Python の標準ライブラリをアプリにコピーするビルドステップを追加します。 "Build Phases" タブで、新しい "Run Script" ビルドステップを、 "Embed Frameworks" ステップの前に追加してください。ステップの名前は、 "Install Target Specific Python Standard Library" にして、 "Based on dependency analysis" のチェックボックスを外し、スクリプトの内容を次のように設定してください:
set -e mkdir -p "$CODESIGNING_FOLDER_PATH/python/lib" if [ "$EFFECTIVE_PLATFORM_NAME" = "-iphonesimulator" ]; then echo "Installing Python modules for iOS Simulator" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64_x86_64-simulator/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" else echo "Installing Python modules for iOS Device" rsync -au --delete "$PROJECT_DIR/Python.xcframework/ios-arm64/lib/" "$CODESIGNING_FOLDER_PATH/python/lib/" fi
XCframework のシミュレーター "slice" の名前は、
XCFramework
がサポートする CPU アーキテクチャによって異なる可能性があることに注意してください。標準ライブラリのバイナリ拡張モジュールをフレームワーク形式に処理する、二つ目のビルドステップを追加します。手順 8 で追加したものの直後に、 "Prepare Python Binary Modules" という名前の "Run Script" ビルドステップを追加してください。 "Based on dependency analysis" のチェックを外して、以下のスクリプトの内容も追加する必要もあります。
set -e install_dylib () { INSTALL_BASE=$1 FULL_EXT=$2 # The name of the extension file EXT=$(basename "$FULL_EXT") # The location of the extension file, relative to the bundle RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/} # The path to the extension file, relative to the install base PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/} # The full dotted name of the extension module, constructed from the file path. FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" "."); # A bundle identifier; not actually used, but required by Xcode framework packaging FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-") # The name of the framework folder. FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework" # If the framework folder doesn't exist, create it. if [ ! -d "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" ]; then echo "Creating framework for $RELATIVE_EXT" mkdir -p "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER" cp "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" plutil -replace CFBundleExecutable -string "$FULL_MODULE_NAME" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" plutil -replace CFBundleIdentifier -string "$FRAMEWORK_BUNDLE_ID" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/Info.plist" fi echo "Installing binary for $FRAMEWORK_FOLDER/$FULL_MODULE_NAME" mv "$FULL_EXT" "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" # Create a placeholder .fwork file where the .so was echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork # Create a back reference to the .so file location in the framework echo "${RELATIVE_EXT%.so}.fwork" > "$CODESIGNING_FOLDER_PATH/$FRAMEWORK_FOLDER/$FULL_MODULE_NAME.origin" } PYTHON_VER=$(ls -1 "$CODESIGNING_FOLDER_PATH/python/lib") echo "Install Python $PYTHON_VER standard library extension modules..." find "$CODESIGNING_FOLDER_PATH/python/lib/$PYTHON_VER/lib-dynload" -name "*.so" | while read FULL_EXT; do install_dylib python/lib/$PYTHON_VER/lib-dynload/ "$FULL_EXT" done # Clean up dylib template rm -f "$CODESIGNING_FOLDER_PATH/dylib-Info-template.plist" echo "Signing frameworks as $EXPANDED_CODE_SIGN_IDENTITY_NAME ($EXPANDED_CODE_SIGN_IDENTITY)..." find "$CODESIGNING_FOLDER_PATH/Frameworks" -name "*.framework" -exec /usr/bin/codesign --force --sign "$EXPANDED_CODE_SIGN_IDENTITY" ${OTHER_CODE_SIGN_FLAGS:-} -o runtime --timestamp=none --preserve-metadata=identifier,entitlements,flags --generate-entitlement-der "{}" \;
Python インタープリターを埋め込みモードで初期化・使用する Objective C コードを追加します。次のことを確認する必要があります:
UTF-8 mode (
PyPreConfig.utf8_mode
) is enabled;Buffered stdio (
PyConfig.buffered_stdio
) is disabled;Writing bytecode (
PyConfig.write_bytecode
) is disabled;Signal handlers (
PyConfig.install_signal_handlers
) are enabled;System logging (
PyConfig.use_system_logger
) is enabled (optional, but strongly recommended; this is enabled by default);
PYTHONHOME
for the interpreter is configured to point at thepython
subfolder of your app's bundle; andThe
PYTHONPATH
for the interpreter includes:
アプリのバンドルの
python/lib/python3.X
サブフォルダアプリのバンドルの
python/lib/python3.X/lib-dynload
サブフォルダアプリのバンドルの
app
サブフォルダアプリのバンドルの場所は
[[NSBundle mainBundle] resourcePath]
を用いて取得できます。
これらの手順 8, 9, 10 は app
という名前のただ一つの純粋な Python アプリケーションのコードのフォルダがあることを前提としています。もしサードパーティのバイナリモジュールがアプリに含まれる場合は、いくつかの追加の手順が必要です:
サードパーティのバイナリを含むフォルダが、アプリのターゲットに関連付けられている、または手順 8 の一部としてコピーされていることを確認する必要があります。手順 8 では、特定のビルドがターゲットとするプラットフォームに適切でないバイナリを取り除く(つまり、もしシミュレーターをターゲットとするアプリをビルドしている場合は、デバイスバイナリを削除する)ことも必要です。
サードパーティのバイナリを含むフォルダは、手順 9 でフレームワークに処理されなければなりません。
lib-dynload
フォルダを処理するinstall_dylib
の呼び出しは、このためにコピー・変更することができます。If you're using a separate folder for third-party packages, ensure that folder is included as part of the
PYTHONPATH
configuration in step 10.If any of the folders that contain third-party packages will contain
.pth
files, you should add that folder as a site directory (usingsite.addsitedir()
), rather than adding toPYTHONPATH
orsys.path
directly.
7.2.3. Testing a Python package¶
The CPython source tree contains a testbed project that is used to run the CPython test suite on the iOS simulator. This testbed can also be used as a testbed project for running your Python library's test suite on iOS.
After building or obtaining an iOS XCFramework (See iOS/README.rst for details), create a clone of the Python iOS testbed project by running:
$ python iOS/testbed clone --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed
You will need to modify the iOS/testbed
reference to point to that
directory in the CPython source tree; any folders specified with the --app
flag will be copied into the cloned testbed project. The resulting testbed will
be created in the app-testbed
folder. In this example, the module1
and
module2
would be importable modules at runtime. If your project has
additional dependencies, they can be installed into the
app-testbed/iOSTestbed/app_packages
folder (using pip install --target
app-testbed/iOSTestbed/app_packages
or similar).
You can then use the app-testbed
folder to run the test suite for your app,
For example, if module1.tests
was the entry point to your test suite, you
could run:
$ python app-testbed run -- module1.tests
This is the equivalent of running python -m module1.tests
on a desktop
Python build. Any arguments after the --
will be passed to the testbed as
if they were arguments to python -m
on a desktop machine.
You can also open the testbed project in Xcode by running:
$ open app-testbed/iOSTestbed.xcodeproj
This will allow you to use the full Xcode suite of tools for debugging.
7.3. App Store コンプライアンス¶
サードパーティの iOS デバイスにアプリを配布する唯一の仕組みは、アプリを iOS App Store に送信することです。配布用に送信されたアプリは、 Apple のアプリ審査プロセスに合格する必要があります。このプロセスは、送信されたアプリケーションバンドルに問題のあるコードがないかどうか検査する自動検証ルールのセットを含んでいます。
Python の標準ライブラリには、これらの自動ルールに違反することが知られているコードがいくつか含まれています。これらの違反は誤検出であると思われますが、 Apple の審査ルールに異議を唱えることはできません。そのため、 Python の標準ライブラリを、アプリが App Store 審査に合格するよう修正する必要があります。
Python のソースツリーは、 App Store 審査プロセスで問題を引き起こすことが知られているすべてのコードを削除する パッチファイル を含んでいます。このパッチは iOS 用のビルド時に自動的に適用されます。