你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 如何使用Xcode Server進行持續集成並自動部署到iTunes Connect

如何使用Xcode Server進行持續集成並自動部署到iTunes Connect

編輯:IOS開發基礎

1-59Ka9k6YIaWdedgDi77zBA.jpeg

  • 原文:CI AND AUTOMATIC DEPLOYMENT TO ITUNES CONNECT WITH XCODE SERVER

  • 作者:Miguel Revetria

  • 譯者:CocoaChina--劉行知(QQ1445152551)


這篇文章中,我將介紹在Xmartlabs項目中,使用Xcode Server進行持續集成,並自動部署到iTunes Connect的一些經驗,以及我所遇到問題。本文將描述我是如何解決其中的一些問題的,以期它可以幫助一些遇到相似情況的人。

已經有很多博客講述如何設置Xcode Server,創建一個集成 bot(譯者注:機器人,為便於理解與實踐,本文中不翻),以及在Xcode上浏覽其結果(問題跟蹤,測試代碼覆蓋率等)。然而,當你嘗試一些更復雜的東西,你可能會遇到一些錯誤時,而這些錯誤一般很難找到描述解決辦法的資源。

為什麼我們需要有自己的CI(continuous integration)服務器?
幾乎每個人都知道擁有CI服務器的好處:它可以自動分析代碼,運行單元和UI測試,在其他有價值的任務中構建項目。如果代碼出現問題,它會將結果通知可能引入該問題的人。 Xcode bot跟蹤每個集成的所有新問題以及已解決的問題。對於新的問題,bot將顯示一系列可能產生問題的提交。此外,我們不再需要處理所部署環境的配置文件和證書,從而允許團隊中的任何人輕松發布新版本的應用程序。

總之,這允許程序員花更多的時間在應用程序開發上,而在應用程序集成和部署上花更少的時間。同時,確保代碼有質量問題的可能性保持在最低。

設置Xcode Server
蘋果公司的[Xcode Server和持續集成指南],很好地介紹了如何設置和使用Xcode Server的知識,我們建議您首先閱讀該指南,我們不再詳細介紹關於設置Xcode Server的基礎知識。

Cocoapods&Fastlane

安裝Xcode Server應用程序並啟用Xcode Server服務,下一步便是安裝 Cocoapods 和 Fastlane 。Fastlane將幫助我們完成許多常規任務,這些任務是構建項目和將應用程序上傳到iTunes Connect所必需的。為了防止它們運行過程中出現權限問題,我們將僅僅為對應的構建者(譯者注:builder user,構建用戶,本文中簡稱構建者),安裝所有gem,使用 gem install --user-install some_gem 命令來完成安裝。另外,我們需要創建符號鏈接,來訪問 Cocoapods 和 Fastlane 二進制文件,以便在我們的bot運行時訪問它們。

在開始之前,通過將下面的這一行加入到`~/.bashrc`和`~/.bash_login`文件內,將ruby bin文件夾包含到構建者的路徑中:

 # It may change depending on the ruby's version on your system(請根據你系統中ruby的版本來修改此處的版本號)
 export PATH="$PATH:/var/_xcsbuildd/.gem/ruby/2.0.0/bin"

現在開始安裝gems:

$ sudo su - _xcsbuildd
    
$ gem install --user-install cocoapods
$ pod setup
$ ln -s `which pod` /Applications/Xcode.app/Contents/Developer/usr/bin/pod
    
$ gem install --user-install fastlane
$ ln -s `which fastlane` /Applications/Xcode.app/Contents/Developer/usr/bin/fastlane

郵件 & 通知

Xcode Server有一個很好的功能,能夠根據集成結果向選定的人發送電子郵件。例如,如果因為項目沒有編譯通過,或者一些測試沒有通過,而導致的集成失敗,bot將發送電子郵件到最後提交者,通知其構建已經失敗了。

由於我們使用Gmail帳戶發送電子郵件,因此需要更改Xcode Server上的郵件服務的設置。首先在服務器上啟用郵件服務,然後檢查選項“Relay outgoing mail through ISP”。在選項對話框中,添加smtp.gmail.com:587,啟用身份驗證並輸入有效的憑據。這就是讓 Xcode Server使用您的Gmail帳戶發送電子郵件需要的所有設置。

