Документация Python неофициальный перевод
Содержание страницы

7. Использование Python на iOSUsing Python on iOS

Авторы:

Russell Keith-Magee (2024-03)

Python на iOS отличается от Python на настольных платформах. На настольной платформе Python обычно устанавливается как системный ресурс, доступный любому пользователю этого компьютера. Пользователи взаимодействуют с Python, запуская python и вводя команды в интерактивном режиме или запуская сценарии Python.

В iOS нет понятия установки как системного ресурса. Единственная единица распространения программного обеспечения – это «приложение». Также здесь нет консоли, в которой можно запустить python или работать с REPL Python.

Поэтому единственный способ использовать Python на iOS – это встраивание: нужно написать нативное приложение iOS и встроить в него интерпретатор Python с помощью libPython, а затем вызывать код Python через API встраивания Python. Полный интерпретатор Python, стандартная библиотека и весь ваш код Python упаковываются в отдельный пакет, который можно распространять через App Store.

Если вы впервые пробуете написать приложение для iOS на Python, такие проекты, как BeeWare и Kivy, предоставят гораздо более простой пользовательский опыт. Они берут на себя все сложности запуска проекта под iOS, так что вам нужно работать только с кодом Python.

7.1. Python во время выполнения на iOSPython at runtime on iOS

7.1.1. Совместимость с версиями iOSiOS version compatibility

Минимальная поддерживаемая версия iOS задаётся на этапе компиляции с помощью параметра --host для configure. По умолчанию при сборке для iOS Python компилируется с минимальной поддерживаемой версией 13.0. Чтобы использовать другую минимальную версию iOS, укажите номер версии в аргументе --host – например, --host=arm64-apple-ios15.4-simulator соберёт сборку для симулятора ARM64 с целевой версией 15.4.

7.1.2. Определение платформыPlatform identification

При выполнении на iOS sys.platform возвращает ios. Это значение возвращается на iPhone или iPad независимо от того, запущено ли приложение на симуляторе или физическом устройстве.

Информацию о конкретной среде выполнения, включая версию iOS, модель устройства и то, является ли оно симулятором, можно получить с помощью platform.ios_ver(). platform.system() вернёт iOS или iPadOS в зависимости от устройства.

os.uname() сообщает детали на уровне ядра; он вернёт имя Darwin.

7.1.3. Доступность стандартной библиотекиStandard library availability

Стандартная библиотека Python имеет некоторые заметные пропуски и ограничения на iOS. Подробнее см. в руководстве по доступности API для iOS.

7.1.4. Двоичные модули расширенияBinary extension modules

Одно из ключевых отличий iOS как платформы – распространение через App Store налагает жёсткие требования к упаковке приложения. Одно из них касается того, как распространяются двоичные модули расширения.

App Store требует, чтобы все двоичные модули в приложении iOS были динамическими библиотеками, помещёнными во фреймворк с соответствующими метаданными и расположенными в папке Frameworks упакованного приложения. Во фреймворке может быть только один двоичный файл, и никаких исполняемых двоичных материалов за пределами папки Frameworks быть не должно.

Это противоречит обычному подходу Python к распространению двоичных файлов, который позволяет загружать двоичный модуль расширения из любого места на sys.path. Чтобы соответствовать политикам App Store, проект iOS должен постобработать все пакеты Python, преобразовав двоичные модули .so в отдельные автономные фреймворки с соответствующими метаданными и подписью. Подробнее о том, как выполнить эту постобработку, см. в руководстве по добавлению Python в ваш проект.

Чтобы помочь Python находить двоичные файлы в новом расположении, исходный файл .so на sys.path заменяется файлом .fwork. Этот файл – текстовый файл, содержащий путь к фреймворку относительно пакета приложения. Чтобы фреймворк мог разрешать обратно исходное расположение, он должен содержать файл .origin с путём к файлу .fwork относительно пакета приложения.

Например, рассмотрим случай импорта 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 будет представлен в исходном расположении маркерным файлом sources/foo/bar/_whiz.abi3.fwork, содержащим путь Frameworks/foo.bar._whiz/foo.bar._whiz. Фреймворк также будет содержать Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin с путём к файлу .fwork.

