Skip to content

Instantly share code, notes, and snippets.

@dombarnes
Created June 22, 2023 21:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dombarnes/8e5e3be5877f7c6f742d1872966d3409 to your computer and use it in GitHub Desktop.
Save dombarnes/8e5e3be5877f7c6f742d1872966d3409 to your computer and use it in GitHub Desktop.
Azure Devops YAML Pipeline for Xcode build and delivery to Testflight
trigger:
branches:
include:
- develop
- main
- feature/*
tags:
include:
- tvos/v*
- ios/v*
pool:
vmImage: 'macos-latest'
variables:
sdk: 'appletvos'
projectPath: 'MyApp.xcodeproj'
workspacePath: 'MyApp.xcodeproj/project.xcworkspace'
configuration: 'Release'
coveragePath: '$(agent.buildDirectory)/coverage/'
signingIdentity: 'Apple Distribution: My Organisation'
artifactName: 'MyApp'
isDevelop: $[eq(variables['Build.SourceBranch'], 'refs/heads/develop')]
isMaster: $[eq(variables['Build.SourceBranch'], 'refs/heads/master')]
buildTvos: $[contains(variables['Build.SourceVersionMessage'], '[tvos]')]
buildIos: $[contains(variables['Build.SourceVersionMessage'], '[ios]')]
${{ if eq(variables['Build.SourceBranch'], 'refs/heads/main') }}:
appidentifier: 'com.myorganisation.myapp'
appAppleID: '123456'
scheme: 'MyApp'
${{ else }}:
appidentifier: 'com.myorganisation.myapp-Dev'
appAppleID: '654321'
scheme: 'MyApp Dev'
stages:
- stage: BuildandTest
jobs:
- job: BuildValidation
condition: eq(variables.isMaster, 'true')
steps:
- script: |
# Install and run the linter
brew install swiftlint
swiftlint lint --path MyApp/ --config .swiftlint.yml --reporter junit
displayName: 'Run SwiftLint'
condition: ne(variables['Build.Reason'], 'PullRequest')
- task: Xcode@5
displayName: 'Build Project'
inputs:
actions: 'build'
sdk: '$(sdk)'
scheme: '$(scheme)'
signingOption: 'manual'
signingIdentity: 'Developer ID Application'
teamId: $(teamId)
packageApp: false
destinationPlatformOption: 'tvOS'
configuration: 'Debug' # only testable for Debug with current config
xcWorkspacePath: '$(workspacePath)'
xcodeVersion: 'default'
args: '-derivedDataPath derivedData/'
- job: RunTestSuite
steps:
- task: Xcode@5
enabled: false
displayName: 'Run MyAppUITests'
inputs:
actions: 'test'
sdk: '$(sdk)'
scheme: '$(scheme)'
packageApp: false
destinationPlatformOption: 'tvOS'
configuration: '$(configuration)' # only testable for Debug with current config
xcWorkspacePath: '$(workspacePath)'
xcodeVersion: 'default'
publishJUnitResults: true
- script: gem install slather
displayName: 'Install Slather'
- script: slather coverage -x --scheme $(scheme) --workspace $(workspacePath) --output-directory $(coveragePath) $(projectPath)
displayName: 'Run slather to convert Code Coverage'
enabled: false
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(coveragePath)/cobertura.xml'
- stage: PublishToTestFlight
condition: contains(variables['Build.SourceVersionMessage'], '[tvos]')
jobs:
- template: BuildIPAForTestFlight.yml
parameters:
platforms:
- tvOS
- iOS
parameters:
- name: "platforms"
type: object
default: {}
jobs:
- ${{ each platform in parameters.platforms }}:
- job: BuildIPAForTestFlight_${{platform}}
steps:
- task: DownloadSecureFile@1
inputs:
secureFile: AuthKey_NTN7Z52VA2.p8
- task: InstallAppleCertificate@2
displayName: 'Install an Apple certificate'
inputs:
certSecureFile: 'Apple Distribution.p12'
certPwd: '$(P12password)'
setUpPartitionIdACLForPrivateKey: false
- task: InstallAppleProvisioningProfile@1
displayName: 'Install an Apple provisioning profile'
inputs:
provProfileSecureFile: 'TTVPlayerDev_20230621.mobileprovision'
- task: Xcode@5
displayName: 'Build Project'
inputs:
# actions: 'archive'
sdk: $(sdk)
scheme: $(scheme)
signingOption: manual
signingIdentity: $(signingIdentity)
provisioningProfileName: $(ProvisioningProfile)
teamId: $(teamId)
packageApp: true
destinationPlatformOption: 'default'
configuration: '$(configuration)' # only testable for Debug with current config
xcWorkspacePath: '$(workspacePath)'
archivePath: 'build/$(scheme)'
xcodeVersion: 'default'
publishJUnitResults: true
exportPath: 'build/$(SDK)/$(Configuration)'
exportOptions: 'plist'
exportOptionsPlist: 'build/exportOptions.plist'
args: '-derivedDataPath derivedData/'
- task: CopyFiles@2
displayName: 'Copy Files to: $(build.artifactstagingdirectory)'
inputs:
SourceFolder: '$(system.defaultworkingdirectory)/build'
Contents: '**/*'
TargetFolder: '$(build.artifactstagingdirectory)/$(configuration)'
- task: PublishPipelineArtifact@1
displayName: 'Swift - Publish Artifact'
inputs:
targetPath: '$(build.artifactstagingdirectory)'
artifactName: $(artifactName)_${{platform}}
- task: AppStoreRelease@1
displayName: 'Publish to the App Store TestFlight track'
inputs:
serviceEndpoint: 'App Store (API Key)'
releaseTrack: 'TestFlight'
appIdentifier: $(appidentifier)
appType: ${{platform}}
ipaPath: '$(build.artifactstagingdirectory)/**/*.ipa'
shouldSkipWaitingForProcessing: true
shouldSkipSubmission: true
appSpecificId: $(appAppleID)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>generateAppStoreInformation</key>
<false/>
<key>manageAppVersionAndBuildNumber</key>
<true/>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>com.myorganisation.myapp</key>
<string>MyApp Provisioning Profile</string>
</dict>
<key>signingCertificate</key>
<string>Apple Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>AABBCCDD</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
@dombarnes
Copy link
Author

You will need:

  • A working Apple Distribution Certificate (uploaded to Secure Files in Azure Devops)
  • A Provisioning Profile (uploaded to Secure Files in Azure Devops)
  • An App Store Connect API Key, Issuer ID and p8 file
  • A Service Connection set up to use the App Store Connect API Key
    Get your base64-encoded p8 file via this:
    cat AuthKey_ABCD1234.p8 | openssl base64

Steps

  1. Move exportOption.plist into a build folder (or somewhere else and change the path in the yml temlplate)
  2. Add your specific scheme, bundle ID, team ID, Provisioning Profile name, P12 password, etc to Variables in your pipeline (mark some of those as secure..)
  3. Profit?

Great References:
https://github.com/microsoft/app-store-vsts-extension
https://damienaicheh.github.io/azure/devops/2021/10/27/configure-azure-devops-app-store-en.html
https://www.matrixprojects.net/p/xcodebuild-export-options-plist/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment