美文网首页iOS开发技巧iOSiOS程序犭袁
一步一步构建iOS持续集成:Jenkins+GitLab+蒲公英

一步一步构建iOS持续集成:Jenkins+GitLab+蒲公英

作者: east520 | 来源:发表于2015-07-30 15:41 被阅读47111次

    什么是持续集成

    持续集成是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    为什么使用持续集成

    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 = ['xxx@xxx.com', 'xxx@xxx.com']
    #根据不同邮箱配置 host,user,和pwd
    mail_host = 'smtp.xxx.com'
    mail_user = 'xxx@xxx.com'
    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())

    相关文章

      网友评论

      • 1c907eb644de:楼主,我能请问下,这里头的 Keychain 具体是干什么用的么
      • 2151da385f88:您好,我在配置Provisioning Profiles时,为什么只有填写路径的选项呢?
      • 司司_elena:我的Jenkins里面一直找不到Post-Build Script Plug-in ,有人遇到过这个问题么,请求支援!!:confounded: :sob:
      • fir_im官方:楼主写的很详细。
        推荐一下 flow.ci https://flow.ci
        iOS 持续集成一步就够了:)
      • 巩固2022:源代码用coding,怎么用
      • 9c070052b0e7:楼主,您好,我再配置git的时候总是 returned status code 128:
        stdout:
        stderr: remote: HTTP Basic: Access denied
        fatal: Authentication failed for 'https://gitlab.com/QinYaffeei/LinkedSports.git/
        是不是SSH配置错了呢?SSH配置的时候用户名和密码填什么呢,SSH信息是不是和gitlab里填写的一样呢
      • 小学生课代表:在Customize Jenkins 这个界面,我选择左面那个模块install suggested plugins 时报这个错误:An error occurred during installation: No such plugin: dashboard-view..要是选择selected plugins to install 然后全选之后点击install也会进到这个错误的页面....这是什么原因?
      • 大号鱼骨头:楼主,我咋不能选择钥匙串和证书呢。那个钥匙串的插件我也下载了。
      • 66a182e73ce4:转到博客保存下
      • 梦幻华:xcodebuild: error: The project named "aiBenDi" does not contain a scheme named "aiBenDi_test". The "-list" option can be used to find the names of the schemes in the project.
        你好,说我的工程中找不到aiBenDi_test这个文件,请问怎么解决?
      • 85edd583c523:大神:
        xcodebuild -project ${WORKSPACE}/testForiOS/testForiOS.xcodeproj -scheme "testForiOS" ,文件名 总是找不到,我用的是cocoapod 将上面的地址换成xcodebuild -project ${WORKSPACE}/KanKan.xcodeproj -scheme "KanKan" 可以运行,但是就是会缺少文件。我想问的是如果是用了cocoapod实现的项目,xcodebuild -project ${WORKSPACE}/KanKan.xcodeproj 这句改怎么写呢.
        孤独感爆棚:@hongxin1121 你好,请问这个问题解决了吗
      • manajay:error: no provisioning profile matches 'Cangjiaquan_Development_com.mobileprovision'
        ** EXPORT FAILED **

        Build step 'Execute shell' marked build as failure
        [PostBuildScript] - Execution post build scripts.
        [PostBuildScript] Build is not success : do not execute script

        用的 SVN 蒙蔽了
      • HizB:进入Keychains and Provisioning Profiles Management页面,点击“浏览”按钮,分别上传自己的keychain和证书。

        请问下这个keychain 文件是什么文件?
      • iOS超级洋:请问一下怎么解决啊
      • iOS超级洋:/bin/sh -c /Users/huyang/Library/Developer/Xcode/DerivedData/zjsy-ddwmbjmaplsowjfwjpbbwxkalcjy/Build/Intermediates/zjsy.build/Release-iphoneos/zjsy.build/Script-132534E09255673A2629FF6B.sh
        /Users/huyang/zidonghua/jenkins/workspace/zjsy/Pods/IQKeyboardManager/IQKeyBoardManager/Resources/IQKeyboardManager.bundle
        error: Resource "/Users/huyang/Library/Developer/Xcode/DerivedData/zjsy-ddwmbjmaplsowjfwjpbbwxkalcjy/Build/Products/Release-iphoneos/JSBadgeView/JSBadgeView.bundle" not found. Run 'pod install' to update the copy resources script.

        ** BUILD FAILED **


        The following build commands failed:
        PhaseScriptExecution [CP]\ Copy\ Pods\ Resources /Users/huyang/Library/Developer/Xcode/DerivedData/zjsy-ddwmbjmaplsowjfwjpbbwxkalcjy/Build/Intermediates/zjsy.build/Release-iphoneos/zjsy.build/Script-132534E09255673A2629FF6B.sh
        (1 failure)
        Build step 'Xcode' marked build as failure
        Finished: FAILURE
        voidxin:请问这怎么解决的?
        iVikings:@iOS超级洋 请问下你这个问题解决了么?怎么解决的呢?
      • Crazy2015:你好,我现在遇到一个问题。我的worspace下面有两个project ,一个project为静态库(带有源代码),另一个project 要用到上面的静态库。之前我们的做法就是将带有源码的静态库的project 打包编译成.a的静态库和.bundle文件,然后通过引用的方式让另一个project 使用。但是Jenkins自动打包的话,这种情况改怎么使用呢?
      • 勃朗峰:/Library/Caches/com.apple.xbs/Sources/IDEXcode3ProjectSupport/IDEXcode3ProjectSupport-9523/Xcode3Core/LegacyProjects/Frameworks/DevToolsCore/DevToolsCore/BuildSystem/DependencyGraph/XCDependencyGraph.mm:688
        Details: unable to write dependency graph: You don’t have permission to save the file “aTest.build” in the folder “Release-iphoneos”.
        我直接在目录下用脚本打包是没问题的,但是配置到Jenkins上面就会出现上面的问题,为什么会提示没权限呢?
      • Colin_狂奔的蚂蚁:方便加下QQ吗?我的qq:1159991642
      • Colin_狂奔的蚂蚁:xcodebuild: error: The workspace named "IOS-WeidaiCreditLoan" does not contain a 我报这个错:
        scheme named "./IOS-WeidaiCreditLoan/IOS-WeidaiCreditLoan". The "-list" option can be used to find the names of the schemes in the workspace.
        Build step 'Xcode' marked build as failure
        Finished: FAILURE
        请问是什么问题???
      • Colin_狂奔的蚂蚁:我遇到了一个问题就是在Keychains and Code Signing Identities选择时,Keychain和Code Signing Identity 选项都选不了,你知道是什么问题吗?
        Colin_狂奔的蚂蚁:@eaedc 不能选是因为有个login.keyachins文件需要设置进去
        eaedc:@狂奔蚂蚁 你好这个问题 解决了吗,我也是不能选
        Koson:@狂奔蚂蚁 估计是Jenkins使用的是系统目录,然而打包的时候时候使用的是个人帐户。试试在Keychain(钥匙串)中把登录环境下的证书配置右键 > 复制,在系统的那个钥匙串下面进行粘贴。如果有mobile provision profile亦如是,通过终端命令进行一次拷贝(或者直接在finder中操作)。
      • Dennis_me:Jenkins 处理https的svn,真是蛋都碎了
      • FortunateStone:你好,我在构建pod+Jenkins遇到了问题,想请教请教、可以给个QQ吗?我得QQ是99249394
        ZhaoXianSheng:@FortunateStone 我这刚解决,权限问题,以后还会有类似问题,有个办法可以一劳永逸。
        FortunateStone:@FortunateStone 我在插件中设置了文件,但是总说找不到
        FortunateStone:@FortunateStone FATAL: The path to store mobile provisioning profile files on the master is not configured. Go the plugin main configuration page and give the path.
        java.io.IOException: The path to store mobile provisioning profile files on the master is not configured. Go the plugin main configuration page and give the path.
      • 瘋魔::target:
      • 向晚forever:菜鸟,想问下在mac下安装了jekins为什么“http://localhost:8888”,一直访问不了。本机的防火墙也关掉了
        Chenchenxs:@向晚forever 本地的端口是8080
      • GLimited:我使用ftp发布阶段,为什么总是报失败呢?你碰到过吗
        FTP: Connecting with configuration [ftp2] ...
        FTP: Disconnecting configuration [ftp2] ...
        ERROR: Exception when publishing, exception message [Software caused connection abort: socket write error]
      • Haer不变:ld: warning: directory not found for option '-L/Users/liushi/.jenkins/jobs/amwy-ios/workspace/ThirdSDK/WeChatSDK/SDKExport'
        ld: library not found for -lAFNetworking
        clang: error: linker command failed with exit code 1 (use -v to see invocation)

        ** BUILD FAILED **


        The following build commands failed:
        Ld build/aimeiwuyou.build/Release-iphoneos/aimeiwuyou.build/Objects-normal/armv7/aimeiwuyou normal armv7
        Ld build/aimeiwuyou.build/Release-iphoneos/aimeiwuyou.build/Objects-normal/arm64/aimeiwuyou normal arm64
        (2 failures)
        Build step 'Xcode' marked build as failure
        Finished: FAILURE



        您好,配置好后打包提示以上错误?能帮忙解决下吗
        Koson:@Haer不变 如果使用了Cocoapods,可以再构建步骤的Xcode前加一个执行脚本:pod install,这样会先更新第三方类库。
        e000a9279be3:你们项目用了pod吧 构建使用workspace 指定schema
        696016f77fc2:@Haer不变 你这个代码是SVN上弄下来的,静态库缺少.a文件,能修改SVN上的代码就commit的时候加上去,不能的话,对比能运行的代码,show in finder,复制AFNetworking的.a文件到workspace下的代码中,然后根据编译结果,添加缺少的项
      • Jisen:楼主你好,我的job构建步骤和你一样,也是使用Keychains and Provisioning Profiles Management,然后使用shell脚本打包的ipa,但是ipa却在手机上安装不了,不知道什么原因?
        Koson:@木棠 签名问题,做CI的电脑上安装了证书,可以默认使用Xcode的配置即可。
      • Lithium:10.10后也可以用 Jenkins 打包生成 ipa,如果遇到的是和我一样的问题的话,把 Code Signing Resource Rules Path 设置成 $(SDKROOT)/ResourceRules.plist 就好了。
        2560dedfd504:@木棠 我打包Debug、Release都成功,但Release不能安装、不能上传。上传时报错
        ERROR ITMS-90339
        Jisen:@Lithium 请问你打包ipa是什么问题?10.10以上Jenkins配置Pack application and build .ipa可以打包成功?
        5fc93c4f4f57:@Lithium 回复楼主,10.10可以打包了. 回复7楼,有更方便的解决方案,见stackoverflow : http://stackoverflow.com/questions/32504355/error-itms-90339-this-bundle-is-invalid-the-info-plist-contains-an-invalid-ke/32762413#32762413
      • 笨驴爱吃胡萝卜:{u'message': u'Api Key could not be empty', u'code': 200, u'data': []}
        提示我这个,可是我的_api_key 设置了
      • Jay神:必须赞!!另外我想请教一下,我在安装了git-plugin后 填写URL,每次构建的时候都是timeout了。不知道怎么出现此情况。网络是通的,使用sourcetree pull代码也是ok的
        b9293939bd51:@Jay神 添加Additional Behaviours-Advanced clone behaviours&Advanced checkout behaviours 超时时间设置长一些
      • 43f853fdf719:楼主是hw的么?
      • ted005:不错的实战总结 学习

      本文标题:一步一步构建iOS持续集成:Jenkins+GitLab+蒲公英

      本文链接:https://www.haomeiwen.com/subject/clvmqttx.html