11.png

創建bot

現在我們已經啟動並運行了Xcode Server,現在該創建我們的Xcode bots了。在Xmartlabs,我們為每個Xcode項目設立了兩個不同的bot。

持續集成 bot

為了確保項目正確構建,以及代碼分析,單元和UI測試相應地都通過。每當一個拉取請求合並到開發分支中時,這個bot都將被自動觸發。如果出現問題,它將通知提交者。

我們可以通過以下簡單的步驟創建bot:

  1. 在Xcode項目中,選擇菜單選項“Product”>”Create Bot”。

  2. 依照創建向導,比較簡單就可以完成。在設置git憑據時,你可能會遇到一些困難。我們選擇創建一個ssh密鑰,並將其用於我們的bot。於是我們最終選擇現有的SSH密鑰,並對所有的bot使用相同的密鑰。

  3. 集成它,看看一切是否運行良好。

202.png

> 比較好用的一點是,電子郵件將被發送到所有可能導致該問題的提交者,你也可以指定其他接收者。

部署型bot

第二個bot負責構建和上傳應用程序IPA到iTunes Connect。它還將負責使用最新的代碼倉庫創建和推送新的git標簽,而這我們將使用Fastlane來實現。因為我們通常需要每周發布一次測試版本,因此通常我們將其配置為按需運行或每周運行。

103.png

  • 證書和私鑰

我們必須確保在系統鑰匙串上已經安裝了分發/開發證書及其對應的私鑰。

104.png

要構建IPA,我們必須在以下文件夾中放入必需的配置文件,因為bot在其自己的用戶`_xcsbuildd `上運行,並在此文件夾中搜索配置文件:

/Library/Developer/XcodeServer/ProvisioningProfiles

集成前的腳本

Xcode集成時,允許我們提供,集成前和集成後的腳本。

在我們的部署型Bot開始集成之前,我們必須執行一些觸發型的命令:

  • 遞增編譯版本號

  • 下載所需的配置文件

  • 安裝項目使用的庫的正確版本
    Fastlane工具將在`Appfile`文件中查找有用信息,以修改諸如 Apple ID 和 application Bundle Identifier 。下面的代碼片段,介紹了`Appfile`:

app_identifier "(MY_APP_BUNDLE_ID)" # The bundle identifier of your app(因識別問題,本段代碼中用圓括號替代尖括號)

apple_dev_portal_id "([email protected])"  # Your Apple email address
itunes_connect_id "([email protected])"

# You can uncomment the lines below and add your own
# team selection in case you are on multiple teams
# team_name "(TEAM_NAME)"
# team_id "(TEAM_ID)"

# To select a team for iTunes Connect use
# itc_team_name "(ITC_TEAM_NAME)"
# itc_team_id "(ITC_TEAM_ID)"

下載和配置“配置文件”由Fastlane [sigh]工具完成。它的用法很簡單,只要正確設置了`Appfile`,剩下的就交給它了。

lane :before_integration do
  # fetch the number of commits in the current branch
  build_number = number_of_commits

  # Set number of commits as the build number in the project's plist file before the bot actually start building the project.
  # This way, the generated archive will have an auto-incremented build number.
  set_info_plist_value(
    path: './MyApp-Info.plist',
    key: 'CFBundleVersion',
    value: "#{build_number}"
  )

  # Run `pod install`
  cocoapods

  # Download provisioning profiles for the app and copy them to the correct folder.
  sigh(output_path: '/Library/Developer/XcodeServer/ProvisioningProfiles', skip_install: true)
end

> `number_of_commits`和`cocoapods`是Fastlane的動作。 `Appfile`和`Fastfile`文件都必須位於項目根目錄中的`fastlane`文件夾中。
如果我們運行`fastlane before_integration`,它將連接到iOS Member Center,並使用在`Appfile`中的bundle id,下載該應用程序的配置信息文件。此外,我們必須將密碼發送到fastlane。從而使這些操作配合Xcode bots工作,我們通過環境變量`FASTLANE_PASSWORD`上傳密碼:

$ export FASTLANE_PASSWORD="(APPLE_ID_PASSWORD)"(圓括號替換尖括號)
$ fastlane before_integration

