一步一步構建iOS持續集成:Jenkins+GitLab+蒲公英+FTP

什麼是持續集成

持續集成是一種軟件開發實踐,即團隊開發成員經常集成它們的工作,通過每個成員每天至少集成一次,也就意味着每天可能會發生多次集成。每次集成都通過自動化的構建(包括編譯,發佈,自動化測試)來驗證,從而儘早地發現集成錯誤。

爲什麼使用持續集成

1.減少風險

2.減少重複過程

3.任何時間、任何地點生成可部署的軟件

4.增強項目的可見性

常用的持續集成工具


市面上的持續集成工具有很多,考慮到Jenkins的穩定性,我們還是選擇以Jenkins來開始iOS的持續集成。

好吧,接下來就正式開始搭建iOS持續集成平臺了。

Jenkins的安裝


在Mac環境下,我們需要先安裝JDK,然後在Jenkins的官網下載最新的war包。

下載完成後,打開終端,進入到war包所在目錄,執行以下命令:

java -jar jenkins.war --httpPort=8888

httpPort指的就是Jenkins所使用的http端口,這裏指定8888,可根據具體情況來修改。待Jenkins啓動後,在瀏覽器頁面輸入以下地址:

http://localhost:8888

這樣就打開Jenkins管理頁面了。

Jenkins的配置


  • 安裝GitLab插件
    因爲我們用的是GitLab來管理源代碼,Jenkins本身並沒有自帶GitLab插件,所以我們需要依次選擇 系統管理->管理插件,在“可選插件”中選中“GitLab Plugin”和“Gitlab Hook Plugin”這兩項,然後安裝。
  • 安裝Xcode插件
    同安裝GitLab插件的步驟一樣,我們依次選擇系統管理->管理插件,在“可選插件”中選中“Xcode integration”安裝。
  • 安裝簽名證書管理插件
    iOS打包內測版時,需要發佈證書及相關簽名文件,因此這兩個插件對於管理iOS證書非常方便。還是在系統管理->管理插件,在“可選插件”中選中“Credentials Plugin”和“Keychains and Provisioning Profiles Management”安裝。
  • 安裝FTP插件
    系統管理->管理插件,在“可選插件”中選中“Publish over FTP”安裝。
  • 安裝腳本插件
    這個插件的功能主要是用於在build後執行相關腳本。在系統管理->管理插件,在“可選插件”中選中“Post-Build Script Plug-in”安裝。

好了,插件安裝完,可以正式開始自動化構建了!!!

自動化構建


在Jenkins中,所有的任務都是以“item”爲單位的。接下來我們就新建一個iOS的項目來開始自動化構建。點擊“新建”,輸入item的名稱,選擇“構建一個自由風格的軟件項目”,然後點擊“OK”。


然後按下圖設置構建信息,


源碼管理:

這裏用到的是GitLab,先需要配置SSH,我們可以在Jenkins的證書管理中添加SSH。在Jenkins管理頁面,選擇“Credentials”,然後選擇“Global credentials (unrestricted)”,點擊“Add Credentials”,如下圖所示,我們填寫自己的SSH信息,然後點擊“Save”,這樣就把SSH添加到Jenkins的全局域中去了。


接下來,我們再回到剛剛新建的任務中,在源碼管理中,選擇Git,按下圖填好相關信息。PS:Credentials不需要選擇。


構建觸發器設置

因爲此教程不涉及自動測試這塊的流程,所以不需要設置觸發器。(以後會有另外的自動測試教程^_^)

構建環境設置

iOS打包需要簽名文件和證書,所以這部分我們勾選“Keychains and Code Signing Identities”和“Mobile Provisioning Profiles”。

這裏我們又需要用到Jenkins的插件,在系統管理頁面,選擇“Keychains and Provisioning Profiles Management”。


進入Keychains and Provisioning Profiles Management頁面,點擊“瀏覽”按鈕,分別上傳自己的keychain和證書。

上傳成功後,我們再爲keychain指明簽名文件的名稱。點擊“Add Code Signing Identity”,最後添加成功後如下圖所示:


