原文地址:https://blog.csdn.net/chsadin/article/details/61192923
#——————————————–
# 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双击打开!果然!!!果然!!!果然!!!舒服呀,是不是有种被治愈的感觉!
对于以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
-p : 指定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企业分发
-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企业分发
参数指定的值,匹配对应的授权文件。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
profileExpirationDate=`$plistBuddy -c 'Print :ExpirationDate' /dev/stdin <<< $($security cms -D -i "$matchMobileProvisionFile" 2>/tmp/log.txt)`
errorExit "授权文件已经过期, 请联系开发人员更换授权文件! 有效日期:${profileExpirationDate}, 过期天数:${expirationDays#-} 天"
elif [[ $expirationDays -le 90 ]];then
errorExit "授权文件即将过期, 请联系开发人员更换授权文件! 有效日期:${profileExpirationDate} ,剩余天数:${expirationDays} 天"
fi
fi
##获取授权文件uuid、name、teamId
profileUuid=`$plistBuddy -c 'Print :UUID' /dev/stdin <<< $($security cms -D -i "$matchMobileProvisionFile" 2>/tmp/log.txt)`
profileName=`$plistBuddy -c 'Print :Name' /dev/stdin <<< $($security cms -D -i "$matchMobileProvisionFile" 2>/tmp/log.txt)`
profileTeamId=`$plistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' /dev/stdin <<< $($security cms -D -i "$matchMobileProvisionFile" 2>/tmp/log.txt)`
if [[ "$profileUuid" == '' ]]; then
errorExit "profileUuid=$profileUuid, 获取参数配置Profile的uuid失败!"
fi
if [[ "$profileName" == '' ]]; then
errorExit "profileName=$profileName, 获取参数配置Profile的name失败!"
fi
logit "发现授权文件参数配置:${profileName}, uuid:$profileUuid, teamId:$profileTeamId"
}
function autoMatchCodeSignIdentity
{
matchCodeSignIdentity=''
if [[ "${bundleIdsForPersion[@]}" =~ "$appBundleId" ]]; then
if [[ "$channel" == 'development' ]]; then
matchCodeSignIdentity=$devCodeSignIdentityForPersion
elif [[ "$channel" == 'app-store' ]]; then
matchCodeSignIdentity=$disCodeSignIdentityForPersion
fi
elif [[ "${bundleIdsForEnterprise[@]}" =~ "$appBundleId" ]]; then
if [[ "$channel" == 'development' ]]; then
matchCodeSignIdentity=$devCodeSignIdentityForEnterprise
elif [[ "$channel" == 'enterprise' ]]; then
matchCodeSignIdentity=$disCodeSignIdentityForEnterprise
fi
else
errorExit "无法匹配【${appBundleId}】的应用的签名,请检查Bundle Id “${$appBundleId}”是否配置在脚本开头的配置列表中!"
fi
logit "匹配到${applicationIdentifier}的签名:$matchCodeSignIdentity"
}
检查环境配置文件
通常我们打包需要打测试包
以及正式包
,测试环境
和正式环境
的配置是在OC代码中通过属性YES/NO
来表示,所以此函数的目的是检查环境配置文件是否存在。后面会有另外2个函数getEnvirionment
和setEnvironment
分别来获取和设置OC代码中的值
##检查环境配置文件
function checkEnvironmentConfigureFile
{
environmentConfigureFile=`find "$xcodeProject/.." -maxdepth 5 -path "./.Trash" -prune -o -type f -name "$environmentConfigureFileName" -print| head -n 1`
if [[ ! -f "$environmentConfigureFile" ]]; then
haveConfigureEnvironment=false;
logit "环境配置文件${environmentConfigureFileName}不存在!"
else
haveConfigureEnvironment=true;
logit "发现环境配置文件:${environmentConfigureFile}"
fi
}
##设置生产环境或者开发环境
function setEnvironment
{
if [[ $haveConfigureEnvironment == true ]]; then
bakExtension=".bak"
bakFile=${environmentConfigureFile}${bakExtension}
if [[ $productionEnvironment == true ]]; then
if [[ "$environmentValue" != "NO" ]]; then
sed -i "$bakExtension" "/kBMIsTestEnvironment/s/YES/NO/" "$environmentConfigureFile" && rm -rf $bakFile
logit "设置配置环境kBMIsTestEnvironment:NO"
fi
else
if [[ "$environmentValue" != "YES" ]]; then
sed -i "$bakExtension" "/kBMIsTestEnvironment/s/NO/YES/" "$environmentConfigureFile" && rm -rf $bakFile
logit "设置配置环境kBMIsTestEnvironment:YES"
fi
fi
fi
getEnvirionment
}
获取git版本数量
通常我们在打包的时候我们都需要修改build号,每次修改其实比较麻烦,于是就使用git版本数量作为build号
##获取git版本数量
function getGitVersionCount
{
gitVersionCount=`git -C "$xcodeProject" rev-list HEAD | wc -l | grep -o "[^ ]\+\( \+[^ ]\+\)*"`
echo "当前版本数量:$gitVersionCount"
}
获取target 和 scheme
scheme只是为了在xcodebuild构建的时候,对-scheme参数进行参数配置,详见build
函数
tartget是因为Build Settings是根据不同target而不同,所在在之后配置Build Settings会使用到target,详见getBuildSettingsConfigure
函数
##获取scheme
function getSchemes
{
##获取scheme,替换#号,是为了方便赋值给数组。scheme名字带有空格的时候例如:Copy of BlueMoonSFA,会被误分割成数组的元素。
schemeList=(`$xcodebuild -project $xcodeProject -list | awk '/\Schemes/{s=$0~/Schemes/?1:0}s' | grep -v "Schemes:" | tr -s '\n'| tr -s ' ' '#'`)
for (( i = 0; i < ${#schemeList[@]}; i++ )); do
scheme=`echo ${schemeList[$i]} | tr -d '#'`
schemes[$i]=$scheme
done
logit "获取到schemes,数量:${#schemes[@]}"
for (( i = 0; i < ${#schemes[@]}; i++ )); do
logit "${schemes[$i]}"
done
}
function getTargets
{
rootObject=`$plistBuddy -c "Print :rootObject" $projectFile`
targetList=`$plistBuddy -c "Print :objects:${rootObject}:targets" $projectFile | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
targets=(`echo $targetList`);#括号用于初始化数组,例如arr=(1,2,3)
logit "发现targets(id):$targets"
}
##获取BuildSetting 配置
function getBuildSettingsConfigure
{
for targetId in ${targets[@]}; do
targetName=`$plistBuddy -c "Print :objects:$targetId:name" $projectFile`
buildTargetNames=(${buildTargetNames[*]} $targetName)
logit "target名字:$targetName"
buildConfigurationListId=`$plistBuddy -c "Print :objects:$targetId:buildConfigurationList" $projectFile`
logit "配置列表Id:$buildConfigurationListId"
buildConfigurationList=`$plistBuddy -c "Print :objects:$buildConfigurationListId:buildConfigurations" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
buildConfigurations=(`echo $buildConfigurationList`)
logit "发现配置:$buildConfigurations"
for configurationId in ${buildConfigurations[@]}; do
logit "=============================="
configurationName=`$plistBuddy -c "Print :objects:$configurationId:name" "$projectFile"`
logit "配置类型: $configurationName"
# CODE_SIGN_ENTITLEMENTS 和 CODE_SIGN_RESOURCE_RULES_PATH 不一定存在,这里不做判断
# codeSignEntitlements=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:CODE_SIGN_ENTITLEMENTS" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
# codeSignResourceRulePath=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:CODE_SIGN_RESOURCE_RULES_PATH" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
codeSignIdentity=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:CODE_SIGN_IDENTITY" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
codeSignIdentitySDK=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:CODE_SIGN_IDENTITY[sdk=iphoneos*]" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
developmentTeam=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:DEVELOPMENT_TEAM" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
infoPlistFile=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:INFOPLIST_FILE" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
iphoneosDeploymentTarget=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:IPHONEOS_DEPLOYMENT_TARGET" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
onlyActiveArch=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:ONLY_ACTIVE_ARCH" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
productBundleIdentifier=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:PRODUCT_BUNDLE_IDENTIFIER" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
productName=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:PRODUCT_NAME" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
provisionProfileUuid=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:PROVISIONING_PROFILE" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
provisionProfileName=`$plistBuddy -c "Print :objects:$configurationId:buildSettings:PROVISIONING_PROFILE_SPECIFIER" "$projectFile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
# logit "codeSignEntitlements:$codeSignEntitlements"
# logit "codeSignResourceRulePath:$codeSignResourceRulePath"
logit "codeSignIdentity:$codeSignIdentity"
logit "codeSignIdentitySDK:$codeSignIdentitySDK"
logit "developmentTeam:$developmentTeam"
logit "infoPlistFile:$infoPlistFile"
logit "iphoneosDeploymentTarget:$iphoneosDeploymentTarget"
logit "onlyActiveArch:$onlyActiveArch"
logit "productBundleIdentifier:$productBundleIdentifier"
logit "productName:$productName"
logit "provisionProfileUuid:$provisionProfileUuid"
logit "provisionProfileName:$provisionProfileName"
logit "=============================="
done
done
}
根据授权文件获取uuid
我们在Xcode Project–Build Settings – Signing–Provisioning Profile中看到的是授权文件的名称,实际上配置的是授权文件的uuid
function getNewProfileUuid
{
newProfileUuid=`$plistBuddy -c 'Print :UUID' /dev/stdin <<< $($security cms -D -i "$newProfile" )`
newProfileName=`$plistBuddy -c 'Print :Name' /dev/stdin <<< $($security cms -D -i "$newProfile" )`
newTeamId=`$plistBuddy -c 'Print :Entitlements:com.apple.developer.team-identifier' /dev/stdin <<< $($security cms -D -i "$newProfile" )`
if [[ "$newProfileUuid" == '' ]]; then
echo "newProfileUuid=$newProfileUuid, 获取参数配置Profile的uuid失败!"
exit 1;
fi
if [[ "$newProfileName" == '' ]]; then
echo "newProfileName=$newProfileName, 获取参数配置Profile的name失败!"
exit 1;
fi
logit "发现授权文件参数配置:${newProfileName}, uuid:$newProfileUuid, teamId:$newTeamId"
}
判断授权文件的类型
我们所知,在apple 开发者后台我们可以新建很多种类型的授权文件,例如:debug、ad-hoc、enterprise(企业账号)、appstore(个人账号),不同的类型决定ipa的分发模式(内部测试、企业分发、商店分发)。
代码里面的识别授权文件类型的逻辑是参考 mobileprovision-read 的代码来写的!
##检查授权文件类型
function getProfileType
{
profile=$1
# provisionedDevices=`$plistBuddy -c 'Print :ProvisionedDevices' /dev/stdin <<< $($security cms -D -i "$profile" ) | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//'`
##判断是否存在key:ProvisionedDevices
haveKey=`security cms -D -i "$profile" | sed -e '/Array {/d' -e '/}/d' -e 's/^[ \t]*//' | grep ProvisionedDevices`
if [[ $? -eq 0 ]]; then
getTaskAllow=`$plistBuddy -c 'Print :Entitlements:get-task-allow' /dev/stdin <<< $($security cms -D -i "$profile" ) `
if [[ $getTaskAllow == true ]]; then
profileType='debug'
else
profileType='ad-hoc'
fi
else
provisionsAllDevices=`$plistBuddy -c 'Print :ProvisionsAllDevices' /dev/stdin <<< $($security cms -D -i "$profile" ) `
if [[ $provisionsAllDevices == true ]]; then
profileType='enterprise'
else
profileType='appstore'
fi
fi
logit "授权文件类型:$profileType"
if [[ "$haveKey" != '' ]]; then
logit "$provisionedDevices"
fi
}
关于get-task-allow
的描述,摘抄网络
get-task-allow, when signed into an application, allows other processes (like the debugger) to attach to your app. Distribution profiles require that this value be turned off, while development profiles require this value to be turned on (otherwise Xcode would never be able to launch and attach to your app).
##设置build号
function setBuildVersion
{
$plistBuddy -c "Set :CFBundleVersion $gitVersionCount" $infoPlistFile
}
设置手动管理签名
即不勾选:Xcode -> General -> Signing -> Automatically manage ,这种能方便我们脚本自行配置各种签名以及授权文件
##获取签名方式
function getCodeSigningStyle
{
for targetId in ${targets[@]}; do
targetName=`$plistBuddy -c "Print :objects:$targetId:name" $projectFile`
##没有勾选过Automatically manage signning时,则不存在ProvisioningStyle
signingStyle=`$plistBuddy -c "Print :objects:$rootObject:attributes:TargetAttributes:$targetId:ProvisioningStyle " "$projectFile"`
logit "获取到target:${targetName}签名方式:$signingStyle"
done
}
##设置手动管理签名
function setManulSigning
{
for targetId in ${targets[@]}; do
targetName=`$plistBuddy -c "Print :objects:$targetId:name" $projectFile`
if [[ "$signingStyle" != "Manual" ]]; then
##如果需要设置成自动签名,将Manual改成Automatic
$plistBuddy -c "Set :objects:$rootObject:attributes:TargetAttributes:$targetId:ProvisioningStyle Manual" "$projectFile"
logit "设置${targetName}的签名方式为:Manual"
fi
done
}
##设置授权文件
function setProfile
{
for configurationId in ${buildConfigurations[@]}; do
configurationName=`$plistBuddy -c "Print :objects:$configurationId:name" "$projectFile"`
if [[ "$provisionProfileName" != "$newProfileName" ]]; then
$plistBuddy -c "Set :objects:$configurationId:buildSettings:PROVISIONING_PROFILE_SPECIFIER $newProfileName" "$projectFile"
logit "设置授权文件SPECIFIER${configurationName} PROVISIONING_PROFILE_SPECIFIER:$provisionProfileName --> $newProfileName"
fi
if [[ "$provisionProfileUuid" != "$newProfileUuid" ]]; then
$plistBuddy -c "Set :objects:$configurationId:buildSettings:PROVISIONING_PROFILE $newProfileUuid" "$projectFile"
logit "设置授权文件${configurationName} PROVISIONING_PROFILE:$provisionProfileUuid --> $newProfileUuid"
fi
done
}
##设置签名
function setCodeSign
{
for configurationId in ${buildConfigurations[@]}; do
configurationName=`$plistBuddy -c "Print :objects:$configurationId:name" "$projectFile"`
##设置CODE_SIGN_IDENTITY
if [[ "$codeSignIdentity" != "$newCodeSign" ]]; then
$plistBuddy -c "Set :objects:$configurationId:buildSettings:CODE_SIGN_IDENTITY $newCodeSign" "$projectFile"
if [[ $? -eq 0 ]]; then
logit "设置签名${configurationName} CODE_SIGN_IDENTITY: $codeSignIdentity --> $newCodeSign"
else
echo "设置签名${configurationName} CODE_SIGN_IDENTITY 失败!"
exit 1
fi
fi
##设置CODE_SIGN_IDENTITY[sdk=iphoneos*]
if [[ "$codeSignIdentitySDK" != "$newCodeSign" ]]; then
$plistBuddy -c "Set :objects:$configurationId:buildSettings:CODE_SIGN_IDENTITY[sdk=iphoneos*] $newCodeSign" "$projectFile"
if [[ $? -eq 0 ]]; then
logit "更改签名配置${configurationName} CODE_SIGN_IDENTITY[sdk=iphoneos*]: $codeSignIdentitySDK --> $newCodeSign"
else
echo "更改签名配置${configurationName} CODE_SIGN_IDENTITY[sdk=iphoneos*] 失败!"
exit 1
fi
fi
##设置teamId
if [[ "$developmentTeam" != "$newTeamId" ]]; then
$plistBuddy -c "Set :objects:$configurationId:buildSettings:DEVELOPMENT_TEAM $newTeamId" "$projectFile"
if [[ $? -eq 0 ]]; then
logit "更改签名配置${configurationName} DEVELOPMENT_TEAM: $developmentTeam --> $newTeamId"
else
echo "更改签名配置${configurationName} DEVELOPMENT_TEAM 失败!"
exit 1
fi
fi
done
}
构建
workspace与不是workspace的打包配置参数不一样,代码会兼容两者。以往使用xcrun -sdk iphoneos PackageApplication "$generated_app_file" -o "$finally_generated_ipa_file"
的方式将app转换成ipa,这里在xcodebuild中使用 archivec 参数生成.xcarchive文件,在通过.xcarchive文件导出ipa。
function build
{
packageDir=$xcodeProject/../build/package
if [[ $debugConfiguration == true ]]; then
configuration="Debug"
else
configuration="Release"
fi
for (( i = 0; i < ${#schemes[@]}; i++ )); do
archivePath=${packageDir}/${schemes[$i]}.xcarchive
exprotPath=${packageDir}/${schemes[$i]}.ipa
if [[ -d $archivePath ]]; then
rm -rf $archivePath
fi
if [[ -f $exprotPath ]]; then
rm -rf $exprotPath
fi
if [[ $isExistXcWorkspace == true ]]; then
cmd="$xcodebuild archive -workspace $xcworkspace -scheme ${schemes[$i]} -archivePath $archivePath -configuration $configuration build"
else
cmd="$xcodebuild archive -scheme ${schemes[$i]} -archivePath $archivePath -configuration $configuration build"
fi
$cmd
if [[ $? -ne 0 ]]; then
echo "构建失败!构建命令:$cmd"
rm -rf ${packageDir}/*
exit 1
fi
##导出ipa
$xcodebuild -exportArchive -exportFormat IPA -archivePath $archivePath -exportPath $exprotPath
if [[ $? -eq 0 ]]; then
logit "打包成功,IPA生成路径:$exprotPath"
else
logit "$xcodebuild -exportArchive -exportFormat IPA -archivePath $archivePath -exportPath $exprotPath 执行失败"
exit 1
fi
checkIPA
renameAndBackup
done
}
构建后检查
在构建完成之后,检查ipa防止出错
##构建完成,检查App
function checkIPA
{
##解压强制覆盖,并不输出日志
unzip -o $exprotPath -d /tmp/ >/dev/null 2>&1
appName=`basename $exprotPath .ipa`
app=/tmp/Payload/${appName}.app
logit "============================="
if [[ -d $app ]]; then
infoPlistFile=${app}/Info.plist
mobileProvisionFile=${app}/embedded.mobileprovision
appName=`$plistBuddy -c "Print :CFBundleName" $infoPlistFile`
appBundleId=`$plistBuddy -c "print :CFBundleIdentifier" "$infoPlistFile"`
appVersion=`$plistBuddy -c "Print :CFBundleShortVersionString" $infoPlistFile`
appBuildVersion=`$plistBuddy -c "Print :CFBundleVersion" $infoPlistFile`
appMobileProvisionName=`$plistBuddy -c 'Print :Name' /dev/stdin <<< $($security cms -D -i "$mobileProvisionFile" )`
appMobileProvisionCreationDate=`$plistBuddy -c 'Print :CreationDate' /dev/stdin <<< $($security cms -D -i "$mobileProvisionFile" )`
appMobileProvisionExpirationDate=`$plistBuddy -c 'Print :ExpirationDate' /dev/stdin <<< $($security cms -D -i "$mobileProvisionFile" )`
appCodeSignIdenfifier=`$codesign --display -r- $app | cut -d "\"" -f 4`
logit "名字:$appName"
logit "配置环境kBMIsTestEnvironment:$currentEnvironmentValue"
logit "bundle identify:$appBundleId"
logit "版本:$appVersion"
logit "build:$appBuildVersion"
logit "签名:$appCodeSignIdenfifier"
logit "授权文件:${appMobileProvisionName}.mobileprovision"
logit "授权文件创建时间:$appMobileProvisionCreationDate"
logit "授权文件过期时间:$appMobileProvisionExpirationDate"
getProfileType $mobileProvisionFile
else
echo "解压失败!无法找到$app"
fi
}
ipa重命名并备份
重命名是为了更好的识别该ipa是哪个版本、测试环境还是生产环境等等
function renameAndBackup
{
backupDir=~/Desktop/PackageLog
backupHistoryDir=~/Desktop/PackageLog/history
if [[ ! -d backupHistoryDir ]]; then
mkdir -p $backupHistoryDir
fi
if [[ "$currentEnvironmentValue" == 'YES' ]]; then
environmentName='开发环境'
else
environmentName='生产环境'
fi
if [[ "$profileType" == 'appstore' ]]; then
profileTypeName='商店分发'
elif [[ "$profileType" == 'enterprise' ]]; then
profileTypeName='企业分发'
else
profileTypeName='内部测试'
fi
date=`date +"%Y%m%d_%H%M%S"`
name=${appName}_${date}_${environmentName}_${profileTypeName}_${appVersion}\($appBuildVersion\)
ipaName=${name}.ipa
textLogName=${name}.txt
logit "ipa重命名并备份到:$backupDir/$ipaName"
mv -f $backupDir/*.ipa $backupHistoryDir
mv -f $backupDir/*.txt $backupHistoryDir
cp -af $exprotPath $backupDir/$ipaName
cp -af $tmpLogFile $backupDir/$textLogName
}
通过使用PlistBuddy修改project.pbxproj后,中文显示乱码,并无法编译通过
通过查看project.pbxproj文件格式类型从:UTF-8 Unicode Text 变成 XML 1.0 document text UTF-8 Unicode Text
project.pbxproj 文件被包含于 Xcode 工程文件 *.xcodeproj 之中,存储着 Xcode 工程的各项配置参数。它本质上是一种旧风格的 Property List 文件,历史可追溯到 NeXT 的 OpenStep。其可读性不如 xml 和 json,苹果却一直沿用至今,作为一家以创新闻名的公司可能这里剩下的就是情怀吧。
Let’s Talk About project.pbxproj
通过百度查阅一序列资料,也确认了PlistBuddy只能读取project.pbxproj,并不再支持修改。
Xcodeproj CocoaPods 写的 Ruby 解析库,用于修改引入 CocoaPods 的工程文件并保存为 XML 格式。CocoaPods 本身是很强大的,还可以用来操作 Xcode workspaces (.xcworkspace), configuration files (.xcconfig) 和 Xcode Scheme files (.xcscheme).
直接上代码:
#!/usr/bin/ruby
# -*- coding: UTF-8 -*-
require 'xcodeproj'
$projectPath = ARGV[0]
$provisionProfileUuid = ARGV[1]
$provisionProfileName = ARGV[2]
$codeSignIdentify = ARGV[3]
$developmentTeam = ARGV[4]
puts $projectPath
project = Xcodeproj::Project.open($projectPath)
def setProvisioningStyle(project)
targetUuid=project.root_object.targets[0].uuid
##判断字典是否存在ProvisioningStyle
haskey=project.root_object.attributes["TargetAttributes"][targetUuid].include?("ProvisioningStyle")
if haskey
project.root_object.attributes["TargetAttributes"][targetUuid]["ProvisioningStyle"]="Manual"
puts "ProvisioningStyle设置为Manual"
provisioningStyle=project.root_object.attributes["TargetAttributes"][targetUuid]["ProvisioningStyle"]
puts "#{targetUuid}当前ProvisioningStyle:#{provisioningStyle}"
else
puts "key:ProvisioningStyle 不存在"
end
end
def setSigning(project)
project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['PROVISIONING_PROFILE[sdk=iphoneos*]'] = $provisionProfileUuid
config.build_settings['PROVISIONING_PROFILE'] = $provisionProfileUuid
config.build_settings['PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]'] = $provisionProfileName
config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = $provisionProfileName
config.build_settings['CODE_SIGN_IDENTITY[sdk=iphoneos*]'] = $codeSignIdentify
config.build_settings['CODE_SIGN_IDENTITY'] = $codeSignIdentify
config.build_settings['DEVELOPMENT_TEAM'] = $developmentTeam
end
end
end
setProvisioningStyle(project)
setSigning(project)
project.save
很早很早之前就想整理脚本打包的文章,一直没有时间也不够兴致去完成。今天终于把它整理出来了,虽然自动打包的脚本l代码在该文章之前我写了好多次,但是把它当做文章来对待时,还是发现之前考虑漏很多问题,很多问题让我重新去探索、去确认一遍。整理该代码以及文章足足花了5天*8小时的时间!!!
完整代码详见github
欢迎各位有相同爱好的同学一起交流,特建此群:
群名称:
iOS 打包签名技术交流
群 号:
629088155
#--------------------------------------------
# 版本:1.0.0
# 功能:
# 1.显示Build Settings 签名配置
# 2.获取git版本数量,并自动更改build号为版本数量号
# 3.日志文本log.txt输出
# 4.自动匹配签名和授权文件
# 5.支持workplace、多个scheme
# 6.校验构建后的ipa的bundle Id、签名、支持最低iOS版本、arm体系等等
# 7.构建前清理缓存,防止xib更改没有被重新编译
# 8.备份历史打包ipa以及log.txt
# 9.可更改OC代码,自动配置服务器测试环境or生产环境
# 10.格式化输出ipa包名称:name_time_开发环境_企业分发_1.0.0(168).ipa
# 作者:
# fenglh 2016/03/06
# 备注:
# 1.security 命令会报警告,忽略即可:security: SecPolicySetValue: One or more parameters passed to a function were not valid.
# 2.支持Xcode8.0及以上版本(8.0前没有测试过)
#--------------------------------------------
#
# 版本:2.0.0
# 优化:
# 1.去掉可配置签名、授权文件,并修改为自动匹配签名和授权文件!
# 作者:
# fenglh 2016/03/06
#
#
#--------------------------------------------
#
# 版本:2.0.1
# 优化:
# 为了节省打包时间,在打开发环境的包时,只打armv7
# profileType==development 时,设置archs=armv7 (向下兼容) ,否则archs为默认值:arm64 和armv7。
# 作者:
# fenglh 2016/03/06
#
#
# 版本:2.0.2
# 优化:兼容xcode8.3以上版本
# xcode 8.3之后使用-exportFormat导出IPA会报错 xcodebuild: error: invalid option '-exportFormat',改成使用-exportOptionsPlist
# Available options: app-store, ad-hoc, package, enterprise, development, and developer-id.
# 当前用到:app-store ,ad-hoc, enterprise, development
# 作者:
# fenglh 201708/05
# 版本:2.0.3
# 优化:对授权文件mobiprovision有效期检测,授权文件有效期小于90天,强制打包失败!
#
# 版本:2.0.4
# 优化:默认构建ipa支持armch 为 arm64。(因iOS 11强制禁用32位)
#
#
# 版本:2.0.5
# 优化:
# 1. 增加一个“修改Bundle Id”功能。如-b com.xxx.xx。
# 2. 优化一些代码
# 作者:
# fenglh 2018/04/12
# 版本:2.0.6
# 1. 优化build函数代码。
# 2. 增加xcpretty 来格式化日志输出
# 3. 支持xcode9(8.0~9.3)
# fenglh 2018/04/19