一切得从上个版本的打包发布说起。

开发中本人负责了iOS包的版本发布工作。iOS打包:不就是选一下证书,再在Xcode上点几下按钮,IDE全都给你设置好流程了,有必要这么麻烦吗?

诚然,如果只是打包,在不考虑团队协同合作、打包效率、重复工作量的前提下,使用Xcode自带的打包方式当然是没问题的。但实际开发中,每次打包大概包含以下流程:

拉取最新代码(SVN或Git) → 编译通过 → 设置打包环境(开发、测试、生产等) → 导出IPA包 → 上传IPA包(App Store或者企业包上传至指定服务器)

可以看出,其中的很多步骤都是机械重复的,特别当进入测试验收阶段,有时每修复几个bug就要重新打包发布测试,比如上个版本的时候新功能主要是队友在开发,测试到最后频繁地让我打包发布,不停地打断我的工作去重复机械的事情,这简直就是在浪费人生啊!!!

虽然之前为了打包方便,我已经整理了一份脚本打包的流程(传送门:Shell脚本——Xcode脚本打包),但还是不够方便快捷,乘着新版发布后的空档期,撸出 一键打包发布系统 ,功能包括:

自动拉取Git最新代码 → 自动选择签名证书并打包导出ipa文件 → Git自动同步代码→ 自动上传ipa包(我们是企业包,上传至自己的服务器,这一步是可选的)

一键打包

一键打包资源(ArchiveSource)简介:

Code文件夹下是打包应用程序源代码

Source文件夹下是打包资源

Source文件夹:

Archive.app(打包应用程序)

ExportOptions.plist(导出ipa包配置文件)

一键打包步骤:

进入Source文件夹,启动 Archive 应用程序

选择 .xcodeproj 或 .xcworkspace 文件

修改打包版本号

启动Xcode,手动选择签名证书(如果默认签名失败的话)

点击打包

打包生成的IPA包路径说明:

../项目所在路径/Archiving/(以AppId命名的文件夹)/(App名+版本号命名的文件夹)/(以打包时间命名的文件夹)

示例:

../Archiving/(AppID)/(App名)_3_1_29/2018_04_10_11:18:51

一键打包原理

一键打包发布系统其实很简单:开发一款Mac应用,应用启动时读取本机已有的Certificates和Provisioning Profiles信息,再在应用内调用Shell脚本,主要通过脚本来实现Git同步以及打包的相关操作。

Shell脚本调用

Objective-C中调用Shell脚本可以使用 NSTask 。通过NSTask,我们可以在应用中调用另一个程序或运行一段脚本并获得其执行状态和最终结果,NSTask最为常用的一个场景是为命令行操作提供图形化的界面。

//创建一个新的Task

NSTask *optTask = [[NSTask alloc] init];

//设置调用路径

optTask.launchPath = shellPath;

//设置调用参数(被调用程序命令)

optTask.arguments = @[@"-ls"];

//创建输出Pipe

NSPipe *outputPipe = [NSPipe pipe];

[optTask setStandardOutput:outputPipe];

//创建错误输出Pipe

NSPipe *errorPipe = [NSPipe pipe];

[optTask setStandardError:errorPipe];

//执行完成Block 通知

optTask.terminationHandler = ^(NSTask *theTask) {

[theTask.standardOutput fileHandleForReading].readabilityHandler = nil;

[theTask.standardError fileHandleForReading].readabilityHandler = nil;

};

//错误输出

[[errorPipe fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {

NSData *data = [file availableData];

NSString *errorMsg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSLog(@"errorMsg = %@",errorMsg);

}];

//执行结果输出

[[outputPipe fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {

NSData *data = [file availableData];

NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

NSLog(@"msg = %@",msg);

}];

//开启执行

[optTask launch];

//阻塞直到执行完毕(NSTask默认是异步执行,如果有同步需求,可调用waitUntilExit()方法)

[optTask waitUntilExit];

[[outputPipe fileHandleForReading] closeFile];

[[errorPipe fileHandleForReading] closeFile];

自动选择签名证书

获取电脑中以iPhone Distribution和iPhone Developer命名开头的Certificates:

+ (void)loadCerListBlock:(CerListBlock)listBlock {

NSDictionary *options = @{(__bridge id)kSecClass: (__bridge id)kSecClassCertificate,

(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitAll};

CFArrayRef certs = NULL;

__unused OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)options, (CFTypeRef *)&certs);

NSArray *certificates = CFBridgingRelease(certs);

NSMutableArray *tempArray=[NSMutableArray array];

for (int i=0;i

SecCertificateRef certificate = (__bridge SecCertificateRef)([certificates objectAtIndex:i]);

NSString *name = CFBridgingRelease(SecCertificateCopySubjectSummary(certificate));

if ([name hasPrefix:@"iPhone Distribution"]||[name hasPrefix:@"iPhone Developer"]) {

[tempArray addObject:name];

}

}

listBlock(tempArray);

}

获取电脑中Provisioning Profiles:

//获取电脑中满足条件的Provisioning Profiles的路径

+ (NSArray *)getAllProvisioningProfileList{

NSString *path=[NSString stringWithFormat:@"%@/%@", NSHomeDirectory(), kMobileprovisionDirName];

NSFileManager *fileManager=[NSFileManager defaultManager];

NSArray *provisioningProfiles =[fileManager contentsOfDirectoryAtPath:path error:nil];

provisioningProfiles = [provisioningProfiles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"pathExtension IN %@", @[@"mobileprovision", @"provisionprofile"]]];

return provisioningProfiles;

}

