Construindo um aplicativo C++ SDL para iOS no Linux

Nesta postagem, descreverei o procedimento para criar um aplicativo C++ SDL para iOS no Linux, assinar um arquivo ipa sem uma assinatura paga do Apple Developer e instalá-lo em um dispositivo limpo (iPad) usando macOS sem Jailbreak.< /p>

Primeiro, vamos instalar o conjunto de ferramentas de compilação para Linux:
https://github.com/tpoechtrager/cctools-port

O conjunto de ferramentas precisa ser baixado do repositório e, em seguida, siga as instruções no site do Godot Engine para concluir a instalação:
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html

No momento, você precisa baixar o Xcode dmg e copiar o SDK de lá para construir o cctools-port. Esta etapa é mais fácil de concluir no macOS; basta copiar os arquivos SDK necessários do Xcode instalado. Após a montagem bem-sucedida, o terminal conterá o caminho para o conjunto de ferramentas do compilador cruzado.

Em seguida, você pode começar a criar o aplicativo SDL para iOS. Vamos abrir o cmake e adicionar as alterações necessárias para construir o código C++:

SET(CMAKE_SYSTEM_NAME Darwin)
SET(CMAKE_C_COMPILER arm-apple-darwin11-clang)
SET(CMAKE_CXX_COMPILER arm-apple-darwin11-clang++)
SET(CMAKE_LINKER arm-apple-darwin11-ld)

Agora você pode compilar usando cmake e make, mas não se esqueça de adicionar $PATH ao conjunto de ferramentas do compilador cruzado:


PATH=$PATH:~/Sources/cctools-port/usage_examples/ios_toolchain/target/bin

Para uma correta vinculação com frameworks e SDL, escrevemos eles em cmake, dependências do jogo Space Jaguar por exemplo:


target_link_libraries(
${FSEGT_PROJECT_NAME}
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libclang_rt.ios.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_mixer.a
${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/libSDL2_image.a
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreServices.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/ImageIO.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Metal.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AVFoundation.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/GameController.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreMotion.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreGraphics.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/AudioToolbox.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/CoreAudio.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/QuartzCore.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/OpenGLES.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/UIKit.framework"
"${FLAME_STEEL_PROJECT_ROOT_DIRECTORY}/scripts/buildScripts/ios/resources/libs/Foundation.framework"
)

No meu caso, as bibliotecas SDL, SDL_Image, SDL_mixer são compiladas no Xcode no macOS antecipadamente para vinculação estática; Frameworks copiados do Xcode. A biblioteca libclang_rt.ios.a também foi adicionada, que inclui chamadas de tempo de execução específicas do iOS, por exemplo isOSVersionAtLeast. Uma macro está incluída para trabalhar com OpenGL ES, desabilitando funções não suportadas na versão móvel, semelhante ao Android.

Depois de resolver todos os problemas de construção, você deverá obter o binário montado para arm. A seguir, vamos considerar a execução do binário montado em um dispositivo sem Jailbreak.

No macOS, instale o Xcode, cadastre-se no portal da Apple, sem pagar pelo programa de desenvolvedor. Adicione uma conta no Xcode -> Preferências -> Contas, crie um aplicativo em branco e construa em um dispositivo real. Durante a montagem, o dispositivo será adicionado à conta de desenvolvedor gratuita. Após a montagem e lançamento, você precisa construir o arquivo para fazer isso, selecione Dispositivo e produto iOS genérico -> Arquivo. Depois que o arquivo for compilado, extraia os arquivos incorporados.mobileprovision e PkgInfo dele. No log de compilação do dispositivo, encontre a linha de codesign com a chave de assinatura correta, o caminho para o arquivo de direitos com a extensão app.xcent e copie-o.

Copie a pasta .app do arquivo, substitua o binário no arquivo por um compilado por um compilador cruzado no Linux (por exemplo SpaceJaguar.app/SpaceJaguar), depois adicione os recursos necessários ao .app, verifique o integridade dos arquivos PkgInfo e incorporado.mobileprovision no .app do arquivo, copie novamente se necessário. Assinamos novamente o .app usando o comando codesign – codesign requer uma chave de entrada para assinar, o caminho para o arquivo de direitos (pode ser renomeado com uma extensão .plist)

Após assinar novamente, crie uma pasta Payload, mova a pasta com a extensão .app para lá, crie um arquivo zip com Payload na raiz, renomeie o arquivo com a extensão .ipa. Depois disso, no Xcode, abra a lista de dispositivos e arraste e solte o novo ipa na lista de aplicativos do dispositivo; A instalação via Apple Configurator 2 não funciona para este método. Se a nova assinatura for feita corretamente, o aplicativo com o novo binário será instalado em um dispositivo iOS (por exemplo iPad) com certificado de 7 dias, o que é suficiente para o período de teste.

Fontes

https://github.com/tpoechtrager/cctools-port
https://docs.godotengine.org/ru/latest/development/compiling/cross-compiling_for_ios_on_linux.html
https://jonnyzzz.com/blog/2018/06/13/link-error-3/
https://stackoverflow.com/questions/6896029/re-sign-ipa-iphone
https://developer.apple.com/library/archive/documentation/Security/Conceptual/CodeSigningGuide/Procedures/Procedures.html

Portando um aplicativo C++ SDL para Android

Neste post descreverei minha experiência de portar um protótipo de editor 3D Cube Art Projectno Android.
Primeiro, vejamos o resultado; um editor com um cursor de cubo 3D vermelho está sendo executado no emulador:

Para uma montagem bem-sucedida, você deve fazer o seguinte:

  1. Instale o Android SDK e o NDK mais recentes (quanto mais recente a versão do NDK, melhor).
  2. Baixe o código-fonte do SDL2 e pegue o modelo de lá para construir o aplicativo Android.
  3. Adicione imagem SDL e misturador SDL à montagem.
  4. Adicionar as bibliotecas do meu mecanismo de jogo e kit de ferramentas, suas dependências (GLM, JSON para C++ moderno)
  5. Adaptar arquivos assembly para Gradle.
  6. Adaptar código C++ para compatibilidade com Android, alterações nos componentes dependentes da plataforma afetados (OpenGL ES, inicialização de contexto gráfico)
  7. Crie e teste o projeto no emulador.

Modelo de projeto

Carregando fontes SDL, SDL Image, SDL Mixer:
https://www.libsdl.org/download-2.0.php
A pasta docs contém instruções detalhadas para trabalhar com o modelo de projeto Android; copie o diretório android-project para uma pasta separada, crie um link simbólico ou copie a pasta SDL para android-project/app/jni.
Substituímos o identificador correto pelo sinalizador avd, iniciamos o emulador Android no diretório Sdk:

cd ~/Android/Sdk/emulator
./emulator -avd Pixel_2_API_24

Especifique os caminhos no script, monte o projeto:

rm -rf app/build || true
export ANDROID_HOME=/home/demensdeum/Android/Sdk/
export ANDROID_NDK_HOME=/home/demensdeum/Android/android-ndk-r21-beta2/
./gradlew clean build
./gradlew installDebug

O modelo de projeto SDL com código C do arquivo deve ser montado

android-sdl-test-app/cube-art-project-android/app/jni/src/YourSourceHere.c

Dependências

Baixe o código-fonte em arquivos para SDL_image, SDL_mixer:
https://www.libsdl.org/projects/SDL_image/
https://www.libsdl.org/projects/SDL_mixer/

Carregando as dependências do seu projeto, por exemplo minhas bibliotecas compartilhadas:
https://gitlab.com/demensdeum/FlameSteelCore/
https://gitlab.com/demensdeum/FlameSteelCommonTraits
https://gitlab.com/demensdeum/FlameSteelBattleHorn
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkit/
https://gitlab.com/demensdeum/FlameSteelEngineGameToolkitFSGL
https://gitlab.com/demensdeum/FSGL
https://gitlab.com/demensdeum/cube-art-project

Fazemos upload de tudo isso para app/jni, cada “módulo” em uma pasta separada, por exemplo app/jni/FSGL. A seguir, você tem a opção de encontrar geradores funcionais para os arquivos Application.mk e Android.mk, não os encontrei, mas talvez haja uma solução simples baseada no CMake. Siga os links e comece a se familiarizar com o formato de arquivo assembly para Android NDK:
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk

Você também deve ler sobre diferentes implementações de APP_STL no NDK:
https://developer.android.com/ndk/guides/cpp-support.html

Após a familiarização, criamos um arquivo Android.mk para cada “módulo”, seguido de um exemplo de arquivo assembly da biblioteca compartilhada Cube-Art-Project:

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

APP_STL := c++_static
APP_CPPFLAGS := -fexceptions
LOCAL_MODULE := CubeArtProject
LOCAL_C_INCLUDES := $(LOCAL_PATH)/src $(LOCAL_PATH)/../include $(LOCAL_PATH)/../include/FlameSteelCommonTraits/src/FlameSteelCommonTraits
LOCAL_EXPORT_C_INCLUDES = $(LOCAL_PATH)/src/

define walk
$(wildcard $(1)) $(foreach e, $(wildcard $(1)/*), $(call walk, $(e)))
endef

ALLFILES = $(call walk, $(LOCAL_PATH)/src)
FILE_LIST := $(filter %.cpp, $(ALLFILES))
$(info CubeArtProject source code files list)
$(info $(FILE_LIST))
LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%)

LOCAL_SHARED_LIBRARIES += FlameSteelCore
LOCAL_SHARED_LIBRARIES += FlameSteelBattleHorn
LOCAL_SHARED_LIBRARIES += FlameSteelCommonTraits
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkit
LOCAL_SHARED_LIBRARIES += FlameSteelEngineGameToolkitFSGL
LOCAL_SHARED_LIBRARIES += FSGL
LOCAL_SHARED_LIBRARIES += SDL2
LOCAL_SHARED_LIBRARIES += SDL2_image

LOCAL_LDFLAGS := -static-libstdc++
include $(BUILD_SHARED_LIBRARY)

Qualquer usuário experiente do CMake entenderá essa configuração desde as primeiras linhas, os formatos são muito semelhantes, o Android.mk não possui GLOB_RECURSIVE, então você deve procurar recursivamente os arquivos de origem usando a função walk.

Alteramos Application.mk, Android.mk para criar código C++ e não C:

APP_ABI := armeabi-v7a arm64-v8a x86 x86_64
APP_PLATFORM=android-16
APP_STL := c++_static
APP_CPPFLAGS := -fexceptions

Renomeie YourSourceHere.c -> YourSourceHere.cpp, faça grep nas entradas, altere o caminho na montagem, por exemplo:

app/jni/src/Android.mk:LOCAL_SRC_FILES := YourSourceHere.cpp

Em seguida, tente construir o projeto, se você encontrar erros do compilador sobre a ausência de cabeçalhos, verifique a exatidão dos caminhos em Android.mk; Se houver erros do vinculador como “referência indefinida”, verifique se os arquivos de código-fonte nos assemblies estão especificados corretamente. As listas podem ser rastreadas especificando $(info $(FILE_LIST)) no arquivo Android.mk; Não se esqueça do mecanismo de ligação dupla, usando módulos na chave LOCAL_SHARED_LIBRARIES e ligação correta através de LD, por exemplo para FSGL:

LOCAL_LDLIBS := -lEGL -lGLESv2

Adaptação e lançamento

Tive que mudar algumas coisas, por exemplo, remover o GLEW das compilações para iOS e Android, renomear algumas das chamadas OpenGL, adicionar o postfix EOS (glGenVertexArrays -> glGenVertexArraysOES), incluir uma macro para as funções de depuração modernas ausentes , a cereja do bolo é a inclusão implícita de cabeçalhos GLES2 indicando macro GL_GLEXT_PROTOTYPES 1:

#define GL_GLEXT_PROTOTYPES 1
#include "SDL_opengles2.h"

Também observei uma tela preta nas primeiras inicializações com um erro do tipo “E/libEGL: validar_display:255 erro 3008 (EGL_BAD_DISPLAY)”, alterei a inicialização da janela SDL, o perfil OpenGL e tudo funcionou:

SDL_DisplayMode mode;
SDL_GetDisplayMode(0,0,&mode);
int width = mode.w;
int height = mode.h;

window = SDL_CreateWindow(
            title,
            0,
            0,
            width,
            height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE
        );

SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES );

No emulador, o aplicativo é instalado por padrão com o ícone SDL e o nome “Jogo”.

Só tenho que explorar a possibilidade de gerar automaticamente arquivos assembly baseados no CMake, ou migrar assemblies de todas as plataformas para Gradle; no entanto, o CMake continua sendo a escolha de fato para o desenvolvimento contínuo de C++.

Código fonte

https://gitlab.com/demensdeum/android- sdl-test-app
https://gitlab.com/demensdeum/android-sdl-test-app/tree/master/cube-art-project-android

Fontes

https://developer.android.com/ ndk/guides/cpp-support.html
https://developer.android.com/ndk/guides/application_mk
https://developer.android.com/ndk/guides/android_mk
https://lazyfoo.net/tutorials/SDL/52_hello_mobile/android_windows/index.php
https://medium.com/androiddevelopers/getting-started-with-c-and-android-native-activities-2213b402ffff

Biblioteca Compartilhada CMake C++

Recentemente, decidi separar todas as partes do FlameSteelFramework em bibliotecas compartilhadas. Depois, mostrarei um exemplo de um arquivo CMakeLists.txt para FlameSteelCore:

cmake_minimum_required(VERSION 3.5)

project (FlameSteelCore)
set(CMAKE_BUILD_TYPE Release)

include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src)

file(GLOB_RECURSE SOURCE_FILES
    "src/FlameSteelCore/*.cpp"
)

add_library(FlameSteelCore SHARED ${SOURCE_FILES})

install(DIRECTORY "${CMAKE_SOURCE_DIR}/src/FlameSteelCore"
        DESTINATION include/FlameSteelFramework
        FILES_MATCHING
        PATTERN "*.h"
)

install(TARGETS FlameSteelCore DESTINATION lib)

Comandos que o CMake executa: coleta todos os arquivos com extensão *.cpp do diretório src/FlameSteelCore/ em uma biblioteca compartilhada, copia todos os cabeçalhos com extensão *.h de src/FlameSteelCore para include/FlameSteelFramework (no meu caso este é /usr/local/include/FlameSteelFramework), copia a lib compartilhada para o diretório lib (/usr/local/lib)
Após a instalação, pode ser necessário atualizar o cache LD – sudoldconfig.
Para compilar e instalar no Ubuntu (se você tiver o conjunto de ferramentas de compilação correto), basta executar os seguintes comandos:

cmake . && make && sudo make install

Para testar o processo de instalação, passo make prefix para a pasta local makeInstallTestPlayground:

cmake -DCMAKE_INSTALL_PREFIX:PATH=/home/demensdeum/makeInstallTestPlayground . && make && make install

Referências

https: //stackoverflow.com/questions/17511496/how-to-create-a-shared-library-with-cmake
https://stackoverflow.com/questions/6003374/what-is-cmake-equivalent-of-configure-prefix-dir-make-all-install