Azure Devops iOS Pipeline

Arda Dinler
Ford Otosan
Published in
9 min readApr 17, 2023

Bir önceki yazımızda iOS Pipeline’nın koşacağı Mac agent’ın kurulumunun nasıl olacağını ve bunun Azure ile nasıl eşleştirildiğini aktarmıştık.

Bkz. https://medium.com/@adinler/733607969ddchttps://medium.com/ford-otosan/azure-devops-self-hosted-mac-agent-kurulum-733607969ddc

Bu yazımızda ise, onun devamı niteliğinde, yine Azure Devops üzerinde iOS Pipeline’nın kurulumunu aktaracağız. Aslında Azure üzerinde iOS Pipeline kurulumunu bulabileceğiniz birçok farklı kaynak var. Fakat bu yazının en önemli kısmı diğer kaynaklarda bulmaya çalıştığımız ve belki zorlandığımız kısımların üzerine gitmek. Aynı zamanda sorunların üzerinden nasıl gelindiğine bu bölüm değineceğiz.

Buradaki temel amacımız yml file’ının konfigürasyonun yapılması. Azure Pipelines için YAML şema kullanılmaktadır ve bunun içerisinde tasklar, stepler, scriptler için bash komutlar, variable bulunmaktadır. Bunlara detaylı bakacağız.

Tabi konfigürasyon yapılırken bazı temel işlerimiz olacak. Bunun başında apple distrubition sertifikası ve provizyon dosyasının oluşturulması ve eklenilmesi var.

Başlamadan önce örnek bir YAML file aşağıya bırakıyorum. Tabi ki detaylandıracağız.

# Contact Azure admin to add your variables to variable group
variables:
- group: MobileAppsVariableGroup

trigger:
- master

pool:
name: vmImage
demands: xcode

# Contact Azure admin to upload your p12 file to Azure Secure Files if needed
# Change "certSecureFile" field with p12 certificate name
- task: InstallAppleCertificate@2
displayName: 'iOS Apple certificate installation'
inputs:
certSecureFile: 'distribution.p12'
certPwd: '$(Cert.p12)'
keychain: 'temp'

# Contact Azure admin to upload your mobileprovision file to Azure Secure Files if needed
# Change "provProfileSecureFile" field with mobileprovision name
- task: InstallAppleProvisioningProfile@1
displayName: 'iOS Apple provisioning profile installation'
inputs:
provisioningProfileLocation: 'secureFiles'
provProfileSecureFile: 'adhoc.mobileprovision'

# Change xcWorkspacePath with project workspace path
# Change scheme with project scheme
# Change xcodeVersion if needed
- task: Xcode@5
displayName: 'iOS Xcode build'
inputs:
actions: 'clean build'
configuration: 'Release'
sdk: 'iphoneos'
xcWorkspacePath: 'ProjectName.xcworkspace'
scheme: 'ProjectNameSchema'
xcodeVersion: 'specifyPath'
xcodeDeveloperDir: '/Applications/Xcode_13.app'
packageApp: true
signingOption: 'manual'
signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)'
provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'

- task: CopyFiles@2
displayName: 'iOS copy ipa file to upload'
inputs:
SourceFolder: '$(system.defaultworkingdirectory)'
Contents: '**/*.ipa'
TargetFolder: '$(build.artifactStagingDirectory)'
OverWrite: true
flattenFolders: true
condition: succeededOrFailed()

- task: PublishBuildArtifacts@1
displayName: 'iOS publish ipa to artifacts'
inputs:
PathtoPublish: '$(build.artifactStagingDirectory)'
condition: succeededOrFailed()

- task: AppCenterDistribute@3
displayName: 'iOS AppCenter distribution'
inputs:
serverEndpoint: 'AzureDevOps'
appSlug: 'AppSlupFromAppCenter'
appFile: '$(build.ArtifactStagingDirectory)/*.ipa'
symbolsIncludeParentDirectory: false
releaseNotesOption: 'input'
releaseNotesInput: '$(Build.SourceVersionMessage)'

Hadi başlayalım!

Ön Gereksinimler

Şimdi adım adım başlayalım. İlk önce ön gereksinimlerimize göre ilk olarak developer account kısmında sertifika ve provizyon dosyalarını oluşturmak ile işe başlayalım.

Apple Certificate

Distrubition sertifikası oluşturmak için aşağıdaki adımları sırası ile takip edelim.

Mac -> Keychain Access App -> Certificate Assistant -> Request a Certificate from Certificate Authority…
Sadece mail adresi girmeniz yeterli (User Email Address kısmına)
Bulabileceğiniz herhangi bir yere kaydedebilirsiniz. Resim-1

Şimdi developer account giriş yapıp create a new certificate kısmından sertifikamızı oluşturalım.

Bu adımda kaydermiş olduğunuz Resim-1'deki kısımdaki file’ı seçiniz.

Download diyerek “distribution.cer” olarak kaydedebiliriz. Ardından çift tıklayarak bu sertifikanın “Keychain Access” uygulamasına atılmasını sağlayalım.

Daha sonra yukarıdaki gibi ilk “Keychain Access” açıp -> Sağ Click yapıp ilgili sertifikamızın export edilmesini sağlayalım.

Not: Buradaki önemli adım hem sertifikanın kendisinin hem de şifresinin birlikte export edilmesi.

Apple Provisioning Profile

Biz bu yazımızda uygulamanın app center üzerinden ad hoc provizyonu ile dağıtımını sağlayacağız. Dolayısıyla da aşağıdaki gibi “Distrubition” adımından “Ad Hoc” kısmını seçiyoruz.

Continue diyerek devam ediyoruz.

App ID seçiyoruz.
Dağıtım sertifikamızı seçiyoruz.
Test etmek istenilen cihazları ekleyebilirsiniz.
İsim verip generate ediyoruz.
Download ediyoruz.

İlk ön gereksinimimiz done.

Azure DevOps Pipeline için Sertifika ve Provizyonun Konfigürasyonu

Organizasyon içindeki projenizin linkine gidip sol menüden variable group alanına tıklayabilirsiniz. Linkin aşağıdaki şekilde olması lazım.

https://dev.azure.com/{your_organization}/{ProjectName}/_library?itemType=VariableGroups

MobileAppsVariableGroup

Not: Aslında istediğinizi kullanabilirsiniz fakat biz pipelineda buna örnek vereceğimiz için bunu kullanalım dedik.

Add dedikten sonra Name: Cert.p12 Value: Cert.p12 şifreniz.

Not: Cert.p12 yazdım çünkü biz pipelineda buna örnek vereceğimiz için böyle yaptık. Cert.p12 şifresini value kısmına yazdık.

Şimdi gelelim daha önce bilgisayarımıza indirdiğimiz distrubition.p12 ve adhoc.mobileprovision dosyalarını secure file adımına eklemeye.

Adım 3

Tekrar yeni bir dosya eklemek için Adım 3 takip edelim. Bu sefer provizyon dosyamızı ekleyelim.

Son halinin aşağıdaki şekilde olması lazım.

Azure DevOps Pipeline için YAML Adımları

Bu kısımda yazımıza giriş kısmında örnek verdiğimiz YAML uzantısını detaylı bir şekilde inceleyeceğiz. Ardından da üzerine bazı task/script eklemeleri yaparak çeşitlendireceğiz.

Variables;

Bu kısım aslında bizim dosyalarımız için bazı konfigürasyonları tutup pipeline içinde bu konfigürasyonları kullanacağımız yer. Önceki bölümde bunun oluşturulmasına bakmıştık. Buradaki amacımız sertifikamızın şifresini admin yetkili kişinin bu group altında tutması örneği. Daha sonra bu group altından bu değişkeni okuyacağız.

variables:
- group: MobileAppsVariableGroup

Trigger;

Bu adımda repomuzun hangi branch üzerinden okunacağını veriyoruz.

trigger:
- qa

Pool;

Bu adımda çeşitli konfigürasyonlar var. Örnek olarak Azure’un sanal makinesini üzerinde jobınızı (pipeline) çalıştırabilirsiniz.

