記一次iOS自動化打包走過的坑-關於React Native-iOS專案

樂帥發表於2019-03-04

引言

最近為公司前期做的一個專案做持續構建平臺打包遷移支援,由於之前未參與類似工作,且我也基本未參與這個專案開發工作,所以途中磕磕碰碰遇到了很多很多的問題,而且由於專案屬於React Native專案,而且程式碼版本較老,更是出現了很多無法預料的問題,因而結合本次Jenkins使用的shell打包指令碼及自動化打包過程中遇到的各種日誌和問題做一個總結,希望給有需要的人做一個參考,也給自己加深理解。本次打包環境如下:

node:V8.3.0
Xcode:xcode8,
iOS:iOS8以後環境,
macOS: macOS Sierra 10.12複製程式碼

思路和相關知識

首先應該明確指令碼打包和用Xcode打包做的是同一件事情,因而你在xcode裡面打包進行的各種操作其實都應該對應在指令碼中的每一句命令,總體上對iOS打包有以下工作階段

  1. 證書配置、專案Target及scheme相關配置
  2. 實際打包過程,包括清理、編譯、build構建、Archive存檔、Export匯出ipa
  3. 掃描ipa包、上傳或歸檔至指定位置供使用者下載(這些屬於個人操作,各有不同)

而指令碼中的命令當然也是要一一完成這些配置和操作,這裡不會去說明如何自動化打出各種不同型別的包,當懂得指令碼打包的具體原理和命令後,自然懂得指令碼打包和手動打包其實是一樣的,從而懂得如何去優化指令碼自動化打出各種不同型別的包。

關於xcodebuild命令的使用和說明應該去了解相關資料,其中打包常用命令如下:

xcodebuild clean // 等同於Xcode下點選Product -> Clean
xcodebuild -configuration //以指定配置編譯並構建專案,等同於Xcode下點選Product -> Build
xcodebuild -xcworkspace // 等同於Xcode下點選執行xcworkspace
xcodebuild -xcodeproj // 等同於Xcode下點選執行xcodeproj
xcodebuild archive // 等同於Xcode下點選Product -> Archive
xcodebuild -exportArchive // 等同於Export匯出ipa複製程式碼

實現和指令碼

部分引數和函式定義

#!/bin/sh
###引數說明
#=======================================證書和版本配置資訊 ======================================
# 網路框架環境: int; stg; prd
env=prd
# 原版本號
S_VERSION="0.0.1"
# 修改後的版本號
T_VERSION="0.0.1"
# 原程式顯示名稱
S_APP_DISPLAY_NAME="Example1"
# 修改後成型顯示名稱
T_APP_DISPLAY_NAME="Example2"
# 裝置型別
device="iPhone"
# 打包描述檔案Provision Profile的對應UUID
PROVISION_PROFILE_NAME="********-****-****-****-************"
# 打包證書名稱
CODESIGN_INDENTITY_NAME="iPhone Distribution: **************"
# APP ID
BUNDLE_IDENTIFIER="com.*******.***"
#===================================== function =====================================
#獲取包名字尾
function getPackageFix(){
    if [ "$env" == "prd" ];then
        echo "prd";
    elif [ "$env" == "int" ];then
        echo "int";
    elif [ "$env" == "stg" ];then
        echo "stg";
    fi
}
#獲取時間戳,格式:yyyymmddHHMMSS
function getTime(){
    echo `date +%Y``date +%m``date +%d``date +%H``date +%M``date +%S`;
}
#獲取裝置型別簡稱
function getDeviceType(){
    if [ "$device" == "iPhone" ];then
        echo "h"
    elif [ "$device" == "iPad" ];then
        echo "a"
    fi
}
#======================================= 路徑及編譯引數===================================
#工作空間
workspace=`pwd`
#工程路徑
projectName="*******"
projectTmpName="*******"
projectBase=*******
projectTmpBase=*******
#ipa包輸出路徑
outputName="*******"
output= *******
#編譯引數
configuration="Release"
targetStr="******"
iphoneos="iphoneos10.0"
IPHONEOS_DEPLOYMENT_TARGET="8.0"
#ipa名稱
ipaMain="*******"
ipaName=${ipaMain}-$(getPackageFix)-$(getVersion)-$(getTime).ipa
ipaShortName=$ipaMain.ipa
appName=$targetStr.app
DSYMName=$targetStr.app.dSYM複製程式碼

環境初始化與專案配置操作

#===================================== 初始化和備份工程 ======================================
cd $workspace
mkdir -pv $output
cd $output
rm -rf $ipaShortName

cd $workspace
echo "remove project bak start..."
rm -rf $projectTmpName
echo "remove project bak end."

echo "make project bak start......"
cp -rf $projectName $projectTmpName
echo "make project bak end."

#====================================== 字元替換 ===============================
#1.修改plist中Bundle identifier,app顯示名,設定版本號
fn="info.plist"
cd $projectTmpBase/ios/Example
sed "s/<string>com.*<\/string>/<string>${BUNDLE_IDENTIFIER}<\/string>/g" $fn>1.plist
sed "s/<string>${S_APP_DISPLAY_NAME}<\/string>/<string>${T_APP_DISPLAY_NAME}<\/string>/g" 1.plist>2.plist
sed "s/<string>${S_VERSION}<\/string>/<string>${T_VERSION}<\/string>/g" 2.plist>3.plist

cat 3.plist>$fn
rm -rf 3.plist
rm -rf 2.plist
rm -rf 1.plist

echo "print ID: ${BUNDLE_IDENTIFIER} "
echo "print $fn start..."
cat $fn
echo "print $fn end."

#2、修改project.pbxproj
fn="project.pbxproj"
cd $projectTmpBase/ios/*****.xcodeproj
sed "s/PRODUCT_BUNDLE_IDENTIFIER = .*;/PRODUCT_BUNDLE_IDENTIFIER = ${BUNDLE_IDENTIFIER};/g" $fn>1.pbxproj
sed "s/DevelopmentTeam = .*//g" 1.pbxproj>2.pbxproj
sed "s/DEVELOPMENT_TEAM = .*/DEVELOPMENT_TEAM = \"\";/g" 2.pbxproj>3.pbxproj

cat 3.pbxproj>$fn
rm -rf 3.pbxproj
rm -rf 2.pbxproj
rm -rf 1.pbxproj

echo "print $fn start..."
cat $fn
echo "print $fn end."複製程式碼

編譯、打包與歸檔匯出

#====================================== 編譯與打包 ==========================================
#clean and compile
appPath="$projectTmpBase/ios/build/$configuration-iphoneos/$appName"
ipaPathWithResigned="$projectTmpBase/ios/${ipaName}"
archivPath="$projectTmpBase/ios/build/$configuration-iphoneos/*****.xcarchive"
cd $projectTmpBase/ios

echo "clean start..."
 xcodebuild clean -configuration $configuration -target $targetStr
echo "clean end."

echo "compile start......"
xcodebuild -configuration $configuration -sdk $iphoneos -target $targetStr IPHONEOS_DEPLOYMENT_TARGET=$IPHONEOS_DEPLOYMENT_TARGET CODE_SIGN_IDENTITY="$CODESIGN_INDENTITY_NAME" PROVISIONING_PROFILE=$PROVISION_PROFILE_NAME
echo "compile end."

echo "xcodebuild archive start...."
xcodebuild archive -project "${ipaMain}.xcodeproj" -scheme $ipaMain -configuration $configuration -archivePath "${archivPath}" CODE_SIGN_IDENTITY="$CODESIGN_INDENTITY_NAME" PROVISIONING_PROFILE=$PROVISION_PROFILE_NAME
xcodebuild -exportArchive -archivePath "${archivPath}" -exportPath "${ipaPathWithResigned}" -exportFormat IPA -exportProvisioningProfile "*****"
echo "xcodebuild archive end...."

#====================================== 歸檔 ===============================================

#output the ipa file to special target
echo "move ipaFile to special target start......"

cd $output
timeDir=$(getTimeFormat)
if [ ! -d "$timeDir" ];then
mkdir $timeDir
fi

#歸檔.app,ipa,dSYM檔案
cd $projectTmpBase/ios
cp -rf $ipaPathWithResigned $output/$timeDir/$ipaShortName
mv $ipaPathWithResigned $output/$timeDir
echo "move ipaFile to special target done."

#刪除.svn檔案
cd $projectTmpBase
find . -type d -name ".svn" |xargs rm -rvf

#拷貝*.ipa到根目錄
cp -rf $output/$timeDir/$ipaShortName $workspace複製程式碼

修改指令碼和編寫指令碼時,在網上看了很多相關資料,同樣的在網上也能找到很多關於iOS打包的shell指令碼,在這裡我的建議是,不要把他人的指令碼直接拿去使用,因為各個環境不同,情況不同,專案不同等原因,他人提供的指令碼不可能直接能在你的環境中也能跑批成功,應該以他人指令碼為參考,從而結合自己的環境、實際操作修改和寫出一份自己的指令碼來實現自己的自動化打包指令碼。

可能遇到問題與解決方案

由於本次打包是React Native的iOS專案,因而其中有一些問題是專案特殊問題,通常原生專案並不會出現,但是可以當作一種參考。

iOS證書及相關配置錯誤

Check dependencies
****** requires a provisioning profile. Select a provisioning profile for the "Release" build configuration in the project editor.
Code signing is required for product type 'Application' in SDK 'iOS 10.0'

** BUILD FAILED **複製程式碼
Check dependencies
No certificate matching 'iPhone Distribution: *****************' for team '*******':  Select a different signing certificate for CODE_SIGN_IDENTITY, a team that matches your selected certificate, or switch to automatic provisioning.
Provisioning profile "******" belongs to team "**************", which does not match the selected team "*********".
Code signing is required for product type 'Application' in SDK 'iOS 10.0'

** BUILD FAILED **複製程式碼

這種問題都是出現在執行xcodebuild命令的時候,證書配置問題錯誤,請首先檢查證書是否有效,指令碼中關於證書的設定項是否正確,jenkins的證書相關設定是否正確,在網上的相關問題中,有在指令碼中進行對project.pbxproj專案檔案進行字串替換和修改等操作來實現證書配置等操作,這裡建議不要使用這種方法,每一代的xcode升級對project.pbxproj專案也許會有些許不同。更好的方式應該使用xcodebuild的相關引數來對進行直接指定證書配置打包,如上面的指令碼。同時確保你的專案關閉證書自動管理。如下:

或命令列中以字串替換方式修改project.pbxproj時直接進行設定

sed "s/ProvisioningStyle = .*/ProvisioningStyle = Manual;/g" 0.pbxproj>1.pbxproj複製程式碼

關於第三方框架和Build Phases問題

clang: error: no such file or directory: '/Users/admin/jenkins/new_slave8/workspace/CI_PIPELINE_1302_8285_ios_build/tmp/node_modules/react-native/React/build/Release-iphoneos/libReact.a'
......
clang: error: no such file or directory: '/Users/admin/jenkins/new_slave8/workspace/CI_PIPELINE_1302_8285_ios_build/tmp/node_modules/react-native/Libraries/WebSocket/build/Release-iphoneos/libRCTWebSocket.a'複製程式碼

React Native的特殊問題,在編譯時沒有連結上官方的React Native相關靜態庫檔案,查閱了相關資料,一般來說,正常也不會出現的,也許是我工作的jenkins平臺環境不一樣,居然有與眾不同的問題出現,但是總歸出問題了,世界很大,還真的是也有人也有類似情況,這裡介紹兩種方法,都是谷歌到的類似情況,我是使用第一種方法解決了,第二種方法是類似問題的參考:

  1. Target -> Build Phases -> Target Dependencies

  1. Target -> Build Setting -> Search Paths -> Library Search Paths

ld: library not found for -lRNDeviceInfo
clang: error: linker command failed with exit code 1 (use -v to see invocation)複製程式碼

這是指令碼打包編譯時才會偶爾出現的問題,如你已經確定這個庫確定已在Link Binary With Libraries中新增,請檢視是否出現該庫的前面小圖示不是很正常,如下:

這也許是一種快取問題,也許是React Native引入其他庫,npm第三方庫連結等等多種原因引起的,你只需要在Link Binary With Libraries中手動刪除了靜態庫,然後重新新增,庫的標誌和連結等都會和官方庫一樣正常,問題自然解決。

許可權控制問題

   /bin/sh -c /Users/admin/jenkins/new_slave8/workspace/CI_PIPELINE_1302_8285_ios_build/tmp/ios/build/*****.build/Release-iphoneos/******.build/Script-00DD1BFF1BD5951E006B06BC.sh
/Users/admin/jenkins/new_slave8/workspace/CI_PIPELINE_1302_8285_ios_build/tmp/ios/build/******.build/Release-iphoneos/*****.build/Script-00DD1BFF1BD5951E006B06BC.sh: line 3: ../node_modules/react-native/packager/react-native-xcode.sh: Permission denied複製程式碼

這又是打包環境引起的問題,對React Native下的繫結指令碼執行失去了讀寫許可權,直接在指令碼中在開始編譯前,對路徑下進行讀寫賦權就好了。

#手動賦予許可權給node_modules下的相關依賴
cd $workspace
chmod -R 777 tmp/node_modules/react-native複製程式碼

總結

花了接近一個星期的時間,我完成了本次自動化打包的工作,其實東西搞懂了也不是很難,中途指令碼打包快接近80次了,終於解決了所有問題,感覺還是值得的。途中遇到的那些問題,如果對你也有幫助,那很高興可以幫助到你。上面的命令很多還是很有趣的,喜歡的可以自己查一下資料,就會懂得指令碼如何寫,還有什麼命令是很有趣的。

努力學習,共同進步,付出才有回報!!!!

相關文章