> 最初,我們嘗試使用Keychain將密碼傳遞給Fastlane `sigh `,但並沒有成功,更多有關這方面的信息,請參閱[這裡]。

我們將通過在*Triggers選項卡*上添加一個before trigger命令來修改部署型bot,使得其執行`before_integration` lane。

544.png

> 注意,在調用`fastlane`之前,我們切換到了`myapp`文件夾,這是git遠程倉庫名稱。**觸發器在父項目文件夾中運行**。

集成後腳本

在bot完成項目集成後,我們將能夠訪問創建的歸檔文件,將其導出為IPA文件並將其上傳到iTunes Connect。我們將創建一個額外的lane,負責將IPA上傳到iTunes Connect,並創建一個git標簽。

讓我們從簡單的開始,現在先不考慮上傳到iTunes Connect:

lane :after_integration do
  plistFile = './MyApp-Info.plist'

  # Get the build and version numbers from the project's plist file
  build_number = get_info_plist_value(
    path: plist_file,
    key: 'CFBundleVersion',
  )
  version_number = get_info_plist_value(
    path: plist_file,
    key: 'CFBundleShortVersionString',
  )

  # Commit changes done in the plist file
  git_commit(
    path: ["#{plistFile}"],
    message: "Version bump to #{version_number} (#{build_number}) by CI Builder"
  )

  # TODO: upload to iTunes Connect

  add_git_tag(
    tag: "beta/v#{version_number}_#{build_number}"
  )

  push_to_git_remote

  push_git_tags
end

現在,我們將從集成期間由bot創建的歸檔文件中導出IPA。我們通過在`after_integration` lane中運行命令`xcrun xcodebuild`來實現。此外,我們將使用Fastlane[交付工具]將IPA上傳到iTunes Connect。詳情如下:

lane :after_integration do
  plistFile = './MyApp-Info.plist'

  # ...

  ipa_folder = "#{ENV['XCS_DERIVED_DATA_DIR']}/deploy/#{version_number}.#{build_number}/"
  ipa_path = "#{ipa_folder}/#{target}.ipa"
  sh "mkdir -p #{ipa_folder}"

  # Export the IPA from the archive file created by the bot
  sh "xcrun xcodebuild -exportArchive -archivePath \"#{ENV['XCS_ARCHIVE']}\" -exportPath \"#{ipa_path}\" -IDEPostProgressNotifications=YES -DVTAllowServerCertificates=YES -DVTSigningCertificateSourceLogLevel=3 -DVTSigningCertificateManagerLogLevel=3 -DTDKProvisioningProfileExtraSearchPaths=/Library/Developer/XcodeServer/ProvisioningProfiles -exportOptionsPlist './ExportOptions.plist'"

  # Upload the build to iTunes Connect, it won't submit this IPA for review.
  deliver(
    force: true,
    ipa: ipa_path
  )

  # Keep committing and tagging actions after export & upload to prevent confirm the changes to the repo if something went wrong
  add_git_tag(
    tag: "beta/v#{version_number}_#{build_number}"
  )

  # ...

end