pool:
vmImage: 'macos-11'
demands: xcode
pool:
vmImage: 'macos-latest'
demands: xcode

Azure macos-latest kullanıldığında en son mac sürümü için ilgili Xcode versiyonunu kullanabilirsiniz. Şu anda en güncel Xcode 13 sürümünü destekliyor. Xcode 14 henüz Mac os 12 geçilemediği için destek vermiyor.

Farklı bir şekilde bir önceki yazımdaki self-hosted kurduğunuz mac agent üzerinde de çalıştırabilirsiniz. Böylece en güncel Xcode sürümünü de kullanabilirsiniz. Bizler projelerimizde şu anda Self-Hosted mac agent üzerinden ilerliyoruz.

pool:
name: macagentpool
demands: xcode

Steps; (Optional)

Aslında burası zorunlu bir yer değil. Eğer projeniz CocoaPods gibi dependency manager kullanıyor ise bu adımı eklemeniz gerekecektir. Ama kullanmıyor iseniz bu adımı atlayabilirsiniz.

steps:
- task: CocoaPods@0
displayName: 'iOS CocoaPods installation'

Tasks;

Farklı task adımlarınız olabilir. Bunlara örnek vermek gerekirse;

Apple sertifika ve provizyon install işlemleri

Xcode Clean, Build, Test adımları,

SonarQube gibi 3rd party frameworkler gösterilebilir.

Install Cert ve Provision Profile taskı

Burada ilk olarak Apple sertifika ve provizyon install işlemleri ele alacağız.

Bir önceki bölümde bahsetmiş olduğumuz MobileAppsVariableGroups altına eklediğimiz variable’ı, certPwd adımında başına ‘$’ ekleyerek kullandık. Benzer şekilde certSecureFile ve provProfileSecureFile kısmını ise secureFiles kısmından okuduk.

# Contact Azure admin to upload your p12 file to Azure Secure Files if needed
# Change "certSecureFile" field with p12 certificate name
- task: InstallAppleCertificate@2
displayName: 'iOS Apple certificate installation'
inputs:
certSecureFile: 'distribution.p12'
certPwd: '$(Cert.p12)'
keychain: 'temp'
# Contact Azure admin to upload your mobileprovision file to Azure Secure Files if needed
# Change "provProfileSecureFile" field with mobileprovision name
- task: InstallAppleProvisioningProfile@1
displayName: 'iOS Apple provisioning profile installation'
inputs:
provisioningProfileLocation: 'secureFiles'
provProfileSecureFile: 'adhoc.mobileprovision'

Xcode@5 taskı

Bir başka task örneği vermek istedik. Xcode@5 taskı clean, build, test adımlarını çalıştırabileceğiniz bir kısım.

Aynı anda 3 adımı da isterseniz çalıştırabilirsiniz. ‘Actions’ kısımlarına bunu boşluk yazarak gerçekleyebilirsiniz.

Bir ipucu verelim; Azure örnek olarak şu anda Xcode 14 henüz geçmedi. Dolayısıyla bu adımda aşağıdaki kısmı düzenleyebilirsiniz.

xcodeDeveloperDir: ‘/Applications/Xcode_13.app’

Hatta: Xcode version kısmına istediğiniz version numarasını da yazabilirsiniz.

Detaylı bilgi için

- task: Xcode@5
displayName: 'iOS Xcode build'
inputs:
actions: 'clean build'
configuration: 'Release'
sdk: 'iphoneos'
xcWorkspacePath: 'ProjeName.xcworkspace'
scheme: 'ProjeScheme'
xcodeVersion: 'specifyPath'
xcodeDeveloperDir: '/Applications/Xcode.app'
packageApp: true
signingOption: 'manual'
signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)'
provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'

Publish Artifacts Taskı

Bu adımda da iOS artifacts staging alana atmak olarak özetleyebiliriz

- task: PublishBuildArtifacts@1
displayName: 'iOS publish ipa to artifacts'
inputs:
PathtoPublish: '$(build.artifactStagingDirectory)'
condition: succeededOrFailed()