//自定义 YAProvisioningProfile ,将profile转换为对应实体

YAProvisioningProfile *profile = [[YAProvisioningProfile alloc] initWithPath:path];

根据App ID匹配 Profile 描述文件:

//根据appBundleIdentifier匹配 profile 描述文件

YAProvisioningProfile *selectProfile = nil;

for (YAProvisioningProfile *profile in profileArray) {

// 查找与bundleIdentifier相等,描述文件不以 XC: 命名开头,而且是最新的文件

if ([profile.bundleIdentifier isEqualToString:appBundleIdentifier] && ![profile.name hasPrefix:@"XC:"] && profile.newest) {

selectProfile = profile;

break;

}

}

读写Xcode工程配置

右键 XXX.xcodeproj 文件,显示包内容,可以看到project.pbxproj。project.pbxproj存储着 Xcode 工程的各项配置参数,我们可以通过脚本来直接读取或修改其配置,执行效果等同于在Xcode的General、Build Settings中的修改。

读取App Bundler Identifier:

# Pbxproj_Path指向 project.pbxproj 文件路径

configuration=$(grep -i "PRODUCT_BUNDLE_IDENTIFIER =" ${Pbxproj_Path})

Array=($(echo $configuration))

# project.pbxproj 中存在多行PRODUCT_BUNDLE_IDENTIFIER信息,从后往前读取

Bundle_Identifier=${Array[5]}

if [ ! -n $Bundle_Identifier ]

then

Bundle_Identifier=${Array[3]}

fi

echo "Bundle_Identifier = ${Bundle_Identifier}"

修改配置信息:

修改 project.pbxproj 配置可以通过 sed 命令实现,比如将签名类型指定为Manual

key="CODE_SIGN_STYLE"

value="Manual;"

# 修改 project.pbxproj 配置,指定签名类型为手动选择

sed -i "" "s/$key =.*$/$key = $value/g" $Pbxproj_Path

如果程序成功读取到对应的证书签名,那么打包前需要修改的配置包括:

#修改 PRODUCT_BUNDLE_IDENTIFIER

changeConfiguration "PRODUCT_BUNDLE_IDENTIFIER" "${APP_ID};"

#修改 PROVISIONING_PROFILE

changeConfiguration "PROVISIONING_PROFILE" "\"${Profile_Name}\";"

#修改 PROVISIONING_PROFILE_SPECIFIER

changeConfiguration "PROVISIONING_PROFILE_SPECIFIER" "${Profile_Specifier};"

#修改 PRODUCT_NAME

changeConfiguration "PRODUCT_NAME" "${Scheme_Name};"

#修改 "CODE_SIGN_IDENTITY[sdk=iphoneos*]"

changeConfiguration "\"CODE_SIGN_IDENTITY\[sdk=iphoneos\*\]\"" '"iPhone Distribution";'

#修改 CODE_SIGN_STYLE

changeConfiguration "CODE_SIGN_STYLE" "Manual;"

#修改 ProvisioningStyle

changeConfiguration "ProvisioningStyle" "Manual;"

#修改 DEVELOPMENT_TEAM

changeConfiguration "DEVELOPMENT_TEAM" "${Development_Team};"

#修改 DevelopmentTeam

changeConfiguration "DevelopmentTeam" "${Development_Team};"

修改项目版本号:

Info.plist文件中CFBundleShortVersionString对应Version号,CFBundleVersion对应Build号,使用脚本可以直接指定版本号

# info.plist文件路径

InfoPlist_Path="${Project_Path}/${App_Name}/Info.plist"

# 如果是旧的项目,info.plist文件对应的名字为 (App_Name)-Info.plist

if [ ! -e "${InfoPlist_Path}" ];then

InfoPlist_Path="${Project_Path}/${App_Name}/${App_Name}-Info.plist"

fi

/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString ${Version_String}" ${InfoPlist_Path}

/usr/libexec/PlistBuddy -c "Set :CFBundleVersion ${Version_String}" ${InfoPlist_Path}

打包项目

# 指定打包模式为Release

Configuration="Release"

