iOS 一套代码多APP/多渠道/多target+自动打包脚本

由于公司需要,需有一套代码出多个APP,(有企业版也有Store版),同时企业版又分多个渠道(二维码放在不同的地方推广),由于企业版和Store版证书App的Logo、启动图、友盟、支付、分享scheme什么的都不相同,一套代码出好几个app,外加几个渠道,一次打近十个包,改到很来多时候自己都蒙了,所以为了不出错,也是为了不头疼所以就只能想想解决办法。

一个自动打包脚本是多么的重要.pic.jpg

1.多target
2.buildSetting中设置不同的宏,以区分是哪个target
3.将channel字断加入到plist中,以区分多版本
4.PlistBuddy修改plist(version及channel)
5.打包脚本

一、多target

如果只是一套代码多个APP可以创建多个target,每个target一套配置文件

添加target.jpg
三处修改.jpg

1.点击工程
2.右键target点击Duplicate
3.修改target名称、修改plist名称、修改scheme(点击上图3处选择EditScheme->MamagerScheme,修改一下名称)


修改Scheme.jpg

4.添加宏代码中通过判断是否定义了该宏来判断当前是哪个target


添加宏.jpg

5.如果你是swift和oc混编的工程interfaceHeader原来是一个配置的值,由于改了target名称回导致新建的target找不到这个文件,所以要这个配置值改为固定值


修改interfaceHeader.jpg

6.代码中判断当前是哪个target

#ifdef  HSENTERPRISE
    //hs
#elif defined XUETUENTERPRISE
    //xuetu
#endif

二、多渠道

多渠道主要是给每个app加一个标识符,以区分是哪个渠道,我们是在每次网络请求时都会上传当前channel,用以统计每个渠道的注册量或者购买等,这里channel字段要加在plist当中,通过自动脚本打包,每次修改一下channel字段,生成对应的ipa包。不然手动改真的爆炸。


将channel字段加入到plist中.jpg

每个target都有对应的一个info-plist,所以我们可以通过以下方法获取到当前target绑定的plist,从而获取channel,当然上面通过宏来判断是哪个target,也可以在plist文件加一个标识符来区分是哪个target。

    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString * channel = [infoDictionary objectForKey:@"channel"];

三、PlistBuddy修改plist

PlistBuddy是mac自带的命令行修改plist的工具,同样我们也可以在脚本中通过PlistBuddy来修改plist
终端输入/usr/libexec/PlistBuddy --help可以查看帮助文档
举几个常用的例子:

  • 添加
    /usr/libexec/PlistBuddy -c "Add :addtest string xixi " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 修改
    /usr/libexec/PlistBuddy -c "Set :addtest haha " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 输出
    /usr/libexec/PlistBuddy -c "print addtest" /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 删除
    /usr/libexec/PlistBuddy -c "Delete :addtest " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 添加字典
    /usr/libexec/PlistBuddy -c "add :adddict:name string zhangsan " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 创建数组
    /usr/libexec/PlistBuddy -c "add :arrayname array " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

  • 为数组增加值
    /usr/libexec/PlistBuddy -c "add :arrayname:0 string lisi " /Users/hsedu/Desktop/hskaoyanStrore-Info.plist

四、自动打包脚本

1、shell几个常用语句

  • 定义变量
    APPVERSION="2.1.7"
  • For in循环(变量无需声明直接用)
 archiveIpas="  vkeEnterprise xuetuEnterprise vkeStore hsStore";
for produce in $archiveIpas
do
#coding
done
  • 函数及case($1代表传入的参数)
teamID=$(getteamID ${produce})
function getteamID()
{
    teamid=""
    case $1 in
    hsEnterprise)
    teamid="93C8xxxxx";
    ;;
    hsStore)
    teamid="5Txxxxxxx";
    ;;
    vkeEnterprise)
    teamid="93CxxxX";
    ;;
    vkeStore)
    teamid="xxxxxJ28";
    ;;
    xuetuEnterprise)
    teamid="9xxxxxYX";
    ;;
    esac
    echo $teamid;
}
  • if语句
methodString=""
B="Enterprise"
#判断是否包含Enterprise,从而判定是企业版还是stroe版证书
if [[ $produce == *$B* ]]
then
methodString="enterprise"
#包含
else
methodString="ad-hoc"
#不包含
fi