Artık artifactimizde (ipa) hazır. Şimdi bu ipayı istediğimiz yere atabiliriz. Burada farklı farklı araçlar kullanabilirsiniz. Bizim örneğimiz App Center üzerinden olacak.

Azure DevOps Pipeline — App Center Entegrasyonu

Ön Gereksinim

  • App Center

Not: App Center konfigürasyonlarının detayını burada aktarmayacağız. Bu başlığımızın konusu değil çünkü.

Burada aslında Azure üzerinde bir ön gereksinime ihtiyacınız var. Bir server endpoint bağlayarak App Center’ı ilk önce burada bağlantısını kurmanız gerekli.

Endpoint adına AzureDevOps diyebilirsiniz. Bir sonraki taskda kullanmak için
# Change appSlug field with AppCenter application name
# Change distributionGroupId field with AppCenter distribution group id
- task: AppCenterDistribute@3
displayName: 'iOS AppCenter distribution'
inputs:
serverEndpoint: 'AzureDevOps'
appSlug: 'AppCenterAppSlug'
appFile: '$(build.ArtifactStagingDirectory)/*.ipa'
symbolsIncludeParentDirectory: false
releaseNotesOption: 'input'
releaseNotesInput: '$(Build.SourceVersionMessage)'
destinationType: 'groups'
distributionGroupId: 'AppCenterGroupId'

Yukarıdaki adımda App Center servis bağlantımızı ve ayarlarını sağladık. Bunun dışında api file’ımızın çıktısını staging alanımızdan okuyup buraya verdik. Release note’da opsiyonal olarak biz pull requestlerimizin başlığını verdik.

Detaylı bilgi için

AzureDevOps Pipeline SonarQube Task

SonarQube kullananlar için bir bonus gelsin bizden :)

Ön Gereksinim

  • Yeni bir service connection eklemeniz gerekli.
“Grant access permission to all pipelines” checkbox’ı işaretliyoruz. İsmi, url ve token kısmını dolduruyoruz.

Şimdi pipeline’a geri geçelim. Taskımızı ekleyelim. İlk önce Xcode@5 taskından önceki satıra tıklıyoruz. Sağdaki bölüme SonarQube yazıyoruz. Prepare adımını seçiyoruz.

Daha önceden service connection eklediğimiz ismi veriyoruz. Projemizin içinde de sonar-project.properties adlı dosyamızı repomuza ekliyoruz.
- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQubeDev'
scannerMode: 'CLI'
configMode: 'file'

Not: Dileyenler bu adımda repodan okumak yerine manuel seçip “Advanced” seçeneği ile ilerleyebilirler. Biz bu şekilde ilerliyoruz.

Aşağıdaki adımı Xcode@5 taskından sonra ekliyoruz. Her şey tamamdır :) Pipeline çalıştıktan sonra SonarQube domaininize girip sonuca bakabilirsiniz.

- task: SonarQubeAnalyze@5
- task: SonarQubePublish@5
inputs:
pollingTimeoutSec: '300'

Sonucu hızlıca sonarqube domaninize giriş yapıp görebilirsiniz. Aşağıdaki herhangi bir proje için örnektir.

Azure Pipeline üzerinden SonarQube Task ile Unit Test Coverage Gösterimi

Ön Gereksinim

Şimdi gelelim bir başka konfigürasyona ve beni en zorlayan kısma :) Sonar kullananların baş belası unit test coveragı’ı, Sonarqube tarafına Azure pipeline üzerinden otomatik bir şekilde atabilmek. Heyecanla buraya adapte olsun :)

Bu kısımda hem bir başka prepare adım örneğini (manuel seçerek) ve unit test coverage’ı da ele alacağımız bir kısımdan bahsedeceğim

Aşağıdaki adımı mutlaka eklemelisiniz. Bunu ya sonar-project.properties dosyanızın içine ya da aşağıdaki manuel yapısında kullanmalısınız.

