Article technique

Compiler un framework iOS Kotlin Multiplatform sur un Mac cloud

Compiler un framework iOS Kotlin Multiplatform sur un Mac cloud

Le fait qu’un dépôt Kotlin Multiplatform compile en local ne garantit pas le même résultat après son transfert sur un Mac cloud. Les écarts les plus fréquents ne viennent pas du code métier, mais de la combinaison de Java, du Gradle Wrapper, de Kotlin/Native, de CocoaPods et de Xcode, ainsi que d’une confusion entre les artefacts destinés aux appareils physiques et ceux du simulateur. Pour fiabiliser la compilation, commencez par figer la chaîne d’outils, puis validez séparément le module partagé, le Framework et, enfin, le workspace Xcode.

Figer d’abord les entrées de compilation

Lors de la première connexion au nœud, ne lancez pas immédiatement le pipeline complet. Commencez par relever les outils réellement utilisés pour la compilation, au lieu de vous fier uniquement aux versions recommandées dans la documentation de l’équipe.

sw_vers
uname -m
xcodebuild -version
xcode-select -p
java -version
./gradlew --version
bundle exec pod --version

Le dépôt doit inclure gradlew, gradle/wrapper/gradle-wrapper.properties, les fichiers de verrouillage des dépendances, Podfile.lock et le fichier de verrouillage des dépendances Ruby. Dans la CI, utilisez ./gradlew plutôt qu’une installation globale de Gradle présente par hasard sur la machine. De même, exécutez CocoaPods avec bundle exec pod afin d’éviter que différents jobs ne chargent des versions distinctes des gems.

S’il faut changer de version de Xcode, définissez-la explicitement au début du job, puis vérifiez-la immédiatement :

sudo xcode-select -s /Applications/Xcode.app
xcodebuild -version

Le fait qu’un outil soit « déjà installé » ne suffit pas à garantir la reproductibilité. Celle-ci exige que sa version, son chemin, ses fichiers de verrouillage et son point d’entrée soient tous vérifiables dans les journaux.

Séparer les cibles pour appareil et simulateur

Sur un Mac Apple Silicon, le simulateur iOS utilise généralement lui aussi l’architecture arm64, mais il ne cible pas la même plateforme qu’un iPhone physique. Se limiter à vérifier l’architecture du processeur peut donc provoquer, à l’étape de l’édition de liens, des erreurs difficiles à interpréter.

Le module partagé peut conserver deux cibles explicites :

kotlin {
    iosArm64()
    iosSimulatorArm64()

    targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget>()
        .configureEach {
            binaries.framework {
                baseName = "SharedKit"
                isStatic = true
            }
        }
}

Compilez d’abord le module partagé séparément afin de raccourcir le chemin de diagnostic :

./gradlew :shared:linkDebugFrameworkIosSimulatorArm64 \
  --no-daemon --stacktrace

./gradlew :shared:linkReleaseFrameworkIosArm64 \
  --no-daemon --stacktrace

Attendez que ces deux tâches réussissent avant de passer aux couches CocoaPods et Xcode. Si l’une d’elles n’existe pas, vérifiez le chemin du module, la déclaration de la cible et le nom du Framework. Ne contournez pas un problème de configuration en copiant un artefact déjà produit.

Faire consommer uniquement des résultats verrouillés à CocoaPods et Xcode

La CI ne doit pas mettre les Pods à jour à chaque compilation. Commencez par générer ou vérifier le podspec, puis installez les dépendances en respectant strictement Podfile.lock :

./gradlew :shared:podspec
bundle check
bundle exec pod install --project-directory=iosApp

Compilez ensuite le workspace, et non le fichier de projet :

xcodebuild \
  -workspace iosApp/App.xcworkspace \
  -scheme App \
  -configuration Debug \
  -sdk iphonesimulator \
  -destination 'generic/platform=iOS Simulator' \
  -derivedDataPath "$PWD/.build/DerivedData" \
  CODE_SIGNING_ALLOWED=NO \
  build

CODE_SIGNING_ALLOWED=NO convient à la validation d’une compilation pour simulateur, mais ne remplace pas la configuration de signature nécessaire à l’archivage pour un appareil physique. Si le projet utilise des configurations personnalisées, vérifiez également que les fichiers .xcconfig correspondants ne définissent pas les chemins de recherche des Frameworks avec des chemins absolus locaux.

Réduire le périmètre du problème avec des contrôles par couche

Couche contrôlée Commande recommandée Vérifications prioritaires en cas d’échec
Code source Kotlin ./gradlew :shared:compileKotlinIosSimulatorArm64 expect/actual, visibilité des dépendances
Édition de liens du Framework linkDebugFrameworkIosSimulatorArm64 plateforme cible, dépendances natives
Intégration des Pods bundle exec pod install fichiers de verrouillage, environnement Ruby, podspec
Compilation Xcode xcodebuild -workspace Scheme, chemins de recherche, configuration de compilation

Mettre les téléchargements en cache sans partager un workspace inscriptible

Il est généralement utile de mettre ~/.gradle/caches et ~/.konan en cache, car leur téléchargement est coûteux. En revanche, Pods et DerivedData exigent davantage de précautions. Si plusieurs jobs concurrents partagent un répertoire DerivedData inscriptible, l’un d’eux peut lire les fichiers intermédiaires d’une autre branche. La méthode la plus sûre consiste à créer un répertoire distinct pour chaque job, puis à le supprimer une fois celui-ci terminé.

La clé de cache doit au minimum inclure :

  • les empreintes de gradle-wrapper.properties et des fichiers de verrouillage des dépendances
  • les empreintes de libs.versions.toml, de Podfile.lock et du fichier de verrouillage des dépendances Ruby
  • la sortie de xcodebuild -version
  • la version majeure de Java et la version du plugin Kotlin
  • la configuration cible, Debug ou Release

Ne mettez pas tout le répertoire utilisateur dans un cache de longue durée. Un tel cache est non seulement difficile à invalider, mais il risque aussi de contenir des identifiants, des journaux et des états sans rapport avec la compilation. Après la restauration du cache, lancez une compilation légère. En cas d’échec, supprimez d’abord le cache du job en cours au lieu d’effacer directement tous les répertoires de développement du nœud.

Diagnostiquer selon la forme de l’erreur et conserver les preuves

Si le message building for iOS Simulator, but linking in object file built for iOS apparaît, vérifiez en priorité la cible ayant produit le Framework au lieu de réinstaller CocoaPods. En cas de message framework not found, contrôlez les chemins de recherche développés dans Build Settings, l’emplacement réel du Framework et l’exécution de la phase de script avant la compilation.

Vous pouvez enregistrer les principales informations de diagnostic comme artefacts du job :

xcodebuild -showBuildSettings \
  -workspace iosApp/App.xcworkspace \
  -scheme App > .build/build-settings.txt

find shared -type d -name '*.framework' -print \
  > .build/framework-inventory.txt

Si la compilation réussit en local mais échoue à distance, comparez ensuite xcodebuild -version, java -version, les empreintes des fichiers de verrouillage, les variables d’environnement et l’heure de génération du workspace. Ne commencez pas par vider tous les caches : vous effaceriez les éléments de diagnostic et feriez peut-être disparaître temporairement le problème sans pouvoir l’expliquer.

Contrôles minimaux avant fusion

Toute demande de fusion doit au minimum valider la compilation du module partagé pour le simulateur, la compilation du workspace Xcode et l’édition de liens du Framework Release pour appareil physique. Les jobs concurrents doivent utiliser des répertoires DerivedData distincts, les journaux doivent conserver les versions des outils ainsi que la commande complète ayant échoué, et les artefacts doivent être nommés selon leur plateforme. Avec ces mesures, les responsabilités des couches Kotlin, Pods et Xcode deviennent suffisamment claires pour localiser la plupart des pannes au cours d’un seul pipeline.

Questions fréquentes

Un framework arm64 pour appareil fonctionne-t-il dans un simulateur Apple Silicon ?

Non. L’architecture processeur peut être identique, mais la plateforme cible diffère. Il faut compiler séparément iosArm64 et iosSimulatorArm64.

Quels caches conserver pour une compilation Kotlin Multiplatform ?

Conservez en priorité les téléchargements Gradle et la chaîne Kotlin/Native. La clé doit inclure les fichiers de verrouillage ainsi que les versions de Gradle, Xcode et Java.

Nœud physique dédié

Déployez vos tâches de build sur un Mac dédié dans le cloud

Choisissez VMOrbit M4 ou VMOrbit M4 Pro, puis sélectionnez un nœud en fonction de l’emplacement de votre équipe. La disponibilité et les informations de livraison affichées dans la console font foi en temps réel.

Choisir un Mac dans le cloud