這樣我們的Adhoc證書和簽名文件就已經在Jenkins中配置好了,接下來我們只需要在item設置中指定相關文件即可。

回到我們新建的item,找到構建環境,按下圖選好自己的相關證書和簽名文件。


Xcode配置

點擊“增加構建步驟”,選擇“Xcode”。

依次按下圖填寫項目信息:




腳本設置

我們沒有勾選“Pack application and build.ipa”的原因是,Jenkins的Xcode插件不支持Mac10.10以上的打包了。所以,我們需要用腳本來自己實現iOS打包。

仍然是點擊“增加構建步驟”,選擇“Execute Shell”。

輸入下列腳本:

if [ -d "${WORKSPACE}/builds" ]; then rm -rf ${WORKSPACE}/builds; fi;

mkdir ${WORKSPACE}/builds;

if [ -d "${WORKSPACE}/builds/${BUILD_NUMBER}" ]; then rm -rf ${WORKSPACE}/builds/${BUILD_NUMBER}; fi;

mkdir ${WORKSPACE}/builds/${BUILD_NUMBER};

xcodebuild -project ${WORKSPACE}/testForiOS/testForiOS.xcodeproj -scheme "testForiOS" -sdk iphoneos archive -archivePath ${WORKSPACE}/builds/${BUILD_NUMBER}/archive CODE_SIGN_IDENTITY="iPhone Distribution: xxxxxxx"

xcodebuild -exportArchive -exportFormat IPA -archivePath ${WORKSPACE}/builds/${BUILD_NUMBER}/archive.xcarchive -exportPath ${WORKSPACE}/builds/${BUILD_NUMBER}/${JOB_NAME}_${BUILD_NUMBER}.ipa -exportProvisioningProfile "XC Ad Hoc: com.xxxxx.xxx"

這樣我們就簡單的實現了自動打包的所有配置了。

不過,當iOS應用打包好後,我們還想發給其他相關人員安裝,包括公司內部的,外網的,都需要。這時我們還需配置OTA服務和內網FTP。

外網安裝App我們需要用到現在市面上比較流行的免費平臺,蒲公英。在上面蒲公英官網設置相關信息後,我們可以寫一個簡單的腳本,來實現App打包後,上傳到蒲公英和公司內網以及郵件提醒相關人員這一系列操作。

我們先用Jenkins的插件配置FTP信息。進入系統管理頁面,選擇系統設置,找到“Publish over FTP”,按下圖填好相關信息:


回到任務配置頁面,點擊“增加構建後操作步驟”,然後選擇“Send build artifacts over FTP”,填寫:


這樣FTP服務就配置好了。

接下來我們再點擊“增加構建後操作步驟”,選擇“Execute a set of scripts”,如下圖所示:


所以配置都已經設置好,點擊“保存”,好了,我們可以試試點擊“立即構建”按鈕了。

SUCCESS!!!

以上就是iOS持續集成的簡單內容,教程中暫未涉及到自動測試,以後會推出自動測試的內容,未完待續。。。。

附(腳本內容):

#coding=utf-8

import time

import urllib2

import time

import json

import mimetypes

import os

import smtplib

from email.MIMEText import MIMEText

from email.MIMEMultipart import MIMEMultipart

import json


#蒲公英應用上傳地址

url = 'http://www.pgyer.com/apiv1/app/upload'

#蒲公英提供的 用戶Key

uKey = 'xxxx'

#上傳文件的文件名(這個可隨便取,但一定要以 ipa 結尾)

file_name = 'xxxx.ipa'

#蒲公英提供的 API Key

_api_key = 'xxxxx'

#安裝應用時需要輸入的密碼,這個可不填

installPassword = '123456'


# 運行時環境變量字典

environsDict = os.environ

#此次 jenkins 構建版本號

jenkins_build_number = environsDict['BUILD_NUMBER']


#項目名稱,用在拼接 tomcat 文件地址

project_name = 'xxxx'

#ipa 文件在 tomcat 服務器上的地址

ipa_file_tomcat_http_url = 'ftp://192.168.10.4/jenkins/iOS/' + jenkins_build_number + '/' + project_name +'_' + jenkins_build_number + '.ipa'


