[Xcode] Intégration continue – gestion multiple environnements

lundi 18 janvier 2016

Cet article a pour but de vous aidez à gérer plusieurs environnements différents pour une même application ce qui vous facilite grandement la vie et peut être intégré pour une solution d’intégration continue de votre application.
En effet, il est souvent utile quand on développe des applications mobile d’avoir recours à des webservices par exemple, et ces webservices ont des URL qui peuvent varier en fonction de leur environnement (PROD,PREPROD,QA,DEV,etc…). De plus, il est possible aussi d’avoir d’autres variables qui changent en fonction de ces environnements. Par exemple des configs du build settings / build phases différents en prod ou preprod, même des compilations différentes.

Vous trouverez ci-dessous les différentes étapes que j’ai réalisé pour ma solution.

  • Première étape: création de nouvelles configurations pour chaque environnements:
    Pour ma part, je créé souvent une configuration de type QA pour les clients qui veulent tester l’application en mode adhoc et valider l’ensemble des fonctionnalités. Par défaut, Xcode nous propose deux configurations Release / Debug. Pour ma part « release » est la configuration pour mon application final (ad hoc / appstore), debug est ma version de dev, toujours utiliser avec Xcode. Donc pour ma part j’ai besoin de rajouter une autre configuration QA. D’abord on duplique la configuration « release », via clique droit de la configuration dans Project Navigator, on clique sur notre projet actuel, sous la partie Configurations, on sélectionne Release et on fait un clique droit duplicate … config_multiple_env_step1_duplicate_configurationOn le renomme en QA.
  • Deuxième étape: modification du scheme qui s’occupe de la liaison entre les configurations et notre projet. config_multiple_env_step2_manage_scheme On duplique le « scheme » actuel. config_multiple_env_step3_duplicate_scheme On renomme le scheme config_multiple_env_step4_renamme_scheme On change la configuration du scheme que l’on vient de créer en y associant la configuration dans onglet archive. config_multiple_env_step5_change_config_for_scheme Et on oublie pas de rendre le nouveau scheme en mode partagé pour pouvoir l’utiliser en dehors de Xcode via le terminal et la commande xcodebuild. Ce qui permettra de générer automatiquement nos archives pour chaque configuration via un script shell décrit plus tard dans l’article. Ce qui permettra de faire de l’intégration continue et par exemple l’automatiser au travers de jenkins par exemple après chaque commit sur une branche. config_multiple_env_step6_shared_scheme_for_developpers_or_xcodebuild_command
  • Troisième étape: création de fichier plist de configuration pour définir leurs valeurs dans chaque environnements. Création d’un folder « Config » dans votre projet et de sous dossier pour chaque configuration: config_multiple_env_step7_create_folder_config Ensuite on ajoute un fichier configuration.plist dans chaque sous dossier config_multiple_env_step8_create_config_plist_file config_multiple_env_step9_desactive_config_file_target Ensuite dans ces fichiers plist, vous définissez vos variables qui varie en fonction de chaque configuration et y associé une valeur. Par exemple, base_url comme variable et vous mettez les bons liens vers votre API de dev/Qa/PROD. config_multiple_env_step12_add_environnement_variable_in_each_config_file
  • Quatrième étape: ajout d’un script shell.
    Sélectionnez votre target de votre projet, ensuite sélectionnez « Build Phases » et appuyer sur le petit + en bas pour ajouter « Run Script Build Phase ». config_multiple_env_step10_new_run_script_build_phase Qui se chargera de déplacer le bon fichier plist de configuration en fonction du scheme/configuration choisi lors de l’archive de l’application. config_multiple_env_step11_script_copy_config_in_good_folder Voici le code à insérer:
    RESOURCE_PATH=${SRCROOT}/${PRODUCT_NAME}/config/${CONFIGURATION}
    BUILD_APP_DIR=${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app
    echo "Copying all files under ${RESOURCE_PATH} to ${BUILD_APP_DIR}"
    cp -v "${RESOURCE_PATH}/"* "${BUILD_APP_DIR}/"
    
  • Cinquième étape: rajouter la fonction suivante pour avoir accès aux variables dans votre programme:
    - (NSString *) readValueFromConfigurationFile {
      NSBundle *bundle = [NSBundle mainBundle];
      NSString *path = [bundle pathForResource:@"Configuration" ofType:@"plist"];
      NSDictionary *config = [NSDictionary dictionaryWithContentsOfFile:path];
      return config[@"base_url"];
    }
    
    Le tour est joué vous aurez dans votre code accès à la bonne valeur du paramètre base_url en fonction de la configuration que vous aurez choisie lors de la réalisation de l’archive.
  • Sixième étape: création d’un script shell qui permettra d’automatiser la génération de vos fichier ipa pour déploiement adhoc.
    Et pour créer une intégration continue via jenkins par exemple.
    POur se faire, j’ai décidé de créer un fichier build.sh dans le dossier Config qui se charge de générer archive/ipa/ fichier plist pour le déploiement Ad hoc. config_multiple_env_step14_add_build_sh_to_build_archive_ipa Voici un exemple de mon fichier:
    # Archive
    PROJECTPATH=/Users/uchrony/Documents/_WORK/PROJECTS/POC/MultipleEnv/MultipleEnvironnement
    PROJECT=MultipleEnvironnement
    SCHEME=QA
    ARCHIVEPATH=/Users/uchrony/Documents/_WORK/PROJECTS/POC/MultipleEnv/MultipleEnvironnement/MultipleEnvironnement/Config/QA
    IPA_NAME=${PROJECT}
    PROJECT_BUILDDIR=${ARCHIVEPATH}/${IPA_NAME}.xcarchive/Products/Applications
    CONFIGPATH="/Users/uchrony/Documents/_WORK/PROJECTS/POC/MultipleEnv/MultipleEnvironnement/MultipleEnvironnement/Config"
    HOST_LOCATION="/Users/uchrony/Documents/website/IOS/app.uchrony.net/Multi"
    
    # compile project
    echo Building Project
    xcodebuild -scheme ${SCHEME} -archivePath ${ARCHIVEPATH}/${PROJECT}.xcarchive clean archive -workspace "${PROJECTPATH}/${PROJECT}.xcworkspace"
    #Check if build succeeded
    if [ $? != 0 ]
    then
      exit 1
    fi
    echo Generate ipa
    xcrun -sdk iphoneos PackageApplication -v ${ARCHIVEPATH}/${PROJECT}.xcarchive/Products/Applications/*.app -o ${ARCHIVEPATH}/${PROJECT}.ipa
    
    #Get the version from the Info.plist file
    APP_PATH="${ARCHIVEPATH}/${PROJECT}.xcarchive/Products/Applications/${PROJECT}.app"
    VERSION=`defaults read ${APP_PATH}/Info CFBundleShortVersionString`
    BUNDLE_ID=`defaults read ${APP_PATH}/Info CFBundleIdentifier`
    #Possible de récupérer dans le code l'url de qa to download app ????
    
    
    # Create plist
    echo Create plist
    cat ${ARCHIVEPATH}/template.plist | sed -e "s/\${APP_NAME}/$IPA_NAME/" -e "s/\${BUNDLE_ID}/$BUNDLE_ID/" -e "s/\${BUNDLE_VERSION}/$VERSION/" -e "s/\${SCHEME}/${SCHEME}/" > ${ARCHIVEPATH}/${IPA_NAME}.plist
    
    echo copy plist and ipa to webserver
    cp "${ARCHIVEPATH}/${PROJECT}.ipa" "${HOST_LOCATION}/${PROJECT}.ipa"
    cp "${ARCHIVEPATH}/${PROJECT}.plist" "${HOST_LOCATION}/${PROJECT}.plist"
    
    
    Ce fichier génère tout d’abord le fichier archive pour générer le fichier ipa nécessaire pour un déploiement de l’application sur un smartphone ou tablette depuis une url (ad hoc). Il génère aussi le fichier plist qui doit être associé au ipa pour permettre au smartphone/tablette de l’installer. Enfin il se charge aussi de copier les deux fichiers et de les placer dans votre répertoire de votre site internet.
  • Sixième étape: gérer correctement les certificats / provisioning profile de votre applications en fonction de vos configurations. Pour ma part, maintenant je génère moi même les certificats et provisioning profile dans le portail d’Apple et non via Xcode. Ensuite pour le code signing je l’aisse tout en automatic ou IOS developper en mode debug et IOS Distribution pour mode Qa et Release. config_multiple_env_step15_add_provisionning_profile_for_each_config Enfin, pour les provisioning profiles, dans Xcode je sélectionnes correctement chaque provisioning profile défini dans le portail d’Apple et le tour est joué. Pas besoin de spécifier via la ligne de commande les noms des provisioning profile, il prendra ceux défini dans Xcode sous Build Settings en rapport avec la configuration.

Il peut aussi être très utile de générer des bundle identifier différent suivant la configuration, ce qui vous permettra par exemple de pouvoir installer plusieurs fois la même application sur un device mais de configuration différente. Par exemple qui interroge des webservices différents suivant la configuration.
Pour réaliser cela, il suffit de rajouter une variable qui aura des valeurs différentes en fonction de la configuration. Donc dans Build Settings de votre projet, vous faites Editor > Add build setting > Add User defined Setting.
Ensuite choisissez un nom de variable par exemple CustomAppBundleId et choisissez pour chaque configuration une valeur de type company.${PRODUCT_NAME:rfc1034identifier}.${CONFIGURATION}. Ensuite, il faut modifier le fichier xxxInfo.plist de l’application et mettre dans le champs « Bundle identifier » le nom de la variable ${CustomAppBundleId}. Et le tour est joué, vous pourrez avoir la même application en mode Qa et dev sur le même device. Et le plus beau dans tout ça c’est que le nom de l’application reste identique.

Attention:sur un projet, je suis tombé sur l’erreur suivant « Unable to download App »=> xxx could not be downloaded at this time. Le plus drôle c’est que cette erreur c’est le client qu’il l’obtenait avec un compte Entreprise et que sur tous mes ipads je n’ai eu aucun soucis à télécharger l’application in House!
Après mettre déplacé chez le client et regarder les logs avec l’outil Device de Xcode, je me suis rendu compte qu’il ne voulait pas mettre à jour le manifest de mon archive. Le client avait ioS 9.1 et chez moi, il était à jour ios 9.2. Après une recherche, je me suis rendu compte que le bundle identifier devait être scrupuleusement correct sous iOS9.1. En faite, mon bundle identifier changeait en fonction de la configuration (.Dev / .QA) et si je ne mettais pas le bon bundle identifier complet correctement sur certain iPAd je suppose sous iOS 9.1 , j’avais une erreur de téléchargement de l’application.

bundle_identifier_change

J’ai aussi trouvé une idée pour générer des icônes différentes pour chaque environnements mais pas encore tester:

config_multiple_env_step16_create_icon_by_configuration

je me suis inspiré de l’article suivant

Tags: configurations , intégration continue , jenkins , Xcode