2.xcodebuild 打包
需要哪些准备

  • teamID
    • 可以在appleDevelop账号的Membership Details中查看开发账号的teamID
    • 可以在project文件显示包内容中的project.pbxproj 中搜索team查看teamID
  • 证书名字
  • 可以在钥匙串显示简介中查看
  • 描述文件(Provisioning Profiles)UUID
  • 可以终端中vi 描述文件(Provisioning Profiles)查看UUID

3.生成xcarchive文件

xcodebuild -help可查看xcodebuild相关帮助文档
man xcodebuild可查看xcodebuild相关帮助文档
xcodebuild -list可查看当前工程target、Configurations以及scheme

#archive workspace-用cocoaPods的
#xcodebuild -archivePath "${archiveProducePath}${produce}${channel}.xcarchive" -workspace hskaoyan.xcworkspace  -scheme "${produce}"  -sdk iphoneos -configuration ${configuration} CODE_SIGN_IDENTITY="$IDENTITY" PROVISIONING_PROFILE="$PROFILE_UUID" archive
#archive project-没用cocoaPods或者cocoaPods在工程内,不含workspace
xcodebuild -archivePath "${archiveProducePath}${produce}${channel}.xcarchive" -project hskaoyan.xcodeproj  -scheme "${produce}"   -sdk iphoneos -configuration ${configuration} CODE_SIGN_IDENTITY="$IDENTITY" PROVISIONING_PROFILE="$PROFILE_UUID" archive

-archivePath 生成archive文件的存放路径
-workspace workspace 名称
-sdk 所用sdk(xcodebuild -showsdks可查看可用sdk)

iOS SDKs:
    iOS 10.2                        -sdk iphoneos10.2

iOS Simulator SDKs:
    Simulator - iOS 10.2            -sdk iphonesimulator10.2

macOS SDKs:
    macOS 10.12                     -sdk macosx10.12

tvOS SDKs:
    tvOS 10.1                       -sdk appletvos10.1

tvOS Simulator SDKs:
    Simulator - tvOS 10.1           -sdk appletvsimulator10.1

watchOS SDKs:
    watchOS 3.1                     -sdk watchos3.1

watchOS Simulator SDKs:
    Simulator - watchOS 3.1         -sdk watchsimulator3.1

-configuration 选择configuration

配置configuration.png

CODE_SIGN_IDENTITY 证书名称
证书名称.png

PROVISIONING_PROFILE描述文件UUID

进入描述文件查看UUID.png
UUID.png

4.导出ipa包

#导出ipa
xcodebuild -exportArchive -archivePath "${archiveProducePath}${produce}${channel}.xcarchive"  -exportPath "${ipaProducePath}${produce}${channel}"   -exportOptionsPlist "$optionsPlist_FILE_PATH"

-archivePath 刚才生成的archive文件路径
-exportPath ipa包导出路径
-exportOptionsPlist 导出相关配置的plist

exportOptionsPlist.png

可以通过修改method参数来选择你要的包是adhoc还是企业版或者是store(不通包所需的描述文件不同)

5.自动打包脚本最终版

#!/bin/bash

echo "      _          ------ _                   "
echo "     (_)        / /    ) )       ___        "
echo "    / /        / /    ) )      / __ \       "
echo "   / /        / /__ _)_)      / /_/ /\      "
echo "  /_/        /_/              \____/\ \     "
#hsEnterprise xx企业版
#hsStore      xxStore版
#vkeEnterprise   Vx企业版
#vkeStore        xxStore版
#xuetuEnterprise    x途企业版

#*****************************************设置区域**********************************************#

