iOS--脚本配置Xcode Project(打包)



前言

作为iOS开发者,每次真机测试我都习惯在Build Settings中设置签名信息Code Signing Identity 、Development Team、 Provisioning Profile、Provisioning Profile(Deprecated)等,并会在General中去掉勾选Automatically manage signning即设置为手动管理证书!所以,在实现自动打包脚本时,我也会通过修改这里的配置来完成所需的签名等配置。本文的目的主要是了解Xcode Project的配置是如何组织的,以及如何用脚本替我们完成Target的配置达到与手动配置一致的效果,其次是实现脚本自动打包功能!

这里写图片描述

这里写图片描述


工欲善其事必先利其器

我们知道Xcode Project 的配置文件是project.pbxproj

  1. 如何更方便查看project.pbxproj文件

    这里写图片描述

  2. 以往很长一段时间,需要在project.pbxproj文件中查找或者编辑某些配置时我都会使用Subline Text、文本编辑器、xcode打开,他们的效果都一样。

    这里写图片描述

    这里写图片描述

  3. 上面的方式如果需要查找某个value或key的层级关系这就悲剧了,接近上万的行数!行数太多不能一屏看完,拖着拖着就不知道拖到那里了。 
    这里写图片描述

  4. 如果project.pbxproj文件能像Plist文件那样展开浏览就好了,于是尝试修改project.pbxproj的后缀名字为”.plist”即project.plist,使用xcode双击打开!果然!!!果然!!!果然!!!舒服呀,是不是有种被治愈的感觉! 
    这里写图片描述


探究project.pbxproj

对于以plist形式展示的project.pbxproj,强烈的激起了我对它的进一步分析的想法!

这里写图片描述

  1. 随意浏览了一下,发现:rootObject :0CA805261D7D1A1900EEC7DA 
    这应该是个入口,下一步搜索”0CA805261D7D1A1900EEC7DA“ 
    这里写图片描述

  2. 这里的targets应该就是正好对应我们工程中的targets 
    这里写图片描述

  3. 这里的ProvisioningStyle 应该就对应Target - General -Automatically manage signning 
    这里写图片描述

  4. 继续搜索targets-item0对应的:0CA8052D1D7D1A1900EEC7DA 
    这里写图片描述

  5. 上面搜索展示出得列表,从名字来看很容易就能猜测出来其代表的意义,buildConfigurationList应该就是配置列表了。搜索:buildConfigurationList:0CA805451D7D1A1900EEC7DA。 
    这里写图片描述

  6. 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
       
       
  • 1
  • 2

完整的命令就是:

/usr/libexec/PlistBuddy -c 'Print :Objects:0C14C6811E4964FA00F40247:List:2:name' $plistFile
       
       
  • 1

这里写图片描述

使用/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


代码实现

  • 帮助
function usage
{
    echo "  -p <Xcode Project File>: 指定Xcode project."
    echo "  -f <Profile>: 指定授权文件."
    echo "  -s <codeSign identify>: 指定签名,使用-l 参数列举可用签名."
    echo "  -g: 获取git版本数量,并自动更改build号为版本数量号."
    echo "  -l: 列举可用的codeSign identify."
    echo "  -x: 脚本执行调试模式."
    echo "  -d: 设置debug模式,默认release模式."
    echo "  -t: 设置为测试(开发)环境,默认为生产环境."
    echo "  -s: 显示有效的签名."
    echo "  -h: 帮助."
    echo "  -v: 输出详细信息."
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 检查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  
        echo "Xcode project 应该带有.xcodeproj文件扩展,.${projectExtension}不是一个Xcode project扩展!"
        exit 1
    else
        projectFile="$xcodeProject/project.pbxproj"
        if [[ ! -f "$projectFile" ]]; then
            echo "项目文件:$projectFile 不存在"
            exit 1;
        fi
        logit "发现pbxproj:$projectFile"
    fi


}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 检查是否是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
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 检查环境配置文件

    通常我们打包需要打测试包以及正式包测试环境正式环境的配置是在OC代码中通过属性YES/NO来表示,所以此函数的目的是检查环境配置文件是否存在。后面会有另外2个函数getEnvirionmentsetEnvironment分别来获取和设置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

}

       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

这里写图片描述

  • 获取git版本数量

    通常我们在打包的时候我们都需要修改build号,每次修改其实比较麻烦,于是就使用git版本数量作为build号

##获取git版本数量
function getGitVersionCount
{
    gitVersionCount=`git -C "$xcodeProject" rev-list HEAD | wc -l | grep -o "[^ ]\+\( \+[^ ]\+\)*"`
    echo "当前版本数量:$gitVersionCount"
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 获取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"
}

       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 获取Build Settings 配置
##获取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
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 根据授权文件获取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"
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 判断授权文件的类型

    我们所知,在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

}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

关于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号
##设置build号
function setBuildVersion
{
    $plistBuddy -c "Set :CFBundleVersion $gitVersionCount" $infoPlistFile
}

       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这里写图片描述

  • 设置手动管理签名

    即不勾选: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

}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 设置授权文件和签名
##设置授权文件
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
}

       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 构建

    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
    }
             
             
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
  • 构建后检查

    在构建完成之后,检查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
    }
             
             
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
  • 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    
}
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

这里写图片描述



遇到的问题

通过使用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

解决方案–Xcodeproj

通过百度查阅一序列资料,也确认了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


       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

总结

很早很早之前就想整理脚本打包的文章,一直没有时间也不够兴致去完成。今天终于把它整理出来了,虽然自动打包的脚本l代码在该文章之前我写了好多次,但是把它当做文章来对待时,还是发现之前考虑漏很多问题,很多问题让我重新去探索、去确认一遍。整理该代码以及文章足足花了5天*8小时的时间!!! 
完整代码详见github

后续优化

2017/04/10 
考虑到其他没有经常接触过bash的同学,使用-f、-s参数指定授权文件和签名还是比较麻烦的~所以此次…去掉-f、-s参数,添加-c参数,使用-c参数自动匹配签名和授权文件!! 
这里写图片描述

其原理就是:工程xcode project的bunleid 和授权文件的application-identifier两者一致,即可匹配到对应的授权文件,再根据-c参数指定分发渠道,并且每个授权文件都可以辨别它是属于哪种授权文件,因此可以再进一步确定匹配到授权文件的哪种分发渠道的授权文件!至于签名身份codeSign identity 则一个根据授权文件的分发渠道类型就可以知道用哪种签名身份了!详见代码,这里不做详细说明了。因为本人所有app是在2个账号上的,一个是个人开发者账号,一个是企业开发者账号,所以在脚本开头你会开到这样的代码…

#####################可配置项目#####################

##个人账号:请把个人账号App的BundleId 配置在这里
bundleIdsForPersion=(cn.com.xx.xxa, cn.com.xx.xxb)
##企业账号:请把企业账号App的BundleId 配置在这里
bundleIdsForEnterprise=(cn.com.xx.xxc, cn.com.xx.xxd, cn.com.xx.xx.xxe)

#####################################################
       
       
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值