Spec-Zone.ru › Python 3.13

Использование Python в 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.

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

7.1. Python во время выполнения в iOS

7.1.1. Совместимость с версиями iOS

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

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

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

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

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

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

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

7.1.4. Бинарные модули расширения

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

App Store для iOS требует, чтобы все бинарные модули в приложении 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. Заглушки бинарных файлов компилятора

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

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

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

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

7.2.1. Инструменты для создания приложений iOS

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

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

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

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» измените следующие параметры:

    • 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
  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
    
        # 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 "{}" \;
    
  10. Добавьте код Objective C для инициализации и использования интерпретатора Python во встроенном режиме. Убедитесь, что:
  • режим UTF-8 (PyPreConfig.utf8_mode) включён;
  • буферизованный стандартный ввод-вывод (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. Тестирование пакета Python

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

После сборки или получения XCFramework для iOS (подробности см. в 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 Store

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

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

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

© 2001–2025 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.13/using/ios.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API