#设置archive生成的.xcarchive文件路径(绝对路径)
archiveProducePath="/Users/hsedu/Desktop/ipa/"
#设置ipa包生成路径(绝对路径)
ipaProducePath="/Users/hsedu/Desktop/ipa/"
#设置Plist文件路径前半部分部分(后半部分需要动态拼接)
infoPlist_HEAD_PATH=$(pwd)
#设置exportOptions.plist文件路径
optionsPlist_FILE_PATH="$(pwd)/exportOptions.plist"
#APP版本号
APPVERSION="2.1.7"
#设置是打测试版还是发布版本(Release or Debug)
configuration="Release"
#设置为哪些App打包ipa hsEnterprise vkeEnterprise xuetuEnterprise vkeStore hsStore
archiveIpas=" vkeStore vkeEnterprise xuetuEnterprise vkeStore hsStore";
#设置APP所对应的渠道标志
function getChannel()
{
    channel=""
    case $1 in
    hsEnterprise)
    channel="www xxbang xixxwu xxshi";
    ;;
    hsStore)
    channel="www xx";
    ;;
    vkeEnterprise)
    channel="xxx xx";
    ;;
    vkeStore)
    channel="xxx xx";
    ;;
    xuetuEnterprise)
    channel="www xx";
    ;;
    esac
    echo $channel;
}
#设置APP所对应的teamID的名字
function getteamID()
{
    teamid=""
    case $1 in
    hsEnterprise)
    teamid="93xxxxxYX";
    ;;
    hsStore)
    teamid="5xxxxAH36";
    ;;
    vkeEnterprise)
    teamid="93xxxxxX";
    ;;
    vkeStore)
    teamid="xxxxx28";
    ;;
    xuetuEnterprise)
    teamid="9xxxxZYX";
    ;;
    esac
    echo $teamid;
}
#设置APP所对应的CODE_SIGN_IDENTITY的名字
function getCODE_SIGN_IDENTITY()
{
    identity=""
    case $1 in
    hsEnterprise)
    identity="iPhone Distribution: xxx Network Technology Beijing Co.Ltd.";
    ;;
    hsStore)
    identity="iPhone Distribution: xxxao (5x8xxx6)";
    ;;
    vkeEnterprise)
    identity="iPhone Distribution: xxx Network Technology Beijing Co.Ltd.";
    ;;
    vkeStore)
    identity="iPhone Distribution: xxxx Network Technology (Beijing) Co.,Ltd. (Sxxxx8)";
    ;;
    xuetuEnterprise)
    identity="iPhone Distribution: xxxr Network Technology Beijing Co.Ltd.";
    ;;
    esac
    echo $identity;
}
#设置APP所对应的PROFILE_UUID的名字
function getPROFILE_UUID()
{
    identity=""
    case $1 in
    hsEnterprise)
    identity="7xxxxxx3-196exxcxx6";
    ;;
    hsStore)
#0exxd79-db21-4xxxcxxx8-118xxxxx5d6 adhoc
#36xx17xx-17xx-4xxx-b4xxx25xxxx66c appstore
    identity="0eb8xxx9-db21-4bxc-8exx-118xxxxxd6";
    ;;
    vkeEnterprise)
    identity="bdxxx6xx-9b92-4xx4-xxx8-cdxxxdb3";
    ;;
    vkeStore)
#183fxxxxxxxxx07 adhoc
#1535ba7dxxxxxxd98 appstore
    identity="1xxfxxx-2d2f-4xxc-8bxxx8-527fxx44b07";
    ;;
    xuetuEnterprise)
    identity="bccxxx9-cxxx-4xxx-xx0-2e8xxxx5";
    ;;
    esac
    echo $identity;
}
#*****************************************Coding**********************************************#
pwd | grep -q '[[:blank:]]' && {
echo "Source path: $(pwd)"
echo "Out of tree builds are impossible with whitespace in source path."
exit 1;
}

