#——————————————–
# 2018/05/16
# 版本:3.0.0
# 1. 自动匹配授权文件和签名(移除config.plist配置)
# 2. 优化授权文件匹配算法,取有效期最长授权文件
# 3. 调整脚本参数,详见-h
# 4. 优化代码
# 5. 兼容长参数
# 6. 增加全局配置文件user.xcconfig
#——————————————–
最新代码详见 github源码
下文是 开发IPABuildShell第一个版本写的,当前版本已经有很大的变化 -2018/05/16
前言
作为iOS开发者,每次真机测试我都习惯在Build Settings
中设置签名信息Code Signing Identity 、Development Team、 Provisioning Profile、Provisioning Profile(Deprecated)等,并会在General
中去掉勾选Automatically manage signning即设置为手动管理证书!所以,在实现自动打包脚本时,我也会通过修改这里的配置来完成所需的签名等配置。本文的目的主要是了解Xcode Project的配置是如何组织的,以及如何用脚本替我们完成Target的配置达到与手动配置一致的效果,其次是实现脚本自动打包功能! github源码
工欲善其事必先利其器
我们知道Xcode Project 的配置文件是project.pbxproj
如何更方便查看project.pbxproj文件
以往很长一段时间,需要在project.pbxproj文件中查找或者编辑某些配置时我都会使用Subline Text、文本编辑器、xcode打开,他们的效果都一样。
上面的方式如果需要查找某个value或key的层级关系这就悲剧了,接近上万的行数!行数太多不能一屏看完,拖着拖着就不知道拖到那里了。
如果project.pbxproj文件能像Plist文件那样展开浏览就好了,于是尝试修改project.pbxproj的后缀名字为”.plist”即project.plist,使用xcode双击打开!果然!!!果然!!!果然!!!舒服呀,是不是有种被治愈的感觉!
探究project.pbxproj
对于以plist形式展示的project.pbxproj,强烈的激起了我对它的进一步分析的想法!
随意浏览了一下,发现:rootObject :0CA805261D7D1A1900EEC7DA
这应该是个入口,下一步搜索”0CA805261D7D1A1900EEC7DA“
这里的targets应该就是正好对应我们工程中的targets
这里的ProvisioningStyle 应该就对应Target - General -Automatically manage signning
继续搜索targets-item0对应的:0CA8052D1D7D1A1900EEC7DA
上面搜索展示出得列表,从名字来看很容易就能猜测出来其代表的意义,buildConfigurationList应该就是配置列表了。搜索:buildConfigurationList:0CA805451D7D1A1900EEC7DA。
buildConfigurations的两个tiem其实分表达标release和debug配置,我们选择第一项继续搜索一下:0CA805461D7D1A1900EEC7DA
由上图可以看出来,图中的key正好对应Xcode Project 中我们在Build Settings中所看到的选项配置!是否好奇,为什么在这里看到的配置那么少?经过测试,发现一些默认的配置,如果你在Xcode Project没有发生过修改,它是不会写在project.pbxproj中,所以我猜测就是这个原因了!至于Xcode Project 中的其他配置,按照上面的方法应该都可以在project.pbxproj中一一找到,这里就不重复了!
在Let’s Talk About project.pbxproj文章中指出了project.pbxproj的内容规则
project.pbxproj 使用 UUID 作为交叉引用的索引,保证每个配置信息对象的唯一性。因为 UUID 根据机器硬件和时间戳生成,避免了多人在同一时间段操作修改工程文件带来的问题。也就是说工程中每项配置对象都有个唯一的 UUID,然后其他配置对象想引用某个配置对象直接使用它的 UUID 即可。这就跟我们编程时使用指针指向某个对象的地址一样,其他对象的属性想引用它,只需要给属性传个指针地址就行了。
利用脚本进行工程配置
涉及工具
工具 | 作用 |
---|---|
PlistBuddy | 读写mobileprovision格式的文件,即可授权文件 |
xcodebuild | Xcode Project 构建 |
security | 解码mobileprovision文件、获取可用签名列表 |
codesign | 代码签名(此处值用来检查APP签名) |
简单介绍PlistBuddy使用
PlistBuddy 使用冒号:来分割每个属性key的名字,例如下图假设需要获取name的值,那么冒号分割key的组成就是
:Objects:0C14C6811E4964FA00F40247:List:2:name
完整的命令就是:
/usr/libexec/PlistBuddy -c 'Print :Objects:0C14C6811E4964FA00F40247:List:2:name' $plistFile
使用/usr/libexec/PlistBuddy -h 可以看到帮助说明
Entry Format:
Entries consist of property key names delimited by colons. Array items
are specified by a zero-based integer index. Examples:
:CFBundleShortVersionString
:CFBundleDocumentTypes:2:CFBundleTypeExtensions
代码实现(最新代码详见 github源码)
- 帮助
-p <Xcode Project File>: 指定Xcode project. 否则,脚本会在当前执行目录中查找Xcode Project 文件
-g: 获取当前项目git的版本数量
-l: 列举可用的codeSign identity.
-x: 脚本执行调试模式.
-b: 设置Bundle Id.
-d: 设置debug模式,默认release模式.
-t: 设置为测试(开发)环境,默认为生产环境.
-r <体系结构>,例如:-r 'armv7'或者 -r 'arm64' 或者 -r 'armv7 arm64' 等
-c <development|app-store|enterprise>: development 内部分发,app-store商店分发,enterprise企业分发
-h: 帮助.
检查Xcode Project
为了方便使用,可以通过-p指定xcode project文件。不使用-p参数,那么代码会自动在脚本执行目录中寻找xcode project文件。
##检查xcode project
function checkForProjectFile
{
##如果没有指定xcode项目,那么自行在当前目录寻找
if [[ "$xcodeProject" == '' ]]; then
pwd=`pwd`
xcodeProject=`find "$pwd" -maxdepth 1 -type d -name "*.xcodeproj"`
fi
projectExtension=`basename "$xcodeProject" | cut -d'.' -f2`
if [[ "$projectExtension" != "xcodeproj" ]]; then
errorExit "Xcode project 应该带有.xcodeproj文件扩展,.${projectExtension}不是一个Xcode project扩展!"
else
projectFile="$xcodeProject/project.pbxproj"
if [[ ! -f "$projectFile" ]]; then
errorExit "项目文件:\"$projectFile\" 不存在"
fi
logit "发现pbxproj:\"$projectFile\""
fi
}
检查是否是workspace,
因为xcodebuild 对于workspace的构建配置参数不一样,详见build函数!
function checkIsExistWorkplace
{
xcworkspace=`find "$xcodeProject/.." -maxdepth 1 -type d -name "*.xcworkspace"`
if [[ -d "$xcworkspace" ]]; then
isExistXcWorkspace=true
logit "发现xcworkspace:$xcworkspace"
else
isExistXcWorkspace=false;
fi
}
- 自动匹配授权文件
通过参数-c <development|app-store|enterprise>: development 内部分发,app-store商店分发,enterprise企业分发
参数指定的值,匹配对应的授权文件。
function autoMatchProvisionFile
{
##授权文件默认放置在和脚本同一个目录下的MobileProvisionFile 文件夹中
mobileProvisionFileDir="$( cd "$( dirname "$0" )" && pwd )/MobileProvisionFile"
if [[ ! -d "$mobileProvisionFileDir" ]]; then
errorExit "授权文件目录${mobileProvisionFileDir}不存在!"
fi
matchMobileProvisionFile=''
for file in ${mobileProvisionFileDir}/*.mobileprovision; do
applicationIdentifier=`$plistBuddy -c 'Print :Entitlements:application-identifier' /dev/stdin <<< $($security cms -D -i "$file" 2>/tmp/log.txt )`
applicationIdentifier=${applicationIdentifier#*.}
if [[ "$appBundleId" == "$applicationIdentifier" ]]; then
getProfileType $file
if [[ "$profileType" == "$channel" ]]; then
matchMobileProvisionFile=$file
logit "授权文件匹配成功:${applicationIdentifier},路径:$file"
profileTypeToName "${channel}"
logit "授权文件分发渠道:$profileTypeName"
break
fi
fi
done
if [[ $matchMobileProvisionFile == '' ]]; then
profileTypeToName "${channel}"
errorExit "无法匹配${appBundleId} 分发渠道为【${profileTypeName}】的授权文件"
fi
##企业分发,那么检查授权文件有效期
if [[ "$channel" == 'enterprise' ]];then
getProvisionfileExpirationDays "$matchMobileProvisionFile"
logit "授权文件有效时长:${expirationDays} 天";
if [[ $expirationDays -lt 0 ]];then
profileExpirationDa