Android、iOS、jenkins全自动化打包

主要流程思路【粗略讲处理思路,若遇到具体问题可留言交流】:

1.android的打包命令

2.ios的打包命令

3.jenkins的参数化构建

4.七牛的上传命令等

5.处理ipa的下载操作及ipa过期的监控

6.下载页面h5页面(css,js,html)

7.打包等数据存入数据库

8.分层封装

代码目录结构:

 一、pkg_common:一些基础的操作都封装在这个目录

(1)安卓的打包命令、apk处理

(2)ios的打包命令

(3)命令运行封装

(4)ipa的监控

(5)jengkins的参数处理

(6)通知处理

(7)七牛上传处理

(8)二维码处理

比如文件处理:

 

 

 拷贝文件,获取文件信息(获取包的名字,版本信息,环境信息,图标信息等)、生成下载html页面,创建下载plist文件等等

 

android的打包上传下载比较简单,重点讲一下ios的打包:

1. ios打包分为三步:清理,编译,导出ipa

(1)清理项目:xcodebuild clean -workspace %s -scheme %s -configuration %s 

(2)编译:xcodebuild archive -workspace %s -scheme %s -configuration %s -archivePath %s  

(3)导出包:xcodebuild -exportArchive -archivePath %s -exportOptionsPlist %s -exportPath %s

贴一个导出包的代码:

 1 from datetime import datetime
 2 from pkg_common.cmd import common_run_cmd as cmd
 3 from pkg_common.handle_file import find_file as fd
 4 
 5 
 6 def export_ipa(xch, plist, ext_path, log, wp):
 7 
 8     """
 9     :param xch: xcarchive文件及路径
10     :param plist: ExportOptions.plist 文件及路径
11     :param ext_path: 导出ipa包的路径
12     :param log: 日志文件
13     :param wp: 执行cmd的目录
14     :return:
15     """
16 
17     """
18     xcodebuild -exportArchive -archivePath /Users/Work/iOS/exrmle/AppStore/20190929171231/covermedia.xcarchive -exportOptionsPlist /Users/Work/iO
19     S/exrmle/AppStore/ExportOptions.plist  -exportPath /Users/Work/iOS/exrmle/AppStore/20190929171231/ > /Users/Work/iOS/exrmle/log/03export.log
20     """
21     cmd_export = "xcodebuild -exportArchive -archivePath %s -exportOptionsPlist %s -allowProvisioningUpdates -exportPath %s > %s" % (xch, plist, ext_path, log)
22     print(datetime.now(), '** Export Begin **')
23     cmd.run_cmd(cmd_export, wp).read()
24     with open(log, 'r', encoding='utf-8') as f:
25         for i in f.readlines():
26             if 'EXPORT SUCCEEDED' in i:
27                 print(datetime.now(), '** Export Succeed **')
28                 break
29         else:
30             error_list = fd.find_log_error(log)
31             for el in error_list:
32                 print(el)
33             raise AssertionError('** Export Error **')

 

 

ExportOptions.plist文件内容:
分methon的不同有4中类型:ad-hoc、app-store、development、enterprise。
不知道怎么构造的化,用xcode直接导出包后就生成了,compileBitcode最好设置为false,不然容易出错不说,导出时候还很慢。
 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 3 <plist version="1.0">
 4 <dict>
 5     <key>compileBitcode</key>
 6     <false/>
 7     <key>method</key>
 8     <string>ad-hoc</string>
 9     <key>signingStyle</key>
10     <string>automatic</string>
11     <key>stripSwiftSymbols</key>
12     <true/>
13     <key>teamID</key>
14     <string>SWM****5PP</string>
15     <key>thinning</key>
16     <string>&lt;none&gt;</string>
17 </dict>
18 </plist>

 

 包导出后,可以上传到第三方如蒲公英、fir下载,最好还是自己搭建下载页面,如果有七牛云的话,更简单,注意:ipa下载需要是https的。

ipa不像apk一样直接上传后获取下载地址就可下载,需要生成下载plist文件才可以下载到ipa的包。

下载plist文件样式:

<plist version="1.0">
<dict>
    <key>items</key>
    <array>
        <dict>
            <key>assets</key>
            <array>
                <dict>
                    <key>kind</key>
                    <string>software-package</string>
                    <key>url</key>
                    <!-- ipa下载地址 -->
                    <string>https://pkgcdn.***r.cn/pkg/cover/ipa/rm_cg_iOS/7.0.0/***.ipa</string>
                </dict>
                <dict>
                    <key>kind</key>
                    <!-- 512 x 512 像素的 PNG 图像 -->
                    <string>full-size-image</string>
                    <key>needs-shine</key>
                    <false/>
                    <key>url</key>
                     <string> </string>
                </dict>
                <dict>
                    <key>kind</key>
                    <!-- 57 x 57 像素的 PNG 图像,在下载和安装过程中显示 -->
                    <string>display-image</string>
                    <key>needs-shine</key>
                    <false/>
                    <key>url</key>
                    <string> </string>
                </dict>
            </array>
            <key>metadata</key>
            <dict>
                <!-- bundle ID -->
                <key>bundle-identifier</key>
                <string>com.C***anCha</string>
                <!-- APP 版本号 -->
                <key>bundle-version</key>
                <string>7.0.0</string>
                <key>kind</key>
                <string>software</string>
                <key>subtitle</key>
                <string>7.0.0</string>
                <!--下载和安装过程中显示的应用的名称 -->
                <key>title</key>
                <string>rm_cg_iOS</string>
            </dict>
        </dict>
    </array>
</dict>
</plist>   
    

 

需要维护ipa下载地址、bundle ID、版本信息等即可

然后把这个plist文件上传的服务器,下载地址生成:

“itms-services://?action=download-manifest&url=”加上plist的下载地址,如:

itms-services://?action=download-manifest&url=https://pk**n.thecover.cn/pkg/cover/plist/rm_cg_iOS/7.0.0/rm**7.plist

好了,iOS的打包全流程就这样~搞明白了还是很简单的。

当然,ipa到此还没有结束,ipa还需要上传到苹果市场,也可以通过命令解决。

(1)上传appstore验证:xcrun altool --validate-app -f %s -t ios --apiKey %s --apiIssuer %s --verbose

(2)上传appstore:xcrun altool --upload-app -f %s -t ios --apiKey %s --apiIssuer %s --verbose 

这两个命令前提是需要到app store connect 用户-密钥去配置:

apiKey:密钥ID

apiIssuer:issuer ID

 

配具体步骤:

 

登录iTunesConnect --->用户与访问--->密钥,至此,生成相应身份的密钥,再将私钥下载下来

下载后需要放到一个固定目录下,'./private_keys'或者'~/private_keys' 或者'~/.private_keys' 

或者'~/.appstoreconnect/private_keys'目录下

到此,ipa的包才算处理完成

 

与iOS相比,Android的打包就简单得多:

1.清理项目:./gradlew clean 

2.编译:./gradlew assemble%s%s

3上传:普通上传,不需特殊处理

4.下载:上传后得到的下载地址即可

 

二、pkg_controller

(1)android的打包流程

(2)ios的打包流程