При запуске на iOS интерпретатор Python устанавливает AppleFrameworkLoader, который умеет читать и импортировать файлы .fwork. После импорта атрибут __file__ двоичного модуля будет указывать на расположение файла .fwork. Однако ModuleSpec для загруженного модуля будет сообщать origin как расположение двоичного файла в папке фреймворка.

7.1.5. Компиляторные заглушкиCompiler stub binaries

Xcode не предоставляет явные компиляторы для iOS; вместо этого он использует скрипт xcrun, который разрешается в полный путь к компилятору (например, xcrun --sdk iphoneos clang для получения clang для устройства iPhone). Однако использование этого скрипта создаёт две проблемы:

  • Вывод команды xcrun содержит пути, зависящие от конкретной машины, из-за чего модуль sysconfig невозможно использовать совместно между пользователями;

  • Это приводит к определениям CC/CPP/LD/AR, содержащим пробелы. Многие инструменты экосистемы C предполагают, что можно разделить командную строку по первому пробелу, чтобы получить путь к исполняемому файлу компилятора; при использовании xcrun это не так.

Чтобы избежать этих проблем, Python предоставляет заглушки для этих инструментов. Эти заглушки представляют собой обёртки в виде сценариев оболочки вокруг базовых инструментов xcrun, распространяемые в папке bin вместе со скомпилированным фреймворком iOS. Эти сценарии являются перемещаемыми и всегда разрешаются в соответствующие локальные системные пути. Включение этих сценариев в папку bin, сопровождающую фреймворк, делает содержимое модуля sysconfig полезным для конечных пользователей, собирающих собственные модули. При компиляции сторонних модулей Python для iOS убедитесь, что эти заглушки находятся в вашем PATH.

7.2. Установка Python на iOSInstalling Python on iOS

7.2.1. Инструменты для сборки iOS-приложенийTools for building iOS apps

Сборка для iOS требует использования инструментов Apple Xcode. Настоятельно рекомендуется использовать самую последнюю стабильную версию Xcode. Для этого потребуется самая последняя (или предпоследняя) версия macOS, так как Apple не поддерживает Xcode для старых версий macOS. Для разработки под iOS недостаточно утилит Xcode Command Line Tools; необходима полная установка Xcode.

Если вы хотите запустить свой код на симуляторе iOS, вам также потребуется установить платформу симулятора iOS. При первом запуске Xcode вам будет предложено выбрать платформу симулятора iOS. Кроме того, платформу симулятора iOS можно добавить, выбрав её на вкладке Platforms панели настроек Xcode.

7.2.2. Добавление Python в проект iOSAdding Python to an iOS project

Python можно добавить в любой проект iOS, используя Swift или Objective C. В следующих примерах будет использоваться Objective C; если вы используете Swift, вам может пригодиться библиотека вроде PythonKit.

