Build automatique et distribution OTA avec Xcode 4.x et Jenkins

3,056 views

Published on

Build automatique et distribution OTA avec Xcode 4.x et Jenkins de Jacque Foucry

Published in: Technology
0 Comments
1 Like
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
3,056
On SlideShare
0
From Embeds
0
Number of Embeds
953
Actions
Shares
0
Downloads
23
Comments
0
Likes
1
Embeds 0
No embeds

No notes for slide
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Build automatique et distribution OTA avec Xcode 4.x et Jenkins

    1. 1. Build automatique etdistribution OTA avec Xcode 4.x et Jenkins
    2. 2. Pour quoi faire ?Développer depuis une autre machineTester sur iDeviceFournir une version récente aux testeurs
    3. 3. JavaJenkins a besoin de java pour tournerJava n’est plus fourni par Apple pour OSX 10.7☞ Il faut installer Java
    4. 4. Installation de Javacurl -O http://supportdownload.apple.com/download.info.apple.com/Apple_Support_Area/Apple_Software_Updates/Mac_OS_X/downloads/041-1941.20111108.6wtg7/JavaForMacOSX10.7.dmghdiutil attach JavaForMacOSX10.7.dmgsudo installer -pkg /Volumes/Java For Mac OS X10.7/JavaForMacOSX10.7.pkg -target /
    5. 5. Installation de JenkinsTéléchargement et installation du dernier paquetsur la machine ciblecurl -O http://jenkins.mirror.isppower.de/osx/jenkins-1.451.pkgsudo installer -pkg jenkins-1.451.pkg -target /
    6. 6. Création d’un utilisateursudo dscl . create /Users/jenkinssudo dscl . create /Users/jenkins PrimaryGroupID 1sudo dscl . create /Users/jenkins UniqueID 300sudo dscl . create /Users/jenkins UserShell /bin/bashsudo dscl . create /Users/jenkins home /Users/Shared/Jenkins/Home/sudo dscl . create /Users/jenkinsNFSHomeDirectory /Users/Shared/Jenkins/Home/sudo dscl . passwd /Users/jenkins
    7. 7. Modification du fichierde configuration/Library/LaunchDaemons/org.jenkins-ci.plist ☞ Changer <UserName> <daemon> par<UserName> <jenkins>Changement de propriétaire pour le home de Jenkins☞ sudo chown -R jenkins /Users/Shared/Jenkins
    8. 8. Lancement de JenkinsJenkins est déjà lancé, il faut l’arrêter ☞ sudo launchctl unload -w /Library/ LaunchDaemons/org.jenkins-ci.plistPuis le relancer ☞ sudo launchctl load -w /Library/ LaunchDaemons/org.jenkins-ci.plist
    9. 9. VérificationDans /var/log/system.log ☞ Feb 27 11:53:32 macmini org.jenkins- ci[37647]: INFO: Jenkins is fully up and running
    10. 10. Connexionhttp://localhost:8080
    11. 11. JenkinsJ’ai réussi à faire un job de compilation pour unprojet purement OSXJ’ai réussi à faire un job de compilation d’unprojet iOS pour simulateur
    12. 12. JenkinsLa compilation d’un projet iPhoneOS...Le processus de la JVM est lancé par launchdet est fils direct de launchdl’ouverture de la keychain utilisateur n’est pascompatible avec cette approcheMême en mettant les certificats et clefs privésdans la keychain system
    13. 13. Conclusion (provisoire)Build automatique etdistribution OTA avecXcode 4.x et Jenkins
    14. 14. Conclusion (provisoire)Build automatique etdistribution OTA avecXcode 4.x et Python
    15. 15. Utiliser l’existantPuisque nous avons créé un utilisateur jenkinspour le serveur de build Jenkins, autantutiliser ce compte pour la suite de nosaventures
    16. 16. PythonPourquoi Python ?Pour apprendre
    17. 17. Schéma Jenkins Serveur Git Échange de clef SSH Serveur de build Serveur de distributionLa clef SSH de jenkins sur la machine de build ne comporte pas de passphrase.Sur le serveur de distribution jenkins n’a AUCUN rôle administratif.Il est aussi possible de n’autoriser les connexions sur le serveur de distributionque par échange de clef (authentification par mot de passe interdite)
    18. 18. Schéma 1 2Jenkins 3 Serveur GitServeur 4de build Serveur de distribution 1.récupération des sources (ou mises à jour) depuis le dépôt 2.compilation 3.préparation du .ipa, du manifest, du html 4.copie sur le serveur et mise à disposition
    19. 19. Étudions tout cela Je débute en Python, il y a surement plus simple ou plus «Pythonnesque» de faire ce script. En particulier, je n’utilise quasiment pas les caractéristiques Objet du langageNous n’allons pas étudier le script dans l’ordre d’entréeen action des étapes 1,2,3,4. Nous laisserons 1 et 4pour fignoler notre script
    20. 20. Étape 2 : la compilationLa compilation, c’est le boulot de xcodebuild :Il a besoin de trouver : le répertoire .xcodeproj la target le SDK la configuration (Debug, Release, AdHoc...)
    21. 21. Appel de xcodebuild# /usr/bin/xcodebuild -sdk iphone5.0 -project /mon/projet.xcodeproj -configuration release -target MaTarget Fastoche non !
    22. 22. Sauf que...xcodebuild a besoin de trouver le certificat dedéveloppement signé par Apple et la clef privée de cecertificatCes deux éléments doivent être placés dans unekeychainJ’ai décidé de mettre cela dans une keychain séparéeLe provisioning profile doit être dans ~/Library/MobileDevices/Provisioning Profiles
    23. 23. Sur la machine de devExport du certificat et de la clef privée dansun fichier .p12Copie de ce fichier sur la machine de buildCopie du Provisioning Profile sur la machinede build
    24. 24. Sur la machine de buildPour créer une keychain à la ligne de commandeon utilise securitysecurity create-keychain jenkins.keychainOn donne un mot de passe à cette keychainImport du fichier .p12 venant de la machine de devsecurity import -t cert -f pkcs12 -T /usr/bin/codesign-T /usr/bin/xcodebuild -P MotDePasse
    25. 25. SecurityIl y a plein d’options amusantes dans security,je vous laisse la surprise
    26. 26. Retour au scriptPour faire marcher xcodebuild, nous allonsavoir besoin : d’ouvrir la keychain de la passer keychain par défaut
    27. 27. Security (bis) C’est toujours avec security que nous allons jouer security unlock-keychain jenkins.keychain -p MotDePasse security default-keychain -d user -s jenkins.keychain J’ai mis cela dans une fonctionSoyez conscient que le mot de passe peut êtreintercepté par un simple ps au moment opportun
    28. 28. Modèle de fonctionToutes les fonctions sont bâties sur le mêmemodèle afin de pouvoir tracer facilement cequi se passeC’est mon côté sysadmin qui cause...
    29. 29. openKeychaindef openKeychain(password, keychain): cmd_array=(["/usr/bin/security","unlock-keychain", "-p", password, keychain]) try: subprocess.check_call(cmd_array) except OSError as e: logger.debug("Error in %s, exiting" % " ".join(cmd_string)) logger.info( "ErrorString == %s ErrorNum == %d" % (e.strerror,e.errno) writeToSTDERR("Error, please see the log file") sys.exit(1) cmd_array=(["/usr/bin/security", "default-keychain","-d","user", "-s", keychain]) try: subprocess.check_call(cmd_array) except OSError as e: logger.debug("Error in %s, exiting" % " ".join(cmd_array)) logger.info( "ErrorString == %s ErrorNum == %d" % (e.strerror,e.errno) En python l’indentation fait partie de la syntaxe
    30. 30. Une fonction pour xcodebuildSur le modèle de la précédente nous pouvonsfaire une fonction qui appelle xcodebuild
    31. 31. compileAppdef compileApp(SDK, project, configuration, target):! cmd_array=(["/usr/bin/xcodebuild","-sdk",SDK,"-project" ,project, "configuration",configuration, "-target", target])! try:! ! subprocess.check_call(cmd_array)! except OSError as e:! ! logger.debug("Error in %s. Exiting" % " ".join(cmd_array)) logger.info( "ErrorString == %s ErrorNum == %d" % (e.strerror,e.errno)! ! writeToSTDERR("Error, please see the log file")! ! sys.exit(1)
    32. 32. Transformation de .app en .ipaL’application générée par xcodebuild n’estpas exploitable sur un iPhoneIl faut la transformer en .ipaC’est xcrun avec les bons paramètres qui vafaire cette transformation(PackageApplication)
    33. 33. createIPAdef createIPA(GSDK, workspace, configuration, target, DeveloperName, ProvisioningProfile,targetFolder): cmd_array=(["/usr/bin/xcrun","-sdk", GSDK, "PackageApplication", "-v"," %s/%s.app" % (APPPath,target),"-o","%s/%s.ipa"%(targetFolder,target), "-sign", DeveloperName, "-embed",ProvisioningProfile]) try: subprocess.check_call(cmd_array) except OSError as e: logger.debug("Error in %s. Exiting" % " ".join(cmd_array)) logger.info(" Error String == %s Error Num == %d" % (e.strerror, e.errno)) writeToSTDERR("Error, please see the log file") sys.exit(1) SDK c’est le nom du SDK nécessaire à la compilation, GSDK c’est la même chose sans le numéro de release (iphoneos5.0 -> iphoneos)
    34. 34. Manifest.plistLe fichier manifest.plist est nécessaire à ladistribution OTALes informations nécessaires sont déjà dansl’application .ipa, autant aller les chercher là.Les .ipa sont des fichiers zipLes informations sont dans le fichier Info.plistLe fichier Info.plist est une plist binaire
    35. 35. Manifest.plistNous n’allons pas détailler les fonctions maisretrieveInfo extrait le fichier Info.plist del’application .ipacreateManifest transforme ce Info.plist binaireen plist texte, récolte les informationsnécessaires (CFBundleName,CFBundleVersion, CFBundleIdentifier) etgénère le fichier manifest grâce au moduleplistlib
    36. 36. createIndexHTML & fillHTML Ces deux fonctions permettent de créer le fichier index.html qui va accompagner vos fichiers manifest.plist et MonApplication.ipa pour la distribution OTACe code et celui de la génération du manifest sont largement inspirés voire presquetotalement copiés de celui-ci <https://github.com/baz/ios-build-scripts/blob/master/generate_manifest.py>.
    37. 37. La suite Votre utilisateur jenkins va faire tourner le script sur la machine de build Déposez sur le serveur de distribution la clef publique de cet utilisateur afin de permettre l’envoi des élémentsPour faciliter l’échange, la clef de l’utilisateur n’a pas de mot de passe. En revanche,sur la machine de distribution, il n’a aucun pouvoir. Pas de sudoers entre autre
    38. 38. Récupération des sourcesPython dispose d’un module de gestion gitIl vous permet de faire le clone initial etensuite à chaque lancement de faire une miseà jour des sources
    39. 39. Et ensuite ?Les méthodes d’échange et de gestion de gitsont présentes dans le script finalDans ce même script, grâce au moduleargparse, nous pouvons gérer les argumentspassés au script
    40. 40. xcodebuild-wrapper.py./xcodebuild-wrapper.py -husage: xcodebuild-wrapper.py [-h] -k KEYCHAIN -K KEYCHAINPASSWORD -p PROJECT -P PROVISIONINGPROFILE -s SDK -c CONFIG -n DEVNAME -t TARGET -d DEPLOY [-r REMOTEHOST] [-u USERNAME] [-w REMOTEPASSWORD] [-f REMOTEFOLDER] [--log LOGLEVEL]xcodebuild wrapper parametersoptional arguments: -h, --help show this help message and exit -k KEYCHAIN, --keychain KEYCHAIN Path to the keychain file -K KEYCHAINPASSWORD, --keychainPassword KEYCHAINPASSWORD keychains password -p PROJECT, --projectPath PROJECT Path to project file -P PROVISIONPROFILE, --provisioningProfile PROVISIONPROFILE Provisioning Profile path -s SDK, --sdk SDK SDK -c CONFIG, --configuration CONFIG Configuration -n DEVNAME, --developerName DEVNAME Developer name. This information is in the provisioning profile -t TARGET, --target TARGET Path to projets file -d DEPLOY, --deploymentAddress DEPLOY Deployment Address, used in manifest.plist file -r REMOTEHOST, --remoteHost REMOTEHOST Remote host, used to distribute IPA -u USERNAME, --username USERNAME Username on the remote host -w REMOTEPASSWORD, --remotePassword REMOTEPASSWORD Password of the username on the remote host -f REMOTEFOLDER, --remoteFolder REMOTEFOLDER Destination folder on remote host --log LOGLEVEL LogLevel, could be DEBUG | INFO | WARNING | ERROR | CRITICAL. Default value is INFO
    41. 41. AméliorationsPour éviter de passer le mot de passe de lakeychain, le script pourrait utiliser un fichier deparamètresLancer le script automatiquementEnvoi de mail aux testeurs pour signaler unenouvelle version…
    42. 42. One more thing...
    43. 43. QuestionComment connaître la version installée sur leiDevice d’un testeur ?Avec le viewController info/about
    44. 44. QuestionEt la version du build ?J’ai trouvé une solution à cette URL et je l’aiadaptée dans mon scripthttp://blog.jayway.com/2011/05/31/auto-incrementing-build-numbers-in-xcode/
    45. 45. Info.plistAjoutons dans le ficher MonApplication-Info.plist une entrée CWBuildNumberIncrémentons cette valeur au moment desbuild OTA (dans le script Python)Lisons cette information au moment del’affichage du viewController info/about
    46. 46. Le code Objective-Cint CWBuildNumber = [[[NSBundle mainBundle]objectForInfoDictionaryKey:@"CWBuildNumber"] intValue];NSString *CFBundleVersion = [[NSBundle mainBundle]objectForInfoDictionaryKey:@"CFBundleVersion"];NSString *CFBundleName = [[NSBundle mainBundle]objectForInfoDictionaryKey:@"CFBundleName"];NSString *aboutString = [NSString stringWithFormat:@"%@, version %@, (Build Number%d)", CFBundleName, CFBundleVersion, CWBuildNumber];aboutLibelle.text = aboutString;
    47. 47. L’ajout de la clef dans MyAPP-info.plist/usr/libexec/PlistBuddy -c "Set :CWBuildNumber $buildNumber" ${PROJECT_DIR}/MyApp-Info.plist
    48. 48. La fonction dans xcodebuild-wrapper.pydef increaseBuildNumber(project,subdir,plistFile):! cmd_array=["/usr/libexec/PlistBuddy", "-c", "Print CWBuildNumber", "%s/%s/%s" %(project,subdir,plistFile)]! s = subprocess.Popen(cmd_array, stdout=subprocess.PIPE)! ret = s.stdout.readline()! buildNumber = int(ret)! buildNumber+=1! cmd_array=["/usr/libexec/PlistBuddy", "-c", "Set :CWBuildNumber %s" %(buildNumber),"%s/%s/%s" % (project,subdir,plistFile)]! try:! ! subprocess.check_call(cmd_array)! except OSError as e:! ! logger.debug("Error in %s. Exiting"% " ".join(cmd_array))! ! logger.debug("ErrorString == %s ErrorNum == %d" % (e.strerror,e.errno))! ! writeToSTDERR("Error during increase BuildNumber")
    49. 49. ConclusionJ’ai mis deux semaines à écrire ce scriptIl est dispo avec beaucoup plus d’explicationssur http://adminblog.foucry.netMerci à Frank, Dominique et Benoît pour leuraide et leur relecture
    50. 50. Le script est en creative commonset il sera sur githubadminblog.foucry.netjacques@foucry.net

    ×