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><none></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)分类下载页面:
包管理平台:
本文来自博客园,作者:drewgg,转载请注明原文链接:https://www.cnblogs.com/drewgg/p/11654210.html