Чтобы добавить Python в проект iOS Xcode:

  1. Соберите или получите Python XCFramework. Инструкции по сборке Python XCFramework см. в iOS/README.rst (в дистрибутиве исходного кода CPython). Как минимум потребуется сборка, поддерживающая arm64-apple-ios и один из вариантов: arm64-apple-ios-simulator или x86_64-apple-ios-simulator.

  2. Перетащите XCframework в свой проект iOS. В следующих инструкциях мы будем считать, что вы поместили XCframework в корень проекта; однако вы можете использовать любое другое расположение, скорректировав пути по необходимости.

  3. Перетащите файл iOS/Resources/dylib-Info-template.plist в проект и убедитесь, что он связан с целью приложения.

  4. Добавьте код вашего приложения как папку в проект Xcode. В следующих инструкциях мы будем считать, что ваш пользовательский код находится в папке с именем app в корне проекта; вы можете использовать любое другое расположение, скорректировав пути по необходимости. Убедитесь, что эта папка связана с вашей целью приложения.

  5. Выберите цель приложения, выбрав корневой узел вашего проекта Xcode, а затем имя цели на появившейся боковой панели.

  6. В настройках “General”, в разделе “Frameworks, Libraries and Embedded Content”, добавьте Python.xcframework, выбрав “Embed & Sign”.

  7. На вкладке “Build Settings” измените следующие параметры:

    • Параметры сборки

      • User Script Sandboxing: Нет

      • Enable Testability: Да

    • Пути поиска

      • Пути поиска фреймворков: $(PROJECT_DIR)

      • Пути поиска заголовков: "$(BUILT_PRODUCTS_DIR)/Python.framework/Headers"

    • Apple Clang - Предупреждения - Все языки

      • Quoted Include In Framework Header: Нет

  8. Добавьте шаг сборки, копирующий стандартную библиотеку Python в приложение. На вкладке «Build Phases» добавьте новый шаг сборки «Run Script» перед шагом «Embed Frameworks», но после шага «Copy Bundle Resources». Назовите шаг «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 может отличаться в зависимости от архитектур процессора, которые поддерживает ваша XCFramework.

  9. Добавьте второй шаг сборки, преобразующий двоичные модули расширения из стандартной библиотеки в формат «Framework». Добавьте шаг сборки «Run Script» непосредственно после шага, добавленного на шаге 8, и назовите его «Prepare Python Binary Modules». У него также должен быть снят флажок «Based on dependency analysis», а содержимое скрипта должно быть следующим:

    set -e
    
    install_dylib () {
        INSTALL_BASE=$1
        FULL_EXT=$2
    
        # Имя файла расширения
        EXT=$(basename "$FULL_EXT")
        # Расположение файла расширения относительно пакета
        RELATIVE_EXT=${FULL_EXT#$CODESIGNING_FOLDER_PATH/}
        # Путь к файлу расширения относительно базовой установки
        PYTHON_EXT=${RELATIVE_EXT/$INSTALL_BASE/}
        # Полное точечное имя модуля расширения, построенное из пути к файлу.
        FULL_MODULE_NAME=$(echo $PYTHON_EXT | cut -d "." -f 1 | tr "/" ".");
        # Идентификатор пакета; на самом деле не используется, но требуется для упаковки фреймворка Xcode
        FRAMEWORK_BUNDLE_ID=$(echo $PRODUCT_BUNDLE_IDENTIFIER.$FULL_MODULE_NAME | tr "_" "-")
        # Имя папки фреймворка.
        FRAMEWORK_FOLDER="Frameworks/$FULL_MODULE_NAME.framework"
    
        # Если папка фреймворка не существует, создайте её.
        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"
        # Создаёт файл-заполнитель .fwork в том месте, где находился .so
        echo "$FRAMEWORK_FOLDER/$FULL_MODULE_NAME" > ${FULL_EXT%.so}.fwork
        # Создаёт обратную ссылку на местоположение файла .so в фреймворке
        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
    
     # Очищает шаблон dylib
     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 "{}" \;
    
  10. Добавьте код на Objective C для инициализации и использования интерпретатора Python во встроенном режиме. Следует убедиться, что:

  • Режим UTF-8 (PyPreConfig.utf8_mode) включён;

  • Буферизированный stdio (PyConfig.buffered_stdio) отключён;

  • Запись байт-кода (PyConfig.write_bytecode) отключена;

  • Обработчики сигналов (PyConfig.install_signal_handlers) включены;

  • PYTHONHOME для интерпретатора настроены указывать на подпапку python пакета вашего приложения; и

  • PYTHONPATH для интерпретатора включает:

    • подпапку python/lib/python3.X пакета вашего приложения,

    • подпапка python/lib/python3.X/lib-dynload в составе пакета приложения и

    • подпапка app в составе пакета приложения

Расположение пакета вашего приложения можно определить с помощью [[NSBundle mainBundle] resourcePath].

Шаги 8, 9 и 10 этой инструкции предполагают, что у вас есть единственная папка с чистым кодом приложения на Python с именем app. Если в вашем приложении есть сторонние двоичные модули, потребуются дополнительные шаги:

  • Необходимо убедиться, что все папки, содержащие сторонние двоичные файлы, либо связаны с целью приложения, либо скопированы на шаге 8. Шаг 8 также должен удалять все двоичные файлы, не подходящие для платформы, на которую ориентирована конкретная сборка (например, удалять двоичные файлы для устройства, если собирается приложение для симулятора).

  • Все папки, содержащие сторонние двоичные файлы, должны быть преобразованы в форму фреймворка на шаге 9. Вызов install_dylib, обрабатывающий папку lib-dynload, можно скопировать и адаптировать для этой цели.

  • Если используется отдельная папка для сторонних пакетов, убедитесь, что она включена в конфигурацию PYTHONPATH на шаге 10.

  • Если какая-либо из папок, содержащих сторонние пакеты, будет содержать файлы .pth, следует добавить эту папку как каталог сайта (с помощью site.addsitedir()), а не добавлять напрямую к PYTHONPATH или sys.path.

7.2.3. Тестирование пакета PythonTesting a Python package

Дерево исходных кодов CPython содержит тестовый проект, который используется для запуска тестового набора CPython в симуляторе iOS. Этот тестовый проект также можно использовать для запуска тестов вашей библиотеки Python на iOS.

После сборки или получения iOS XCFramework (подробнее см. iOS/README.rst) создайте клон тестового проекта Python iOS, выполнив:

$ python iOS/testbed clone --framework <path/to/Python.xcframework> --app <path/to/module1> --app <path/to/module2> app-testbed

Вам нужно изменить ссылку iOS/testbed так, чтобы она указывала на этот каталог в дереве исходников CPython; все папки, указанные с флагом --app, будут скопированы в клонированный тестовый проект. Полученный тестовый проект будет создан в папке app-testbed. В этом примере module1 и module2 будут импортируемыми модулями во время выполнения. Если в вашем проекте есть дополнительные зависимости, их можно установить в папку app-testbed/iOSTestbed/app_packages (с помощью pip install --target app-testbed/iOSTestbed/app_packages или аналогичного инструмента).

Затем можно использовать папку app-testbed для запуска тестового набора вашего приложения. Например, если module1.tests была точкой входа вашего тестового набора, можно выполнить:

$ python app-testbed run -- module1.tests

Это эквивалентно запуску python -m module1.tests в настольной сборке Python. Все аргументы после -- будут переданы тестовому проекту так, как если бы они были аргументами для python -m на настольной машине.

Тестовый проект также можно открыть в Xcode, выполнив:

$ open app-testbed/iOSTestbed.xcodeproj

Это позволит использовать полный набор инструментов Xcode для отладки.

Аргументы, используемые для запуска тестового набора, определяются в рамках тестового плана. Чтобы изменить тестовый план, выберите узел тестового плана в дереве проекта (это должен быть первый дочерний элемент корневого узла) и выберите вкладку «Configurations». Измените значение «Arguments Passed On Launch», чтобы изменить аргументы тестирования.

Тестовый план также отключает параллельное тестирование и предписывает использование файла iOSTestbed.lldbinit для настройки отладчика. Конфигурация отладчика по умолчанию отключает автоматические точки останова на сигналы SIGINT, SIGUSR1, SIGUSR2 и SIGXFSZ.

7.3. Соответствие требованиям App StoreApp Store Compliance

Единственный механизм распространения приложений на сторонние устройства iOS – это отправка приложения в App Store; приложения, отправленные на распространение, должны пройти процесс проверки приложений Apple. Этот процесс включает набор автоматических правил проверки, которые анализируют отправленный пакет приложения на наличие проблемного кода.

Стандартная библиотека Python содержит код, который, как известно, нарушает эти автоматические правила. Хотя эти нарушения кажутся ложными срабатываниями, правила проверки Apple не подлежат обжалованию; поэтому необходимо модифицировать стандартную библиотеку Python, чтобы приложение прошло проверку App Store.

Дерево исходных кодов Python содержит файл исправлений, который удалит весь код, вызывающий проблемы в процессе проверки App Store. Это исправление применяется автоматически при сборке для iOS.