6. Usando Python no iOS¶
- Autores:
Russell Keith-Magee (2024-03)
Python no iOS é diferente do Python em plataformas de desktop. Em uma plataforma desktop, o Python geralmente é instalado como um recurso do sistema que pode ser usado por qualquer usuário daquele computador. Os usuários então interagem com o Python executando um executável python e inserindo comandos em um prompt interativo ou executando um script Python.
No iOS, não existe o conceito de instalação como recurso do sistema. A única unidade de distribuição de software é uma aplicação, ou “app”. Também não há console onde você possa executar um executável python ou interagir com um REPL do Python.
Como resultado, a única maneira de usar Python no iOS é no modo incorporado – ou seja, escrevendo uma aplicação iOS nativo e incorporando um interpretador Python usando libPython e invocando o código Python usando API de incorporação do Python. O interpretador Python completo, a biblioteca padrão e todo o seu código Python são então empacotados como um pacote independente que pode ser distribuído pela iOS App Store.
Se você deseja experimentar pela primeira vez escrever uma aplicação iOS em Python, projetos como BeeWare e Kivy irão fornecer uma experiência de usuário muito mais acessível. Esses projetos gerenciam as complexidades associadas à execução de um projeto iOS, portanto, você só precisa lidar com o próprio código Python.
6.1. Python em tempo de execução no iOS¶
6.1.1. iOS version compatibility¶
The minimum supported iOS version is specified at compile time, using the
--host option to configure. By default, when compiled for iOS,
Python will be compiled with a minimum supported iOS version of 13.0. To use a
different miniumum iOS version, provide the version number as part of the
--host argument - for example,
--host=arm64-apple-ios15.4-simulator would compile an ARM64 simulator build
with a deployment target of 15.4.
6.1.2. Identificação da plataforma¶
Ao executar no iOS, sys.platform reportará como ios. Este valor será retornado em um iPhone ou iPad, independentemente da aplicação estar execitando no simulador ou em um dispositivo físico.
Informações sobre o ambiente de execução específico, incluindo a versão do iOS, modelo do dispositivo e se o dispositivo é um simulador, podem ser obtidas usando platform.ios_ver(). platform.system() reportará iOS ou iPadOS, dependendo do dispositivo.
os.uname() reporta detalhes em nível de kernel; ele reportará o nome Darwin.
6.1.3. Disponibilidade da biblioteca padrão¶
A biblioteca padrão do Python tem algumas omissões e restrições notáveis no iOS. Consulte o guia de disponibilidade de API para iOS para obter detalhes.
6.1.4. Módulos de extensão binária¶
Uma diferença notável sobre o iOS como plataforma é que a distribuição da App Store impõe requisitos rígidos ao empacotamento de uma aplicação. Um desses requisitos rege como os módulos de extensão binária são distribuídos.
A iOS App Store exige que todos os módulos binários em uma aplicação iOS sejam bibliotecas dinâmicas, contidas em um framework com metadados apropriados, armazenados na pasta Frameworks da aplicação empacotada. Pode haver apenas um único binário por framework, e não pode haver nenhum material binário executável fora da pasta Frameworks.
Isto entra em conflito com a abordagem usual do Python para distribuição de binários, que permite que um módulo de extensão binária seja carregado de qualquer local em sys.path. Para garantir a conformidade com as políticas da App Store, um projeto iOS deve pós-processar quaisquer pacotes Python, convertendo módulos binários .so em estruturas independentes individuais com metadados e assinatura apropriados. Para obter detalhes sobre como realizar esse pós-processamento, consulte o guia para adicionar Python ao seu projeto.
Para ajudar o Python a descobrir binários em seu novo local, o arquivo .so original em sys.path é substituído por um arquivo .fwork. Este arquivo é um arquivo texto que contém a localização do binário do framework, relativo ao pacote de aplicações. Para permitir que o framework retorne ao local original, o framework deve conter um arquivo .origin que contém a localização do arquivo .fwork, relativo ao pacote da aplicação.
Por exemplo, considere o caso de uma importação from foo.bar import _whiz, onde _whiz é implementado com o módulo binário sources/foo/bar/_whiz.abi3.so, com sources sendo o local registrado em sys.path, relativo ao pacote da aplicação. Este módulo deve ser distribuído como Frameworks/foo.bar._whiz.framework/foo.bar._whiz (criando o nome do framework a partir do caminho de importação completo do módulo), com um arquivo Info.plist no diretório .framework identificando o binário como um framework. O módulo foo.bar._whiz seria representado no local original com um arquivo marcador sources/foo/bar/_whiz.abi3.fwork, contendo o caminho Frameworks/foo.bar._whiz/foo.bar._whiz. O framework também conteria Frameworks/foo.bar._whiz.framework/foo.bar._whiz.origin, contendo o caminho para o arquivo .fwork.
Ao executar no iOS, o interpretador Python instalará um AppleFrameworkLoader que é capaz de ler e importar arquivos .fwork. Uma vez importado, o atributo __file__ do módulo binário reportará como a localização do arquivo .fwork. Entretanto, o ModuleSpec para o módulo carregado reportará a origin como a localização do binário na pasta do framework.
6.1.5. Binários stub do compilador¶
O Xcode não expõe compiladores explícitos para iOS; em vez disso, ele usa um script xcrun que resolve um caminho do compilador (por exemplo, xcrun --sdk iphoneos clang para obter o clang para um dispositivo iPhone). No entanto, usar este script apresenta dois problemas:
A saída de
xcruninclui caminhos que são específicos da máquina, resultando em um módulo sysconfig que não pode ser compartilhado entre usuários; eIsso resulta em definições
CC/CPP/LD/ARque incluem espaços. Existem muitas ferramentas do ecossistema C que pressupõem que você pode dividir uma linha de comando no primeiro espaço para obter o caminho para o executável do compilador; este não é o caso ao usarxcrun.
Para evitar esses problemas, o Python forneceu stubs para essas ferramentas. Esses stubs são wrappers de script de shell em torno das ferramentas subjacentes xcrun, distribuídos em uma pasta bin distribuída junto com a estrutura iOS compilada. Esses scripts são relocáveis e sempre serão resolvidos para os caminhos apropriados do sistema local. Ao incluir esses scripts na pasta bin que acompanha um framework, o conteúdo do módulo sysconfig se torna útil para usuários finais compilarem seus próprios módulos. Ao compilar módulos Python de terceiros para iOS, você deve garantir que esses binários stub estejam no seu caminho.
6.2. Instalando Python no iOS¶
6.2.1. Ferramentas para construir aplicações de iOS¶
A construção para iOS requer o uso das ferramentas Xcode da Apple. É altamente recomendável que você use a versão estável mais recente do Xcode. Isso exigirá o uso da versão mais recente (ou segunda) do macOS lançada recentemente, já que a Apple não mantém o Xcode para versões mais antigas do macOS. As ferramentas de linha de comando do Xcode não são suficientes para o desenvolvimento iOS; você precisa de uma instalação completa do Xcode.
Se quiser executar seu código no simulador iOS, você também precisará instalar um iOS Simulator Platform. Você deverá ser solicitado a selecionar um iOS Simulator Platform ao executar o Xcode pela primeira vez. Alternativamente, você pode adicionar um iOS Simulator Platform selecionando na guia Platforms do painel Settings do Xcode.
6.2.2. Adicionando Python a um projeto iOS¶
Python pode ser adicionado a qualquer projeto iOS, usando Swift ou Objective C. Os exemplos a seguir usarão Objective C; se você estiver usando Swift, poderá achar uma biblioteca como PythonKit útil.
Para adicionar Python a um projeto Xcode de iOS:
Construa ou obtenha um
XCFrameworkem Python. Veja as instruções em iOS/README.rst (na distribuição fonte do CPython) para detalhes sobre como construir umXCFrameworkem Python. No mínimo, você precisará de uma construção que tenha suportearm64-apple-ios, além dearm64-apple-ios-simulatoroux86_64-apple-ios-simulator.Arraste o
XCframeworkpara o seu projeto iOS. Nas instruções a seguir, presumiremos que você colocou oXCframeworkna raiz do seu projeto; no entanto, você pode usar qualquer outro local desejado ajustando os caminhos conforme necessário.Arraste o arquivo
iOS/Resources/dylib-Info-template.plistpara o seu projeto e certifique-se de que ele esteja associado ao destino da aplicação.Adicione o código da sua aplicação como uma pasta no seu projeto Xcode. Nas instruções a seguir, presumiremos que seu código de usuário está em uma pasta chamada
appna raiz do seu projeto; você pode usar qualquer outro local ajustando os caminhos conforme necessário. Certifique-se de que esta pasta esteja associada ao destino da sua aplicação.Selecione o destino da aplicação selecionando o nó raiz do seu projeto Xcode e, em seguida, o nome do destino na barra lateral que aparece.
Nas configurações de “General”, em “Frameworks, Libraries and Embedded Content”, adicione
Python.xcframework, com “Embed & Sign” selecionado.Na guia “Build Settings”, modifique o seguinte:
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
Adicione uma etapa de construção que copie a biblioteca padrão do Python em sua aplicação. Na aba “Build Phases”, adicione uma nova etapa de construção “Run Script” antes da etapa “Embed Frameworks”, mas depois da etapa “Copy Bundle Resources”. Nomeie a etapa como “Install Target Specific Python Standard Library”, desative a caixa de seleção “Based on dependency analysis” e defina o conteúdo do script como:
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
Note que o nome da “fatia” do simulador no XCframework pode ser diferente, dependendo das arquiteturas de CPU que seu
XCFrameworkprovê suporte.Adicione uma segunda etapa de construção que processe os módulos de extensão binária na biblioteca padrão no formato “Framework”. Adicione uma etapa de construção “Run Script” diretamente após aquela que você adicionou na etapa 8, chamada “Prepare Python Binary Modules”. Também deve ter “Based on dependency analysis” desmarcado, com o seguinte conteúdo de script:
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 "{}" \;
Adicione código Objective C para inicializar e usar um interpretador Python em modo embarcado. Você deve garantir que:
Modo UTF-8esteja enabled;
Buffered stdioesteja disabled;
Escrita de bytecodeesteja disabled;
Manipuladores de sinaisestejam enabled;
PYTHONHOMEpara o interpretador esteja configurado para apontar para a subpastapythondo pacote da sua aplicação; eO
PYTHONPATHpara o interpretador inclui:
a subpasta
python/lib/python3.Xdo pacote da sua aplicação,a subpasta
python/lib/python3.X/lib-dynloaddo pacote da sua aplicação, ea subpasta
appdo pacote da sua aplicaçãoO local do pacote da sua aplicação pode ser determinada usando
[[NSBundle mainBundle] resourcePath].
Os passos 8, 9 e 10 destas instruções presumem que você tem uma única pasta de código de aplicação Python puro, chamada app. Se você tiver módulos binários de terceiros em sua aplicação, serão necessárias algumas etapas adicionais:
Você precisa garantir que todas as pastas que contenham binários de terceiros sejam associadas ao destino da aplicação ou copiadas como parte da etapa 8. A etapa 8 também deve limpar quaisquer binários que não sejam apropriados para a plataforma que uma construção específica está direcionando (ou seja, exclua todos os binários do dispositivo se estiver criando uma aplicação direcionado ao simulador).
Quaisquer pastas que contenham binários de terceiros devem ser processadas no formato framework no passo 9. A invocação de
install_dylibque processa a pastalib-dynloadpode ser copiada e adaptada para este propósito.Se você estiver usando uma pasta separada para pacotes de terceiros, certifique-se de que essa pasta esteja incluída como parte da configuração
PYTHONPATHno passo 10.