#獲取 ipa 文件路徑

def get_ipa_file_path():

#工作目錄下面的 ipa 文件

ipa_file_workspace_path = '/Users/Shared/Jenkins/Home/jobs/' + project_name + '/workspace/builds/' + jenkins_build_number + '/' + project_name + '_' + jenkins_build_number + '.ipa'

#tomcat 上的 ipa 文件

ipa_file_tomcat_path = '/usr/local/tomcat/webapps/' + project_name + '/static/' + jenkins_build_number + '/' + jenkins_build_number + '.ipa'


if os.path.exists(ipa_file_workspace_path):

    return ipa_file_workspace_path

elif os.path.exists(ipa_file_tomcat_path):

    return ipa_file_tomcat_path


#ipa 文件路徑

ipa_file_path = get_ipa_file_path()

print ipa_file_path

#請求字典編碼

def _encode_multipart(params_dict):

boundary = '----------%s' % hex(int(time.time() * 1000))

data = []

for k, v in params_dict.items():

    data.append('--%s' % boundary)

    if hasattr(v, 'read'):

        filename = getattr(v, 'name''')

        content = v.read()

        decoded_content = content.decode('ISO-8859-1')

        data.append('Content-Disposition: form-data; name="%s"; filename="SASDKDemo.ipa"' % k)

        data.append('Content-Type: application/octet-stream\r\n')

        data.append(decoded_content)

    else:

        data.append('Content-Disposition: form-data; name="%s"\r\n' % k)

        data.append(v if isinstance(v, str) else v.decode('utf-8'))

data.append('--%s--\r\n' % boundary)

return '\r\n'.join(data), boundary


#處理蒲公英上傳結果

def handle_resule(result):

json_result = json.loads(result)

if json_result['code'is 0:

    send_Email(json_result)


#發送郵件

def send_Email(json_result):

appName = json_result['data']['appName']

appKey = json_result['data']['appKey']

appVersion = json_result['data']['appVersion']

appBuildVersion = json_result['data']['appBuildVersion']

appShortcutUrl = json_result['data']['appShortcutUrl']

#郵件接受者

mail_receivers = ['[email protected]''[email protected]']

#根據不同郵箱配置 host,user,和pwd

mail_host = 'smtp.xxx.com'

mail_user = '[email protected]'

mail_pwd = '123'

mail_to = ','.join(mail_receivers)

msg = MIMEMultipart()


environsString = '<h3>移動端iOS安裝包</h3><p>'

environsString += '<p>內網ipa包下載地址 : ' + ipa_file_tomcat_http_url + '<p>'

environsString += '<p>外網在線安裝 : ' + 'http://www.pgyer.com/' + str(appShortcutUrl) + '<p>'

environsString += '<li><a href="itms-services://?action=download-manifest&url=https://ssl.pgyer.com/app/plist/' + str(appKey) + '">手機直接安裝</a></li>'

message = environsString

body = MIMEText(message, _subtype='html', _charset='utf-8')

msg.attach(body)

msg['To'] = mail_to

msg['from'] = mail_user

msg['subject'] = 'iOSxxx版本最新打包文件'


try:

    s = smtplib.SMTP()

    s.connect(mail_host)

    s.login(mail_user, mail_pwd)


    s.sendmail(mail_user, mail_receivers, msg.as_string())

    s.close()


    print 'success'

except Exception, e:

    print e

#############################################################


#請求參數字典

params = {

'uKey': uKey,

'_api_key': _api_key,

'file': open(ipa_file_path, 'rb'),

'publishRange''2',

}

coded_params, boundary = _encode_multipart(params)

req = urllib2.Request(url, coded_params.encode('ISO-8859-1'))

req.add_header('Content-Type''multipart/form-data; boundary=%s' % boundary)

try:

resp = urllib2.urlopen(req)

body = resp.read().decode('utf-8')

handle_resule(body)

except urllib2.HTTPError as e:

print(e.fp.read())

further readings


發佈了52 篇原創文章 · 獲贊 26 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章