> 我們沒有使用bot來創建IPA文件,因為它在觸發器執行期間不可用。我們也不是使用[gym](https://github.com/fastlane/gym),因為鑰匙串限制問題。

支持多個Target

通常我們的項目有應用的產品以及多個階段的target(staging application targets)。對於我們想要上傳到iTunes Connect的每個target,`Fastfile`文件將需要不同的lane。我們需要修改`Appfile`文件,以根據每個lane設置正確的應用標識符:

for_platform :ios do
  for_lane :before_integration_staging do
      app_identifier "com.xmartlabs.myapp.staging"
  end

  for_lane :after_integration_staging do
    app_identifier "com.xmartlabs.myapp.staging"
  end

  for_lane :before_integration_production do
      app_identifier "com.xmartlabs.myapp"
  end

  for_lane :after_integration_production do
    app_identifier "com.xmartlabs.myapp"
  end
end

apple_dev_portal_id ""
itunes_connect_id ""

# team_name ""(此處圓括號替代尖括號)
# team_id ""(此處圓括號替代尖括號)

> 設置 apple\_dev\_portal\_id 和 itunes\_connect\_id 允許我們使用不同的帳戶來抓取配置文件,以及分別上傳到iTunes Connect。

最後,在一些重構後,`Fastfile`文件可能如下所示:

require './libs/utils.rb'

fastlane_version '1.63.1'

default_platform :ios

platform :ios do  
  before_all do
    ENV["SLACK_URL"] ||= "https://hooks.slack.com/services/#####/#####/#########"
  end

  after_all do |lane|
  end

  error do |lane, exception|
    reset_git_repo(force: true)
    slack(
      message: "Failed to build #{ENV['XL_TARGET']}: #{exception.message}",
      success: false
    )
  end

  # Custom lanes

  desc 'Do basic setup, as installing cocoapods dependencies and fetching profiles, before start integration.'
  lane :before_integration do
    ensure_git_status_clean

    plist_file = ENV['XL_TARGET_PLIST_FILE']

    # This is a custom action that could be find in the libs/utils.rb
    increase_build_number(plist_file)

    cocoapods
    sigh(output_path: '/Library/Developer/XcodeServer/ProvisioningProfiles', skip_install: true)
  end

  desc 'Required tasks before integrate the staging app.'
  lane :before_integration_staging do
    ENV['XL_TARGET_PLIST_FILE'] = './MyAppStaging-Info.plist'
    before_integration
  end

  desc 'Required tasks before build the production app.'
  lane :before_integration_production do
    ENV['XL_TARGET_PLIST_FILE'] = './MyApp-Info.plist'
    before_integration
  end

  desc 'Submit a new Beta Build to Apple iTunes Connect'
  lane :after_integration do
    branch = ENV['XL_BRANCH']
    deliver_flag = ENV['XL_DELIVER_FLAG'].to_i
    plist_file = ENV['XL_TARGET_PLIST_FILE']
    tag_base_path = ENV['XL_TAG_BASE_PATH']
    tag_base_path = "#{tag_base_path}/" unless tag_base_path.nil? || tag_base_path == ''
    tag_link = ENV['XL_TAG_LINK']
    target = ENV['XL_TARGET']

    build_number = get_info_plist_value(
      path: plist_file,
      key: 'CFBundleVersion',
    )
    version_number = get_info_plist_value(
      path: plist_file,
      key: 'CFBundleShortVersionString',
    )

    ENV['XL_VERSION_NUMBER'] = "#{version_number}"
    ENV['XL_BUILD_NUMBER'] = "#{build_number}"

    tag_path = "#{tag_base_path}release_#{version_number}_#{build_number}"
    tag_link = "#{tag_link}#{tag_path}"
    update_changelog({
      name: tag_path,
      version: version_number,
      build: build_number,
      link: tag_link
    })

    ENV['XL_TAG_LINK'] = "#{tag_link}"
    ENV['XL_TAG_PATH'] = "#{tag_path}"

    sh "git config user.name 'CI Builder'"
    sh "git config user.email '[email protected]'"

    git_commit(
      path: ["./CHANGELOG.md", plist_file],
      message: "Version bump to #{version_number} (#{build_number}) by CI Builder"
    )

    if deliver_flag != 0
      ipa_folder = "#{ENV['XCS_DERIVED_DATA_DIR']}/deploy/#{version_number}.#{build_number}/"
      ipa_path = "#{ipa_folder}/#{target}.ipa"
      sh "mkdir -p #{ipa_folder}"
      sh "xcrun xcodebuild -exportArchive -archivePath \"#{ENV['XCS_ARCHIVE']}\" -exportPath \"#{ipa_path}\" -IDEPostProgressNotifications=YES -DVTAllowServerCertificates=YES -DVTSigningCertificateSourceLogLevel=3 -DVTSigningCertificateManagerLogLevel=3 -DTDKProvisioningProfileExtraSearchPaths=/Library/Developer/XcodeServer/ProvisioningProfiles -exportOptionsPlist './ExportOptions.plist'"

      deliver(
        force: true,
        ipa: ipa_path
      )
    end

    add_git_tag(tag: tag_path)

    push_to_git_remote(local_branch: branch)

    push_git_tags

    slack(
      message: "#{ENV['XL_TARGET']} #{ENV['XL_VERSION_NUMBER']}.#{ENV['XL_BUILD_NUMBER']} successfully released and tagged to #{ENV['XL_TAG_LINK']}",
    )
  end

  desc "Deploy a new version of MyApp Staging to the App Store"
  lane :after_integration_staging do
    ENV['XL_BRANCH'] = current_branch
    ENV['XL_DELIVER_FLAG'] ||= '1'
    ENV['XL_TAG_BASE_PATH'] = 'beta'
    ENV['XL_TARGET_PLIST_FILE'] = './MyApp Staging-Info.plist'
    ENV['XL_TARGET'] = 'MyApp Staging'
    ENV['XL_TAG_LINK'] = 'https://github.com/xmartlabs/MyApp/releases/tag/'

    after_integration
  end

  desc "Deploy a new version of MyApp to the App Store"
  lane :after_integration_production do
    ENV['XL_BRANCH'] = current_branch
    ENV['XL_DELIVER_FLAG'] ||= '1'
    ENV['XL_TARGET_PLIST_FILE'] = './MyApp-Info.plist'
    ENV['XL_TARGET'] = 'MyApp'
    ENV['XL_TAG_LINK'] = 'https://github.com/company/MyApp/releases/tag/'
    after_integration
  end
end

關於前一個`Fastfile`文件的注意事項:

  • 為生產環境和多階段環境定義兩個`before_integration` lane,以便使用`Appfile`設置正確的應用程序標識符。

  • 編譯,版本控制操作和部署操作封裝在`after_integration` lane中。這使得我們可以產品和分階段的`after_integration` lane,設置了不同的參數和內部調用。

  • ensure_git_status_clean`將檢查bot的工作文件夾是否有更改,若更改,則運行失敗。這將確保bot的工作副本與遠程存儲庫文件完全相同。由於我們正在更新我們`after_integration` lane上的本地文件,如果出現問題,我們將需要重置所有文件。因此,我們在`error`塊中添加了`reset_git_repo`操作。

  • 命令`xcrun xcodebuild -exportArchive`需要使用選項`-exportOptionsPlist`指定的配置文件。我們在`fastlane`文件夾中創建了`ExportOptions.plist`文件,其內容類似於:

800.png        
最後一步,添加一個新的在集成後觸發器(After Integration Trigger),執行我們的`after_integration_staging` lane:

52.png

您可以在 [Fastlane CI files](https://github.com/xmartlabs/Fastlane-CI-Files)這個github倉庫中,找到上面列出Fastlane文件的模板。

故障排除(Troubleshooting)

在我們設置Xcode Server的過程中,我們面臨許多不容易解決的錯誤和問題,主要是因為我們在網絡上找不到任何相關信息。我們決定制定一份全面的名單,以便能夠幫助處於同樣情況的任何人。

嘗試傳遞開發者密碼給Fastlane tools

`sigh`將嘗試將密碼存儲在鑰匙串中,當沒有提供密碼時,它將嘗試訪問它,但是當從bot的觸發器運行`sigh`時,這不起作用,因為觸發器命令無法訪問bot用戶的鑰匙串。
我們試圖解鎖它,然後運行`sigh`時,結果如下所示:

# Try to unlock the keychain to be accessed by fastlane actions
$ security -v unlock-keychain -p `cat /Library/Developer/XcodeServer/SharedSecrets/PortalKeychainSharedSecret` /Library/Developer/XcodeServer/Keychains/Portal.keychain
    
# Will download profiles using sigh
$ fastlane before_integration_staging

在輸出日志中顯示下一條消息:
54.png
我們根本無法在運行Fastlane時訪問鑰匙串。我們選擇僅將密碼保存為系統環境變量。

CocoaPods無法更新依賴關系

[!] Unable to satisfy the following requirements:

- `SwiftDate` required by `Podfile`
- `SwiftDate (= 3.0.2)` required by `Podfile.lock`

> 注意:Podfile中的依賴關系似乎是好的,可能是當pods嘗試在用戶的文件下更新它的倉庫文件夾時,發送了權限問題。

我們是這樣解決的:卸載重裝CocoaPods。如下所示:

$ sudo rm -fr /var/_xcsbuildd/.cocoapods
 
$ sudo su - _xcsbuildd
$ gem install --user-install cocoapods
$ pod setup
$ ln -s `which pod` /Applications/Xcode.app/Contents/Developer/usr/bin/pod

Fastlane - Sigh & Gym 無法訪問鑰匙串

結果就是這樣,它們不能訪問鑰匙串。看到這條消息(或類似的),當運行`gym`或`sigh`產生的結果如下:

security:SecKeychainAddInternetPassword
:User interaction is not allowed.
  • 它們無法訪問存儲的登錄密碼,必須使用`FASTLANE_PASSWORD`通過env變量傳遞密碼至`sigh`。

  • `gym`無法訪問安裝在鑰匙串中的分發證書,所以使IPA使用`xcrun xcodebuild`而不是`gym`中。

證書和私鑰

請確保:

  • 它們必須安裝在系統鑰匙串中,以便Xcode Bot可以訪問它們。

  • 在鑰匙串應用程序上,更改證書和私鑰訪問控制,允許`codesign`和`security` 二級制文件訪問它們。

無法在Xcode Server程序中 選擇 Xcode

將Xcode更新到版本7.2.1後,我們能夠在Xcode Server上選擇它,之後Xcode service被禁用了。當我們嘗試選擇正確的Xcode 時,會顯示一個對話框,說明“您必須同意xcode軟件許可協議的條款”。我們找到了解決方案,在蘋果論壇問題中 [“Can not choose Xcode in Server App - “You must agree to the terms…”],運行如下命令將允許您在Xcode Server中選擇Xcode:

$ sudo /Applications/Xcode.app/Contents/Developer/usr/bin/xcscontrol --initialize

IPA not available

在編譯完成之後,bot構建的IPA被拷貝到了下面的路徑:

/Library/Developer/XcodeServer/IntegrationAssets/$XCS_BOT_ID-$XCS_BOT_NAME/$XCS_INTEGRATION_NUMBER/$TARGET_NAME.ipa

但是,直到集成後的觸發器運行完畢後,它才是可用的。

沒有定義XCS_ARCHIVE

只有當bot被設置為執行歸檔操作時,環境變量XCS_ARCHIVE才被定義。

使用自定義ssh key

修改日志的更改和內部版本號,這些內容的提交,我們需要從`_xcsbuildd`的shell對倉庫有訪問權限。如果您更喜歡使用SSH訪問git服務器,則需要在構建者(builder user)`.ssh`文件夾中添加有效的簽名。請注意,此簽名不應設置密碼。否則,觸發器將停止其處理過程,您輸入了shh key 密碼。

  • 使用`_xcsbuildd`登錄: $ sudo su - _xcsbuildd

  • 拷貝一個有效的ssh key到: ~/.ssh.

  • 修改` ~/.bash_login`以便自動添加你自定義的key到 ssh agent:     

$ echo 'eval "$(ssh-agent -s)"' >> ~/.bash_login
$ echo 'ssh-add ~/.ssh/id_rsa_github' >> ~/.bash_login
  • 修改` ~/.ssh/config` 文件,以決定哪個key將被用戶訪問git倉庫,比如下面的幾行:

    Host github.com
       HostName github.com
     IdentityFile ~/.ssh/id_rsa_github

這也將有助於獲取git子模塊。

無效簽名

如果上傳到iTunes Connect失敗,並出現類似於“Invalid Signature. A sealed resource is missing or invalid.“,可能會發生,因為export archive命令(xcodebuild命令)未配置參數選項`-exportOptionsPlist`。需要加上它,並保證文件的路徑是正確的。完整的錯誤信息是:
parameter ErrorMessage = ERROR ITMS-90035: "Invalid Signature. A sealed resource is missing or invalid. Make sure you have signed your application with a distribution certificate, not an ad hoc certificate or a development certificate. Verify that the code signing settings in Xcode are correct at the target level (which override any values at the project level). Additionally, make sure the bundle you are uploading was built using a Release target in Xcode, not a Simulator target. If you are certain your code signing settings are correct, choose "Clean All" in Xcode, delete the "build" directory in the Finder, and rebuild your release target. For more information, please consult https://developer.apple.com/library/ios/documentation/Security/Conceptual/CodeSigningGuide/Introduction/Introduction.html

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved