Xcode编译工程的时候,可以在Build Phases里增加Run Script脚本,用来打包SDK、运行Python修改工程信息或者其他功能,这里简单介绍一下使用Shell脚本来辅助打包SDK的相关知识;
在打包SDK的时候,有时要打模拟器和真机包2个包,然后进行合包从而生成模拟器和真机都支持的SDK,本文就是主要阐述如何通过Shell脚本,来实现上述步骤。
1.lipo
介绍Shell前,我们需要先了解一下lipo命令。什么是lipo?从字面意思理解,它是脂肪的意思,app为了兼容不同的架构(armv7, arm64, i386, x86_64...),需要将不同平台的编译程序合并起来,生成所谓的胖文件(Fat File),而我们的lipo,就是用来干这个的。
1.查看静态库(通用文件)支持的CPU架构
lipo -info libname.a
输出:Architectures in the fat file: libname.a are: i386 x86_64 armv7 arm64
2.合并静态库
#lipo -create 静态库存放路径1 静态库存放路径2 ... -output 整合后存放的路径
lipo -create libname-armv7.a libname-armv7s.a libname-i386.a -output libname.a
3.静态库拆分或者“瘦身”(提取单个平台)
# lipo 静态库源文件路径 -thin CPU架构名称 -output 拆分后文件存放路径
# 架构名为armv7/armv7s/arm64等,与lipo -info 输出的架构名一致
lipo libname.a -thin armv7 -output libname-armv7.a
4.提取、替换和去除指定CPU架构
# 提取出armv7架构并新建一个通用文件,类似于-thin选项。
lipo -extract armv7 libname.a -output libname_armv7.a
#去除armv7架构
lipo -remove armv7 libname.a -output libname_exceptArmv7.a
#对输入文件libname.a中的armv7架构文件采用librepace.a进行替换,并输出到liboutput.a中
lipo libname.a -replace armv7 libreplace.a -output liboutput.a
2.Shell 命令
暂时不打算仔细学习Shell语法,如果以后有经常使用Shell的场景,会回头再起文章记录,这里就简单记录一下Run Script里会用到的常见语法
1.shell中的各种括号
要注意的是,使用[]和[[]]的时候不要吝啬空格,每一项两边都要有空格,[[ 1 == 2 ]]的结果为“假”,但[[ 1==2 ]]的结果为“真”!后一种显然是错的
1.1 (): 括号中的命令将会新开一个子shell顺序执行,括号中多个命令之间用分号隔开
#具体使用场景暂时未了解
#个人理解了一下,貌似是类似于在终端直接输入,例如下面我本想判断环境变量ACTION的值
if ( "${ACTION}" == "install" )
#但是输出却是 install: ==: No such file or directory
1.2 (()) : 是一种数学计算命令,它除了可以进行最基本的加减乘除运算,还可以进行大于、小于、等于等关系运算,以及与、或、非逻辑运算。这种扩展计算是整数型的计算,不支持浮点型(没试过)。
#依然以这个判断为例,这样写就没问题了
if (( "${ACTION}" == "install" ))
#之前为了判断字符串相等,加了双引号,后来试了一下,不加双引号也没问题
if (( ${ACTION} == "install" ))
1.3 []:用来用于字符串比较的,不可用于整数比较
#同样的,加不加双引号都行,字符串比较的话还是用[]吧
if [ "${ACTION}" == "install" ]
if [ ${ACTION} == "install" ]
1.4 [[]]:在[[和]]之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换(暂时不理解,留作备注)。
#支持字符串的模式匹配,例如
[[ hello == hell? ]],结果为真
#[[ ]] 中匹配字符串或通配符,不需要引号
2.if elif else 语法
if语法其实很简单,写 if 和 else if的时候,后面加个then就完事,写完if整个逻辑后用fi收尾即可;
shell的if语法和C语言等高级语言非常相似,唯一需要注意的地方就是shell的if语句对空格方面的要求比较严格(其实shell对所有语法的空格使用都比较严格),如果在需要空格的地方没有打上空格,都会报错。
if (( $a == $b ))
then
echo "a和b相等"
else if (( $a > $b || $a < $b))
then
echo "a不等于b"
else
echo "进不来的"
fi
3.shell中条件判断if中的-z到-d的意思
[ -a FILE ] 如果 FILE 存在则为真。
[ -b FILE ] 如果 FILE 存在且是一个块特殊文件则为真。
[ -c FILE ] 如果 FILE 存在且是一个字特殊文件则为真。
[ -d FILE ] 如果 FILE 存在且是一个目录则为真。
[ -e FILE ] 如果 FILE 存在则为真。
[ -f FILE ] 如果 FILE 存在且是一个普通文件则为真。
[ -g FILE ] 如果 FILE 存在且已经设置了SGID则为真。
[ -h FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -k FILE ] 如果 FILE 存在且已经设置了粘制位则为真。
[ -p FILE ] 如果 FILE 存在且是一个名字管道(F如果O)则为真。
[ -r FILE ] 如果 FILE 存在且是可读的则为真。
[ -s FILE ] 如果 FILE 存在且大小不为0则为真。
[ -t FD ] 如果文件描述符 FD 打开且指向一个终端则为真。
[ -u FILE ] 如果 FILE 存在且设置了SUID (set user ID)则为真。
[ -w FILE ] 如果 FILE 如果 FILE 存在且是可写的则为真。
[ -x FILE ] 如果 FILE 存在且是可执行的则为真。
[ -O FILE ] 如果 FILE 存在且属有效用户ID则为真。
[ -G FILE ] 如果 FILE 存在且属有效用户组则为真。
[ -L FILE ] 如果 FILE 存在且是一个符号连接则为真。
[ -N FILE ] 如果 FILE 存在 and has been mod如果ied since it was last read则为真。
[ -S FILE ] 如果 FILE 存在且是一个套接字则为真。
[ FILE1 -nt FILE2 ] 如果 FILE1 has been changed more recently than FILE2,or 如果 FILE1 exists and FILE2 does not则为真。
[ FILE1 -ot FILE2 ] 如果 FILE1 比 FILE2 要老, 或者 FILE2 存在且 FILE1 不存在则为真。
[ FILE1 -ef FILE2 ] 如果 FILE1 和 FILE2 指向相同的设备和节点号则为真。
[ -o OPTIONNAME ] 如果 shell选项 “OPTIONNAME” 开启则为真。
[ -z STRING ] “STRING” 的长度为零则为真。
[ -n STRING ] or [ STRING ] “STRING” 的长度为非零 non-zero则为真。
3.Xcode 环境变量
ACTION
: 编译模式,archive->"install"、build->"build"
SRCROOT
: 根目录,~/Desktop/Linkplay/ios_muzoplayer/LPVBSKit
BUILD_ROOT
: 构建目录,~/Library/Developer/Xcode/DerivedData/.../Build/Products
CONFIGURATION
: 编译模式Debug、Release
PROJECT_NAME
: 工程名,LPVBSKit
TARGETNAME
: target名,LPBLESetupKit
BUILD_DIR
: 编译后路径,archive后里面分别有Release-iphoneos、iphonesimulator、universal三个文件夹 /Users/yining/.../LPVBSKit-xxx/.../LPBLESetupKit/BuildProductsPath
TARGET_BUILD_DIR
:也是编译路径,BUILD_DIR下的文件的原始地址 /Users/yining/.../LPVBSKit-xxx/.../LPBLESetupKit/IntermediateBuildFilesPath/UninstalledProducts/iphoneos
4.编译SDK的Shell
4.1 shell代码
格式化代码
加了空格仅仅是为了方便阅读
,真实环境不用加,也不确定加了是否对使用造成影响
#Release-universal文件目录
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
#项目下Products文件夹的路径
INSTALL_DIR=${SRCROOT}/Products
#创建Release-universal文件目录,实验了一下,即便不手动创建,archive也会自动创建
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
#删除Products文件夹
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi
#重新创建文件夹(-p 的作用是即使上级目录不存在,也会按目录层级自动创建目录,起一个保护作用,防止创建失败)
mkdir -p "${INSTALL_DIR}"
#判断是不是根调用,不是根调用,不要递归(估计就是防止多余调用)
if [ "false" == ${ALREADYINVOKED:-false} ]
then
#1.首先确认是在archive
if [ "${ACTION}" = "install" ]
then
#2. 设置环境变量 ALREADYINVOKED 为 true(防止多次调用,例如同个项目下,可能有多个targetSDK,如果相互引用,就能确保仅打包一个SDK了,**猜的!**)
export ALREADYINVOKED="true"
#3. 开始编译模拟器环境SDK
xcodebuild -target "${TARGETNAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
#4. 将生成的framework拷贝至universal文件夹下面(-R/r 是递归操作,会将目录下的文件/文件件一起拷贝)
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGETNAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
#5. 将Release-iphonesimulator/iphoneos文件夹下的framework文件夹下的执行文件合包,替换universal文件夹下framework的执行文件
# 注:虽然是替换了universal下的文件,但其实universal里的文件,指向的是TARGET_BUILD_DIR下的文件,所以修改universal下framework的文件,就是在修改源文件
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${TARGETNAME}.framework/${TARGETNAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${TARGETNAME}.framework/${TARGETNAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGETNAME}.framework/${TARGETNAME}"
#6. 打开Products文件夹,并把BUILD_DIR下的framework拷贝过来(可以右键查看原文件)
#注:这个framework其实指向的也是TARGET_BUILD_DIR下的framework,但是直接拷贝TARGET_BUILD_DIR的话,就没法右键查看原文件,其实没啥区别
open "${INSTALL_DIR}"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${TARGETNAME}.framework" "${INSTALL_DIR}/${TARGETNAME}.framework"
fi
fi
4.2 步骤说明
Release-iPhoneos 和 Release-universal下的framework指向的其实都是TARGET_BUILD_DIR的原文件
- archive后,先把Release-iPhoneos下的framework copy至Release-universal下
- 将Release-iphoneos和Release-iphonesimulator下的执行文件合包,并覆盖到Release-universal下
- 将Release-iphoneos拷贝至Products目录下,方便拿去使用
4.3 路径图解
- BUILD_DIR下的Release-iPhoneos/Release-universal
- TARGET_BUILD_DIR下的原文件