sonar.coverageReportPaths=sonarqube-generic-coverage.xml

Not: ‘*’ alanları değiştirebilirsiniz.

- task: SonarQubePrepare@5
inputs:
SonarQube: 'SonarQubeDev*'
scannerMode: 'CLI'
configMode: 'manual'
cliProjectKey: 'SonarQubeProjectKey*'
cliSources: '.'
extraProperties: |
sonar.host.url=https://sonarqubedev.domain.com.tr - domaniniz*
sonar.login=loginKeyiniz*
sonar.language=swift
sonar.c.file.suffixes=-
sonar.cpp.file.suffixes=-
sonar.objc.file.suffixes=-
sonar.sources=.
sonar.test.inclusions=**/*Test*/**
sonar.exclusions=**/*.xml,Pods/**/*
sonar.tests=TestSchemenız
sonar.swift.simulator=platform=iOS Simulator,name=iPhone 14,OS=16.2
sonar.swift.project=ProjectName.xcodeproj
sonar.swift.workspace=ProjectName.xcworkspace
sonar.swift.appName=ProjectName*
sonar.swift.appScheme=ProjectNameScheme*
sonar.coverageReportPaths=sonarqube-generic-coverage.xml
enabled: true
- task: Xcode@5
displayName: 'Clean Build Test'
inputs:
actions: 'clean build test'
configuration: 'Debug'
sdk: 'iphoneos'
xcWorkspacePath: 'ProjectName.xcworkspace'
scheme: 'ProjectSchemeName'
xcodeVersion: 'specifyPath'
xcodeDeveloperDir: '/Applications/Xcode.app'
packageApp: false
args: '-enableCodeCoverage YES -derivedDataPath DerivedData'
signingOption: 'manual'
signingIdentity: '$(APPLE_CERTIFICATE_SIGNING_IDENTITY)'
provisioningProfileUuid: '$(APPLE_PROV_PROFILE_UUID)'
useXcpretty: true
publishJUnitResults: true
testRunTitle: 'FordTruckiOS'
destinationPlatformOption: 'iOS'
destinationTypeOption: 'simulators'
destinationSimulators: 'iPhone 14'

Not: Eğer bizim proje gibi firebase kullanıyorsanız unit test kısmında configuration Debug mode’a çekmenizi öneririm. Ya da sadece test için debug mode’da yapabilirsiniz. Bunu Xcode@5 taskını çoklayarak üstesinden gelebilirsiniz.

Peki bitti mi hayır. Çünkü coverage dosyamız şu anda xcresult şeklinde. Bunu sonar’ın anlayacağı şekle dönüştürmemiz gerekli. Aşağıdaki adımı Xcode@5 taskından sonra ekliyoruz.

Bir önceki kısımda agenttan bahsetmiştim bu agent’ın kurulduğu dosyanın uzantısına gitmeliyiz (derivedDataya kadar). Ve xcresult uzantısını sonarın anlayacağı coverage dosyasına çevirmeliyiz. Bunun için de repomuz için ./xccov-to-sonarqube-generic.sh dosyasını kullanacağız. (ön gereksinim kısmına bakabilirsiniz)

- task: Bash@3
inputs:
targetType: 'inline'
script: 'bash ./xccov-to-sonarqube-generic.sh /Users/adinler-mdm/myagent/_work/1/s/DerivedData/Logs/Test/*.xcresult/ > sonarqube-generic-coverage.xml'

Bu da bittikten sonra yine SonarQubeAnalyze ve SonarQubePublish adımını da çalıştıracağız.

- task: SonarQubeAnalyze@5
- task: SonarQubePublish@5
inputs:
pollingTimeoutSec: '300'

Tüm sorularınız ve önerileriniz için iletişimde kalmak bizi son derece mutlu edecektir. Bir sonraki yazımızda görüşmek üzere.

Referanslar:

https://itnext.io/continuous-integration-ci-cd-for-ios-on-azure-devops-part-1-7236d9b897f9

Connected Products & Engineering Tribe
Mobile Chapter Team Member
Arda DİNLER — iOS Developer

--

--