使用 jenkins 有将近三四个月了,为了支持 iOS 打包,网上有开源的工具集 fastlane,就简单总结一下,使用的心得和过程中遇到的坑。。。。

一、打包

下载 fastlane,就三个命令:

sudo gem update --system
sudo gem install bundler
sudo gem install fastlane

可以查看一下 fastlane -v的版本,我们打包主要用到gym和上传命令pilot,其他工具略有研究,但是未深入,还要提醒,我都是用的命令行格式,未使用官方推荐的 Fastfile 的方式,主要是我对 ruby 不够了解,因为我也是个大菜鸟。附上打包命令。

gym 打包,需要讲解的不多,DEVELOPMENT_TEAM 等等解释,可以详细看这里https://dpogue.ca/articles/cordova-xcode8.html
需要提醒的是 xcpretty_report_josn 可以输出oclint 需要的 xcpretty 执行之后的内容,这里可以 oclint 集成到一起

DEVELOPER_DIR="/Applications/Xcode.app" gym --workspace ${workspace_path} 
--scheme ${scheme} --clean true 
--configuration ${configuration} 
--xcargs "PRODUCT_BUNDLE_IDENTIFIER='${bundle_id}' DEVELOPMENT_TEAM='${DEVELOPMENT_TEAM}' PROVISIONING_PROFILE_SPECIFIER='${PROVISIONING_PROFILE_SPECIFIER}'" 
--export_method ${export_method} --archive_path ${archive_path} 
--codesigning_identity "${rightDistributionSign}" --xcpretty_report_json "${json_path}" 
--export_options ${ExoptionPlist} 
--output_directory ${ipa_path} --output_name ${ipa_name} || exit

二、集成

讲讲其他遇到的坑和解决方案。
由于主要使用 fastlane 中 gym 打包,但是这次需要注意的我们的需求是:


1、分别打三种包,对证书和rightProvision了解的不够透彻?

解决:把三种证书都从开发那里要过来,然后将自己的 APPLE ID 升级到开发者Admin状态,就具有了三种证书的使用权限,然后跟开发一一对接,对每个包分别对应的证书了解并整理,打包的时候一一对应上就好。这个过程中,收获的是对 xcodebuild 的 method 参数有更深的了解,其实 inhouse,adhoce,appstore 分别对应的打包方式是enterprisead-hocapp-store

2、需要更换 inhouse 的图标?

解决:之前想通过是打包的时候指向不同的target,后来的解决方案是直接替换。

for file in os.listdir(appIconPath):
    if ".png" in file:
        for appicon in os.listdir(replaceAppIconPath):
            if file == appicon:
                shutil.copy(replaceAppIconPath+'/'+appicon, appIconPath+'/'+file)

但是需要注意的是像素和尺寸需要保持一致,设计同学的帮忙很必要。

3、和 slack 对接

解决:其实我觉得 slack 真的非常好用,因为它提供了各种整合持续集成的 api,这里特别佩服我们老大对这块内容的重视,slack 和 jenkins 集成非常方便。
只是网速有点不靠谱。slack api 地址:https://api.slack.com/docs/message-guidelines
附上我写的脚本,借助 slack web api 推送信息到 slack,并且可以取出 jenkins 中执行者的信息:

#coding=utf-8
import sys
import time
import requests
import re
from slacker import Slacker

def send(job_url, job_name, starters, build_timestamp, workspace, build_cause, git_branch, change_log, text, userid):
    """执行结果推送slack,可选个人和channel"""
    api_token = "xxxx"
    slack = Slacker(api_token, 100)
    data = [{
        "fallback": "Required plain-text summary of the attachment.",
        "color": "#36a64f",
        "fields": [
            {
                "title": "Job name",
                "value": job_name,
                "short": True
            },
            {
                "title": "Build Trigger",
                "value": starters,
                "short": True
            },
            {
                "title": "Build Time",
                "value": build_timestamp,
                "short": True
            },
            {
                "title": "Build Workspace",
                "value": workspace,
                "short": True
            },
            {
                "title": "Root Build Cause",
                "value": build_cause,
                "short": True
            },
            {
                "title": "GIT_BRANCH",
                "value": git_branch,
                "short": True
            },
            {
                "title": "Job url",
                "value": job_url,
            },
            {
                "title": "CHANGE_LOG",
                "value": change_log,
            }

        ],
        "footer": "XX公司",
        "ts": time.time()
        }
    ]
    try_times = 0
    while try_times < 5:
        try:
            slack.chat.post_message(userid, text=text, attachments=data)
            return "push to slack failed"
        except Exception as e:
            time.sleep(50)
            try_times += 1
        if try_times >= 5:
            raise Exception('push to slack failed')

def get_content(job_url):
    """取出console log全部内容"""
    url = job_url + "consoleText/api/json"
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json"
    }
    response = requests.get(url, auth=("xx", "xx"))
    assert response.status_code == requests.codes.ok
    return response.content

def get_trigger(result):
    """取出构建者"""
    trigger_user = re.findall(r"Started by", result)
    trigger_other = re.findall(r"Triggered by", result)
    starters = ""
    if len(trigger_user) > 0:
        starters = re.findall(r"Started by(.*)", result)[0].lstrip()
    elif len(trigger_other) > 0:
        starters = re.findall(r"Triggered by(.*)", result)[0].lstrip()
    return starters

if __name__ == '__main__':
    if len(sys.argv) >= 2:
        job_url = sys.argv[1]
        job_name = sys.argv[2]
        build_timestamp = sys.argv[3]
        workspace = sys.argv[4] 
        build_cause = sys.argv[5]
        git_branch = sys.argv[6]
        text = sys.argv[7]
        userid = sys.argv[8]
        change_log = sys.argv[9]
        result = get_content(job_url)
        starters = get_trigger(result)
        result = get_content(job_url)
        starters = get_trigger(result)
        send_userid = ["#xx", userid]
        for i in send_userid:
            send(job_url, job_name, starters, build_timestamp, workspace, build_cause, git_branch, change_log, text, i)

然后构造请求,发送即可,详细不表。

4、因为我们 app 两个平台用的 xcode 版本不一致,需要升级到 xcode8,需要将自动管理改为手动管理证书。
sed -i '' 's/ProvisioningStyle = Automatic;/ProvisioningStyle = Manual;/g' 
"$project_path/project.pbxproj"
5、pod install 总是出现问题,分析可能是因为网络或者本机 pod 安装有问题。

若是 pod 问题,可以通过升级 gem 解决。

sudo gem update --system
sudo gem install -n /usr/local/bin cocoapods

最后

我知道我写的可能并不好,排版可能也不漂亮,但是是我们已经落地在用的工程,后续再加上持续集成 jenkins 中遇到的问题,请大家轻拍。谢谢。
再附上我们推送的截图。


↙↙↙阅读原文可查看相关链接,并与作者交流