贴一个ios的打包流程,当然,还可以进一步封装,后续再优化优化。

  1 # coding = utf-8
  2 # ***iOS打包
  3 
  4 
  5 import sys
  6 sys.path.append('..')
  7 from datetime import datetime
  8 from PIL import Image
  9 from pkg_dao import deal_sql_data as ds
 10 from pkg_common.qiniu import common_qiniu as qn
 11 from pkg_common.notice import common_notice as notice
 12 from pkg_common.qr_code import common_qr_code as qr_code
 13 from pkg_common.jenkins import get_jenkins_parameter as get_jenkins
 14 from pkg_common.ios import ios_modify_file as mf
 15 from pkg_common.ios import ios_build_ipa as build
 16 from pkg_common.ios import ios_export_ipa as export
 17 from pkg_common.ios import ios_clean_workspace as clean
 18 from pkg_common.ios import ios_pod_update as pod
 19 from pkg_common.handle_file import zntopy as pin
 20 from pkg_common.handle_file import deal_file as df
 21 from pkg_common.handle_file import find_file as find
 22 from pkg_common.handle_file import common_pkg_info as pkg
 23 from pkg_common.handle_file import common_upload as upload
 24 from pkg_common.handle_file import create_plist as ipa_plist
 25 from pkg_common.ipa_monitor import copy_ipa_monitor as monnitor
 26 from pkg_common.handle_file import create_download_html as down_html
 27 
 28 
 29 # 需要重jenkins获取的参数
 30 jks = [
 31     'WORKSPACE',    # jenkins工作路径
 32     'build_environment',    # 打包环境 test/product
 33     'build_configuration',  # 打包模式 Release/Debug
 34     'export_environment',   # 导出包的模式AdHoc/AppStore/Development/Enterprise
 35     'upload_app_store',   # 上传app store
 36     'BUILD_NUMBER',         # 打包次数
 37     'is_direct_export'   # 是否跳过编译直接导出
 38     ]
 39 
 40 dic_jks = get_jenkins.get_jenkins(jks)
 41 wk = dic_jks['WORKSPACE']
 42 be = dic_jks['build_environment']
 43 ee = dic_jks['export_environment']
 44 ua = dic_jks['upload_app_store']
 45 cfg = dic_jks['build_configuration']
 46 ie = dic_jks['is_direct_export']
 47 bid = dic_jks['BUILD_NUMBER']
 48 
 49 
 50 # wk = "/Users/drew/.jenkins/workspace/iOS**"
 51 # be = 'product'
 52 # ee = 'appstore'
 53 # ua = 'T'
 54 # cfg = 'Release'
 55 # ie = 'T'
 56 # bid = '115'
 57 
 58 
 59 class AppPackaging:
 60 
 61     """
 62     iOS打包类
 63     """
 64 
 65     def __init__(self, ex_path, con_fig, key_word):
 66 
 67         """
 68         变量自定义方法
 69         :param ex_path: 输出输入的目录
 70         """
 71 
 72         # 日志目录
 73         self.lc = ex_path + 'log/01_clean.log'
 74         self.lp = ex_path + 'log/02_uppod.log'
 75         self.lb = ex_path + 'log/03_build.log'
 76         self.le = ex_path + 'log/04_export.log'
 77         self.lv = ex_path + 'log/05_validate.log'
 78         self.lu = ex_path + 'log/06_upstore.log'
 79         self.lx = ex_path + 'log/07_xcarchive.log'
 80 
 81         # 输出文件子目录【存放.plist文件和每次编译后的文件】
 82         ex_c = ''
 83         if ee.upper() == 'ADHOC':
 84             ex_c = ex_path + 'AdHoc/'
 85         if ee.upper() == 'APPSTORE':
 86             ex_c = ex_path + 'AppStore/'
 87         if ee.upper() == 'DEVELOPMENT':
 88             ex_c = ex_path + 'Development/'
 89         if ee.upper() == 'ENTERPRISE':
 90             ex_c = ex_path + 'Enterprise/'
 91 
 92         # 输出孙目录【存放.xcarchive,ipa文件】
 93         self.ex_g = ex_c + datetime.now().strftime('%Y%m%d%H%M%S') + '/'
 94         # plist文件及路径
 95         self.plist = ex_c + 'ExportOptions.plist'
 96 
 97         # 查找当前目录下需要修改环境的配置文件
 98         self.f, self.f_p = find.find_file(con_fig, wk)
 99         # 定位关键词:指定关键词以定位修改文件的地方