for produce in $archiveIpas
do
echo "${produce}"
#*****************************************修改infoPlistVersion**********************************************#
#拼接info.plist路径
infoPlist_FILE_PATH="${infoPlist_HEAD_PATH}/${produce}-info.plist"
echo "infoPlistPath:${infoPlist_FILE_PATH}"
#修改plist中Version
/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString $APPVERSION" $infoPlist_FILE_PATH
#输出修改后version
versionPrint=$(/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" $infoPlist_FILE_PATH)
echo "plistVersion:$versionPrint"

#****************************************修改exportOptionPlist***************************************#
#method : String
#Describes how Xcode should export the archive. Available options: app-store, package, ad-hoc, enterprise, development, and developer-id. The list of options varies based on the type of archive. Defaults to development.

methodString=""
B="Enterprise"
#判断是否包含Enterprise,从而判定是企业版还是stroe版证书
if [[ $produce == *$B* ]]
then
methodString="enterprise"
#包含
else
methodString="ad-hoc"
#不包含
fi

#修改plist中method
/usr/libexec/PlistBuddy -c "Set :method $methodString" $optionsPlist_FILE_PATH
#输出修改后method
methodPrint=$(/usr/libexec/PlistBuddy -c "Print method" $optionsPlist_FILE_PATH)

echo "plistMethod:$methodPrint"
#设置provisioningProfileName
teamID=$(getteamID ${produce})
echo "${teamID}"

#修改plist中teamID
/usr/libexec/PlistBuddy -c "Set :teamID $teamID" $optionsPlist_FILE_PATH
#输出修改后teamID
channelPrint=$(/usr/libexec/PlistBuddy -c "Print teamID" $optionsPlist_FILE_PATH)
echo "plistTeamID:$channelPrint"


for channel in $(getChannel ${produce})
do

#修改plist中channel
/usr/libexec/PlistBuddy -c "Set :channel $channel" $infoPlist_FILE_PATH
#输出修改后channel
channelPrint=$(/usr/libexec/PlistBuddy -c "Print channel" $infoPlist_FILE_PATH)
echo "plistChannel:$channelPrint"

PROFILE_UUID=$(getPROFILE_UUID ${produce})
IDENTITY=$(getCODE_SIGN_IDENTITY ${produce})
#archive workspace-用cocoaPods的
#xcodebuild -archivePath "${archiveProducePath}${produce}${channel}.xcarchive" -workspace hskaoyan.xcworkspace  -scheme "${produce}"  -sdk iphoneos -configuration ${configuration}  archive
#archive project-没用cocoaPods或者cocoaPods在工程内,不含workspace
xcodebuild -archivePath "${archiveProducePath}${produce}${channel}.xcarchive" -project hskaoyan.xcodeproj  -scheme "${produce}"   -sdk iphoneos -configuration ${configuration} CODE_SIGN_IDENTITY="$IDENTITY" PROVISIONING_PROFILE="$PROFILE_UUID" archive

#rvm list 可查看安装的ruby列表
#rvm system
#导出ipa
xcodebuild -exportArchive -archivePath "${archiveProducePath}${produce}${channel}.xcarchive"  -exportPath "${ipaProducePath}${produce}${channel}"   -exportOptionsPlist "$optionsPlist_FILE_PATH"

done


done

printf "\n(-_-)-Complete-(-_-)\n\n"


exit 0

注意

现在xcode都是自动签名的,因为脚本需要指定证书和描述文件所以工程中不能使用自动签名,当然也可以不指定,采用自动签名,但是具体打出包是什么类型不知道,没去研究。下面两个是遇到的错误及解决的办法

Error Domain=IDEDistributionErrorDomain Code=14 "No applicable devices found." UserInfo={NSLocalizedDescription=No applicable devices found.}

这里

遇到的坑:① Code=1 (这个操作不能完成)、② Code=14 (没有试用的设备 Domain=IDEDistributionErrorDomain Code=14 "No applicable devices found.)、③ "rvm use ..." rvm不可用的问题。

解决办法:code=1,证书有问题,重做证书。code=14,先rvm system,然后再export也可以。如果rvm不能用,出现rvm use ...、rvm找不到等问题,那就是使用的rvm或者rvm路径不正确,导致使用的默认ruby有问题,这里需要使用系统的ruby,需要查看本机rvm。个人遇到的问题是,本机rvm路径有问题,PATH里面的rvm路径也有问题,找不到正确的rvm以及ruby。unset rvm 清空PATH,重新添加相关路径(source /ect/paths)即可,删除rvm(不需要rvm,rvm system不能用就删除rvm不需要),重启。

如果工程中某个target报某个类没有找到,那一定是创建类的时候忘记勾选targt了,(我这是git两个分支合并的时候运行报的xxx这个类找不到)
点击这个类,右边类的属性中target memberShip勾选一下即可

勾选一下即可.pic.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容

  • 本文始发于我的博文详解Shell脚本实现iOS自动化编译打包提交,现转发至此。 目录 前言 Shell脚本涉及的工...
    zackzheng阅读 44,446评论 95 173
  • 起初想法: 基于公司原有的Jenkins服务的基础上,最近在公司自动化打包的时,遇到一个尴尬的问题?为什么不能直接...
    Evans_Xiao阅读 3,777评论 0 3
  • 本文转自CaryaLiu's Blog。 本文最终实现的是使用脚本打 Ad-hoc 包,并发布测试,当然稍微修改一...
    M_Baron阅读 1,002评论 1 0
  • #!/bin/bash #$1表示第一个参数 PROJECTPATH="$1" build_configurati...
    biggerworld阅读 397评论 0 0
  • 纪伯伦说过,“友谊要在温柔甜美中充满欢笑和同乐”,“不是为了填补你心灵的空虚,而是为了满足你的需要”。“因为在友谊...
    简简风阅读 298评论 0 6