# 判断编译的项目类型是workspace还是project

if [[ ${Is_Workspace} == "YES" ]]; then

# 编译前清理工程,>> ${Log_path} 表示将日志输出写入到Log_path文件

xcodebuild clean -configuration "${Configuration}" -alltargets >> ${Log_path}

# 自定义签名,指定签名证书

if [[ ${Custom_Sign} == "YES" ]]; then

xcodebuild archive -workspace "${Workspace_Path}" \

-scheme "${Scheme_Name}" \

-archivePath "${Xcarchive_path}" \

-configuration "${Configuration}" \

PROVISIONING_PROFILE="${Profile_Name}" \

CODE_SIGN_IDENTITY="${Sign_Identity}" \

>> ${Log_path}

else

xcodebuild archive -workspace "${Workspace_Path}" \

-scheme "${Scheme_Name}" \

-archivePath "${Xcarchive_path}" \

-configuration "${Configuration}" \

>> ${Log_path}

fi

else

# 编译前清理工程

xcodebuild clean -configuration "${Configuration}" -alltargets >> ${Log_path}

if [[ ${Custom_Sign} == "YES" ]]; then

xcodebuild archive -project "${Xcodeproj_Path}" \

-scheme "${Scheme_Name}" \

-archivePath "${Xcarchive_path}" \

-configuration "${Configuration}" \

PROVISIONING_PROFILE="${Profile_Name}" \

CODE_SIGN_IDENTITY="${Sign_Identity}"\

>> ${Log_path}

else

xcodebuild archive -project "${Xcodeproj_Path}" \

-scheme "${Scheme_Name}" \

-archivePath "${Xcarchive_path}" \

-configuration "${Configuration}" \

>> ${Log_path}

fi

fi

导出IPA包

xcodebuild -exportArchive -archivePath "${Xcarchive_path}" -exportPath "${IPA_Archiving_Path}" -exportOptionsPlist "${ExportOptionsPlistPath}" >> ${Log_path}

Xcarchive_path指向上一步打包生成的*.xcarchive文件路径;IPA_Archiving_Path指向导出的*.ipa文件所在路径;ExportOptionsPlistPath对应ExportOptions.plist文件路径,ExportOptions.plist是使用xcodebuild -exportArchive指令导出ipa包时需要指定的配置文件

ExportOptions.png

compileBitcode:不上架App Store,Xcode是否启用Bitcode重新编译,默认为YES。

method:归档类型,包括app-store、ad-hoc、

package、enterprise、development以及developer-id。

provisioningProfiles:打包证书信息,包含的字典信息格式::。

uploadBitcode:上线App Store是否开启Bitcode,默认为YES。

uploadSymbols:上线App Store,是否开启符号序列化,这是与查crash相关的,默认为YES。

关于更多的xcodebuild指令,可以通过xcodebuild -help查看。

其他

其他部分还包括Git代码管理、IPA包上传、无效资源删除等,都可以直接通过NSTask执行脚本命令实现,比如拉取Git代码:git pull origin,当然前提是你本地的Git已经配置为免密操作。

如果你是使用SourceTree进行Git管理,而且是http模式,那么可以这样设置免密操作,第3步将远程仓库路径编辑为 http://用户名:密码@仓库地址 这样的方式;当然如果你是SSH模式,那么本身就支持免密码操作了。

SourceTree.png

上传IPA包。

我这里打的是企业包,所以只要将ipa文件上传到指定服务器就能够下载了(想了解更多关于企业包的下载信息,可以参照我的另一篇文章iOS如何部署企业包,以供他人下载)。一开始想着将自动上传也集成到打包程序内,但最后发现这涉及到内网间不同服务器以及账号验证过程,太过复杂暂时将这一部分阉割了……

写在最后

以上便是一键打包系统的功能讲解,想了解更多可以查看源码

欢迎点赞以及GitHub Star

最新补充

根据反馈发现:不同的Xcode版本构建的项目配置文件project.pbxproj会存在差异,另外不同用户电脑上的Provisioning Profiles也存在各种情况(比如团队中不同成员拥有不同名字的有效Profile)。

此时运行一键打包可能会失败!提示 重命名ipa包失败 或者 导出ipa包失败 ,这种情况下请关闭“默认签名”选项,同时打开Xcode在工程文件中手动选择对应打包证书后,再返回Archive.app执行一键打包。

关闭默认签名.png

应用打包截图

打包.png