100         self.key_word = key_word
101         # workspace名称
102         self.ws = find.find_file('*.xcworkspace', wk)[0][0]
103 
104     def ios_pra(self):
105         """
106         定义iOS打包参数
107         :return:
108         """
109 
110         # workspace文件
111         wsn = self.ws + '.xcworkspace'
112         # scheme名称
113         schn = self.ws
114         # 编译后生成的xcarchive文件
115         xch = self.ex_g + self.ws + '.xcarchive'
116         # 需要上传的ipa文件
117         ipa_path = self.ex_g + self.ws + '.ipa'
118         return wsn, schn, xch, ipa_path
119 
120     def packaging(self, dic, pod_sign=0, copy_sign=0):
121         """
122 
123         :param dic:
124         :param pod_sign:
125         :param copy_sign:
126         :return:
127         """
128 
129         desc = ''
130         push_file = ''
131         if be.upper() == 'TEST':
132             desc = '内网测试环境'
133             push_file = wk + dic['str_test']
134         if be.upper() == 'PRODUCT':
135             desc = '外网正式环境'
136             push_file = wk + dic['str_product']
137         if ee.upper() == 'APPSTORE':
138             desc = '外部发布上线【非安装版本】'
139             push_file = wk + dic['str_product']
140         push_file_to = wk + dic['str_to']
141 
142         if ie == "F":
143             # 删除xcode缓存文件
144             clean.del_file(self.ws + '*')
145             # 清理项目
146             clean.clean_xcworkspace(dic['wsn'], dic['schn'], cfg, self.lc, wk)
147             # 修改配置文件
148             mf.modify_file(be, self.key_word, self.f_p[0])
149             # 配置阿里推送文件目录
150             if dic['push_sign'] == 1:
151                 # 拷贝配置
152                 mf.copy_file(push_file, push_file_to)
153             if pod_sign == 1:
154                 pod.update_pod(self.lp, wk)
155             # 编译
156             build.build_ipa(dic['wsn'], dic['schn'], cfg, dic['xch'], self.lb, wk)
157             # 编译成功后把.xcarchive文件路径写入log文件
158             df.write_file(self.lx, dic['xch'])
159             # 导出ipa
160             export.export_ipa(dic['xch'], self.plist, self.ex_g, self.le, wk)
161         else:
162             # 读取上次编译的.xcarchive文件目录
163             xch = df.read_file(self.lx)
164             # 导出ipa
165             export.export_ipa(xch, self.plist, self.ex_g, self.le, wk)
166 
167         # --------------------------------参数定义-----------------------------
168 
169         # ipa名称
170         ipa_name = self.ws + bid + '.ipa'
171         # plist 名称
172         plist_name = dic['project_name'] + bid + '.plist'
173         # plist文件路径
174         file_plist_path = dic['path_plist'] + plist_name
175         # icon 名称
176         img_name = dic['project_name'] + bid + 'ios_icon.png'
177         # 当前包二维码名称
178         qr_name = dic['project_name'] + bid + 'ios'
179         # 当前包二维码上传七牛key名称
180         qn_qr_name = qr_name + '.png'
181         # --------------------------------参数定义-----------------------------
182 
183         # -------------------------上传七牛生成二维码及下载页面--------------------
184 
185         # 获取APP版本等信息
186         app_image, app_bundle_id, app_version, app_name = pkg.get_ipa_info(dic['app_icon'], wk)
187 
188         # --------------------------------参数定义-----------------------------
189         # 通用下载html名称
190         html_name = pin.zn_to_py(app_name) + "_gdh.html"
191         # html文件路径+
192         file_html_path = dic['path_html'] + html_name
193         adg = pin.zn_to_py(app_name) + bid + "ios_adg.html"
194         app_dg_html_path = dic['path_html'] + adg
195         # 通用html生成二维码名称
196         qr_name_html = dic['project_name'] + '_html' + bid + 'ios'
197         # 通用html二维码上传七牛key名称
198         qn_qr_name_html = qr_name_html + '.png'
199         # --------------------------------参数定义-----------------------------
200         # 上传icon图标
201         img_key, img_download_url = qn.qiniu_upload(app_image, dic['project_name'], app_version, img_name, 'img')
202         # 上传ipa文件
203         ipa_key, ipa_download_url = qn.qiniu_upload(dic['ipa_path'], dic['project_name'], app_version, ipa_name, 'ipa')
204         # 生成plist文件
205         ipa_plist.create_plist(ipa_download_url, app_bundle_id, app_version, dic['project_name'], file_plist_path)
206         # 上传plist
207         plist_key, plist_download_url = qn.qiniu_upload(file_plist_path, dic['project_name'], app_version, plist_name, 'plist')
208         # 生成ipa下载地址
209         ios_download_path = 'itms-services://?action=download-manifest&url=%s' % plist_download_url
210 
211         # 查询数据库
212         sql_where = {
213             'app_name': app_name,
214             'app_type': 'iOS'
215         }
216         sql1, sql2, sql_a_q, sql_a_o, sql_i_t, sql_i_p = ds.rs(sql_where)
217         app_related = ds.read_sql(sql1)
218         pkg_path = ds.read_sql(sql2)
219         if len(app_related) == 0:
220             app_related = '0'
221         else:
222             app_related = app_related[0][0]
223         if len(pkg_path) == 0:
224             pkg_path = ''
225         else:
226             pkg_path = pkg_path[0][0]
227 
228         saq = ds.read_sql(sql_a_q)
229         sao = ds.read_sql(sql_a_o)
230         sit = ds.read_sql(sql_i_t)
231         sip = ds.read_sql(sql_i_p)
232         saq_html = down_html.create_table(saq)
233         sao_html = down_html.create_table(sao)
234         sit_html = down_html.create_table(sit)
235         sip_html = down_html.create_table(sip)
236 
237         adg_img = Image.open(app_image)
238         adg_img = adg_img.resize((120, 120), Image.ANTIALIAS)
239         qr_adg_name = pin.zn_to_py(app_name) + bid + 'ios.png'
240         save_file = dic['path_qr_code'] + qr_adg_name
241         adg_img.save(save_file, quality=100)
242         qr_adg_key, qr_adg_download_url = qn.qiniu_upload(save_file, dic['project_name'], app_version, qr_adg_name, 'img')
243         app_dg = {
244             'type': 'ios',
245             'cfg': cfg,
246             'app_version': app_version,
247             'bid': bid,
248             'desc': desc,
249             'dg_url': ios_download_path,
250             'app_dg_html_path': app_dg_html_path,
251             'app_name': app_name,
252             'icon_url': qr_adg_download_url
253         }
254         # 生成app单次下载html
255         down_html.create_html_app_dg(app_dg)
256         # 上传html
257         a_key, adg_url = qn.qiniu_upload(app_dg_html_path, dic['project_name'], app_version, adg, 'html')
258         # 生成二维码
259         qr_save_img = qr_code.create_qr_code(adg_url, app_image, qr_name, dic['path_qr_code'])
260         # 上传二维码图片
261         qr_code_key, qr_code_download_url = qn.qiniu_upload(qr_save_img, dic['project_name'], app_version, qn_qr_name, 'img')
262         # 替换https为http(钉钉不支持https)
263         qr_code_download_url = qr_code_download_url.replace('https', 'http')
264 
265         qr_dic = {
266             'qr_code_download_url': qr_code_download_url,
267             'ipa_download_url': ios_download_path,
268             'apk_download_url': pkg_path,
269             'file_html_path': file_html_path,
270             'app_name': app_name,
271             'app_version': app_version,
272             'bid': bid,
273             'v_code': '0',
274             'type': 'iOS',
275             'cfg': cfg,
276             'desc': desc,
277             'saq_html': saq_html,
278             'sao_html': sao_html,
279             'sit_html': sit_html,
280             'sip_html': sip_html
281         }
282 
283         # 生成通用下载html
284         down_html.create_html(qr_dic)
285         # 上传html
286         html_code_key, html_code_download_url = qn.qiniu_upload(file_html_path, dic['project_name'], app_version, html_name, 'html', 1)
287         # 通用html生成二维码图片
288         save_html_img = qr_code.create_qr_code(html_code_download_url, app_image, qr_name_html, dic['path_qr_code'])
289         # 上传二维码图片
290         qr_code_key_html, qr_code_download_url_html = qn.qiniu_upload(save_html_img, dic['project_name'], app_version, qn_qr_name_html, 'img')
291         qr_code_download_url_html = qr_code_download_url_html.replace('https', 'http')
292 
293         # ------------------------上传七牛生成二维码及下载页面-----------------------
294 
295         # 生成钉钉数据
296         app_info = {
297             'buildName': app_name,
298             'buildVersion': app_version,
299             'buildBuildVersion': bid,
300             'buildUpdated': datetime.now(),
301             'buildUpdateDescription': desc,
302             'buildShortcutUrl': html_code_download_url,
303             'buildQRCodeURL': qr_code_download_url_html
304         }
305 
306         # 通知钉钉
307         notice.ding_talk(app_info, 'iOS', cfg)
308 
309         # 插入数据库
310         sql_dic = {
311             'app_name': app_name,
312             'app_type': 'iOS',
313             'bundle_id': app_bundle_id,
314             'build_num': bid,
315             'version_id': 0,
316             'version': app_version,
317             'icon_path': img_download_url,
318             'qr_path': qr_code_download_url,
319             'pkg_path': ios_download_path,
320             'build_env': be,
321             'build_type': cfg,
322             'export_env': ee,
323             'app_related': app_related,
324             'html_download1': html_code_download_url,
325             'html_download2': adg_url
326         }
327         ds.write_sql(ds.ws(sql_dic))
328         ds.close_mysql()
329 
330         # 上传AppStore
331         if ua == 'T':
332             # 验证
333             upload.up_validate(dic['ipa_path'], dic['ios_key'], dic['ios_issuer'], self.lv)
334             # 上传
335             upload.up_app_store(dic['ipa_path'], dic['ios_key'], dic['ios_issuer'], self.lu)
336 
337         if copy_sign == 1:
338             # 拷贝ipa文件
339             monnitor.copy_ipa(dic['ipa_path'], app_name)

 

流程根据自己需要处理,这里的代码涉及数据库交互,数据相关交互还可以进一步封装处理,这样代码可读性更好一点。

三、pkg_dao

主要处理数据交互与数据库配置信息等

不详细描述

四、pkg_view

android打包实现页面

ios打包实现页面

 

 五、resource

资源模块

(1)配置文件

(2)css文件

(3)html文件

(4)plist文件

(5)其他

六、jenkins的配置

主要用的是自由模式的参数化构建

 

jenkins再加上邮件监控,打包失败自动发布邮件,网上有各种邮件模版:

 

七、打包成功后通知钉钉

网上有很多例子,这不讲了

 

败邮件通知:

 

 

效果:

(1)钉钉通知:

 

 

 

 

(2)下载页面

 

 

 

 

 (3)分类下载页面:

 

 

包管理平台:

 

 

 

posted @ 2019-10-11 14:56  drewgg  阅读(914)  评论(4编辑  收藏  举报