服务器全自动打包,懒到极致之怒撸一键打包发布系统相关推荐

  1. 懒到极致之怒撸一键打包发布系统

    一切得从上个版本的打包发布说起. 开发中本人负责了iOS包的版本发布工作.iOS打包:不就是选一下证书,再在Xcode上点几下按钮,IDE全都给你设置好流程了,有必要这么麻烦吗? 诚然,如果只是打包, ...

  2. tim指定保存云服务器_阿里云发布 Cloud Toolkit for VS Code 新版本!一键打包部署,开发提速 8 倍...

    去年,阿里云发布了本地 IDE 插件 Cloud Toolkit,仅 IntelliJ IDEA 一个平台,就有 15 万以上的开发者进行了下载,体验了一键部署带来的开发便利.时隔一年,阿里云正式发布 ...

  3. vue项目打包后生成一个配置文件可以修改打包后的服务器api地址

    vue项目打包后生成一个配置文件可以修改打包后的服务器api地址 问题描述:vue项目打包上线之后,如果要改服务器api地址,只能在源码更改然后重新打包发布,为了解决这个问题,我们可以在static增 ...

  4. 删除vue打包大小限制_压缩Vue.js打包后的体积方法总结(Vue.js打包后体积过大问题)...

    问题 由于这次项目是在初学 Vue 之后的第一个正式项目,没有考虑到类似 路由懒加载. 按需加载的问题 ,所以呢,也算是没经验. 到了这些天,项目写得差不多了,准备放到服务器测试,才发现这个问题. 优 ...

  5. DotNet4应用程序打包工具(把DotNet4安装程序打包进你的应用程序;WINAPI开发,无dotNet环境也可顺利执行)【一】整体思路...

    先说废话 很多朋友对我写的这个系列的第五篇比较感兴趣:http://www.cnblogs.com/liulun/archive/2011/12/08/2280110.html 因为我承诺第五篇就公布 ...

  6. docker打包镜像上传_Jenkins | 一键打包部署Spring Boot 应用的Docker镜像

    一.前言 1.本文主要内容 将在项目中实际使用到的相关东西整理记录一波,同时可以方便其他同学在使用到的时候参考一下(自己也备忘),有不对的地方,欢迎指出~~ Docker部署SpringBoot 项目 ...

  7. vue打包后获取不到数据_vue 打包后,如何修改接口地址?

    bug收集:专门解决与收集bug的网站 网址:www.bugshouji.com 01背景 在 vue 项目中,我们可以通过不同的环境,访问不同的服务器. 虽然可以使用环境的切换,但是在打包成html ...

  8. 如何设置计划任务程序 每6小时运行一次_如何用 Python 打造一个全自动赚钱的 YouTube 视频发布系统并月入过万(被动收益)

    前言 这篇文章将告诉你如何通过Python打造一台全自动发布YouTube视频并专区美元收益的系统. 目前我自己用的这套系统已基本稳定运行了三个月.其中一个YouTube频道从零起步到达到最低开通获利 ...

  9. Hbuilder云打包安心打包错误问题总结(非法字符打包失败问题+win7非法选项:RSA导致不能生成签名)

    错误问题1: 在我本地win10下安心打包总是去我c盘存放缓存(网上资源查了一天,官群也加了问,没有解决方案,这个路径好像被写死在服务器上了),然后我户名是中文,就显示非法字符打包失败,如果使用的路径 ...

最新文章

  1. paip.无线路由器的无线接入WAN方式WDS设置大法
  2. 【驱动笔记9】初探IRP
  3. 解读 Q_D, Q_Q 指针
  4. 宜搭数字工厂,让订单周期缩减三分之一
  5. PHP之post请求php脚本实现
  6. 路印zkRollup AMM将在月底启动流动性挖矿
  7. 深入浅出C/C++中的正则表达式库(一)--GNU Regex Library
  8. InstallSield更新包快速入门文档
  9. Reflector 3在录制中如何添加自己的声音
  10. shell编程(精华总结版)
  11. 流媒体服务器搭建之Red5
  12. IP修改后自动还原(IP地址无法修改)
  13. 揭秘淘宝286亿海量图片存储与处理架构
  14. php7 slowlog,php7: php-fpm seems busy提示
  15. 网页版怎么连接tcp服务器,请教怎么做一个tcp客户端访问网页
  16. 前端和后端哪个工资高?前端工程师的工资,比后端低吗?
  17. 光照相关 shader
  18. 戴维·考克斯爵士去世
  19. win10网络不出现计算机列表,win10系统电脑不显示无线网络的解决方法
  20. 《Android开发卷——自定义日期选择器(二)》

热门文章

  1. UE4全场景卡通渲染
  2. SI战队人物专访-杜泽文
  3. 电脑清除dns缓存命令
  4. 计算机网络存在异常请,win10系统电脑网络出现异常的解决方法
  5. opencv 等比例缩放图像(图像尺寸不变)
  6. vite(一)前端打包工具发展史
  7. uniapp 开发移动端对接巴法云物联网平台控制ESP8266开关灯
  8. 云和恩墨精彩亮相“第五届数字中国建设峰会·云生态大会”成果展
  9. leetcode:356. 直线对称
  10. 中望CAD Linux预览版打不开