版權(quán)聲明:本文為博主原創(chuàng)文章榛臼,轉(zhuǎn)載請附上原文出處鏈接
Jenkins+fastlane 統(tǒng)一打包 RN+iOS
Fastlane配置
iOS根目錄下,建一個fastlane文件夾响蕴,并保存
Fastfile
Appfile
Fastfile信息參考如下
fastlane_version "1.98.0"
WORKSPACE = ENV["WORKSPACE"]
IPA_SCHEME = ENV["IPA_SCHEME"]
IPA_NAME = ENV["IPA_NAME"]
IPA_BUILD = ENV["IPA_BUILD"]
IPA_CODESIGN = ENV["IPA_CODESIGN"]
IPA_PROVISION = ENV["IPA_PROVISION"]
IPA_PROVISION_SPECIFIER = ENV["IPA_PROVISION_SPECIFIER"]
IPA_BUNDLEID = ENV["IPA_BUNDLEID"]
TEST_SERVER_URL = ENV["TEST_SERVER_URL"]
TEST_SERVER_PATH = ENV["TEST_SERVER_PATH"]
IPA_ENV = ENV["IPA_ENV"]
IPA_TEAM = ENV["IPA_TEAM"]
if "#{IPA_TEAM}" != "" then
IPA_TEAM_DEFINE = "DEVELOPMENT_TEAM=\'#{IPA_TEAM}\'"
else
IPA_TEAM_DEFINE = ""
end
# 獲取工程屬性
def getBuildSetting(scheme, key)
# 嘗試從工程的plist文件中查找
plistPath = `xcodebuild -showBuildSettings -workspace ../#{WORKSPACE}.xcworkspace -scheme #{scheme} | grep INFOPLIST_FILE | awk -F '=' '{print $2}' | awk '{$1=$1;print}' | tr -d '\n'`
value = get_info_plist_value(path: "#{plistPath}", key: "#{key}")
# 查看從plist中找到的結(jié)果是否指向工程屬性
if value
key = /(?=\$\()[^\)]*\)/.match("#{value}")
if ! key
return value
end
# 重新定義工程屬性的名稱
key = value[2,value.length-3]
end
# 如果plist中沒有查找到或指向了工程中的屬性,則從工程屬性中查找
value = `xcodebuild -showBuildSettings -workspace ../#{WORKSPACE}.xcworkspace -scheme #{scheme} | grep \" #{key}\" | awk -F '=' '{print $2}' | awk '{$1=$1;print}' | tr -d '\n'`
return value
end
# 生成下載用的PLIST文件
def genPlist(url, ipaName, name, version, bundleID, plistFile)
plistContent = "<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\n"+
"<!DOCTYPE plist PUBLIC \\\"-//Apple//DTD PLIST 1.0//EN\\\" \\\"http://www.apple.com/DTDs/PropertyList-1.0.dtd\\\">\n"+
"<plist version=\\\"1.0\\\">\n"+
" <dict>\n"+
" <key>items</key>\n"+
" <array>\n"+
" <dict>\n"+
" <key>assets</key>\n"+
" <array>\n"+
" <dict>\n"+
" <key>kind</key>\n"+
" <string>software-package</string>\n"+
" <key>url</key>\n"+
" <string>#{url}/#{ipaName}</string>\n"+
" </dict>\n"+
# " <dict>\n"+
# " <key>kind</key>\n"+
# " <string>full-size-image</string>\n"+
# " <key>needs-shine</key>\n"+
# " <false/>\n"+
# " <key>url</key>\n"+
# " <string>#{url}/full-size-image.png</string>\n"+
# " </dict>\n"+
# " <dict>\n"+
# " <key>kind</key>\n"+
# " <string>display-image</string>\n"+
# " <key>needs-shine</key>\n"+
# " <false/>\n"+
# " <key>url</key>\n"+
# " <string>#{url}/display-image.png</string>\n"+
# " </dict>\n"+
" </array>\n"+
" <key>metadata</key>\n"+
" <dict>\n"+
" <key>title</key>\n"+
" <string>#{name}</string>\n"+
" <key>bundle-version</key>\n"+
" <string>#{version}</string>\n"+
" <key>bundle-identifier</key>\n"+
" <string>#{bundleID}</string>\n"+
" <key>kind</key>\n"+
" <string>software</string>\n"+
" </dict>\n"+
" </dict>\n"+
" </array>\n"+
" </dict>\n"+
"</plist>"
`echo \"#{plistContent}\" > #{plistFile}`
end
# 制作ipa
def genIPA(isTestEnv)
`rm -rf ../build`
# 鑰匙串的路徑為 ${HOME}/Library/Keychains/ 惠桃,我們需要操作的是 login.keychain-db 或者 login.keychain
#示例 security set-key-partition-list -S apple-tool:,apple: -s -k keychainPass keychainName
#具體 security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k 123456 ~/Library/Keychains/login.keychain-db
#更新 bundleID
update_app_identifier(
xcodeproj: "./#{WORKSPACE}.xcodeproj", # Optional path to xcodeproj, will use the first .xcodeproj if not set
plist_path: "./#{WORKSPACE}/Info.plist", # Path to info plist file, relative to xcodeproj
app_identifier: "#{IPA_BUNDLEID}" # The App Identifier
)
#更新配置文件設(shè)置浦夷,只對打包的 scheme 進(jìn)行修改辖试,其他不動
update_project_provisioning(
xcodeproj: "./#{WORKSPACE}.xcodeproj",
profile: "./profile.mobileprovision", # optional if you use sigh
target_filter: "#{IPA_SCHEME}", # matches name or type of a target
build_configuration: "Release",
code_signing_identity: "#{IPA_CODESIGN}" # optionally specify the codesigning identity
)
gym(
workspace: "./#{WORKSPACE}.xcworkspace",
scheme: "#{IPA_SCHEME}",
clean: true,
derived_data_path: './build/',
output_directory: './build/',
archive_path: './build/',
output_name: "#{IPA_SCHEME}",
configuration: 'Release',
codesigning_identity: "#{IPA_CODESIGN}",
include_symbols: 'true',
include_bitcode: 'false',
export_xcargs: "-allowProvisioningUpdates",
toolchain: "cn.ijiami.obf",
export_method: "#{IPA_ENV}",
export_options: {
provisioningProfiles: {
"#{IPA_BUNDLEID}":"#{IPA_PROVISION}"
},
},
xcargs: "PROVISIONING_PROFILE_SPECIFIER='#{IPA_PROVISION}' PRODUCT_BUNDLE_IDENTIFIER='#{IPA_BUNDLEID}' ENVIRONMENT_TEST='#{isTestEnv}' CURRENT_PROJECT_VERSION='#{IPA_BUILD}' PROVISIONING_PROFILE_SPECIFIER='#{IPA_PROVISION_SPECIFIER}' PRODUCT_BUNDLE_NAME='#{IPA_NAME}' #{IPA_TEAM_DEFINE}"
)
end
# 制作framework
def genFramework(isTestEnv)
`rm -rf ../build`
xcodebuild(
workspace: "../#{WORKSPACE}.xcworkspace",
scheme: "#{FRAMEWORK_SCHEME}",
derivedDataPath: './build/',
archive_path: './build/',
configuration: 'Release',
xcargs: "CODE_SIGN_IDENTITY='#{FRAMEWORK_CODESIGN}' PROVISIONING_PROFILE='#{FRAMEWORK_PROVISION}' PRODUCT_BUNDLE_IDENTIFIER='#{FRAMEWORK_BUNDLEID}' ENVIRONMENT_TEST='#{isTestEnv}'"
)
`cp -r ../build/Build/Products/Release-iphoneos/#{FRAMEWORK_SCHEME}.framework ../build/`
end
# ipa發(fā)布到測試服務(wù)器
def uploadTestServer(isTestEnv, isAppStore, isFramework)
# 創(chuàng)建打包目錄
foldername = `date +%s | tr -d '\n'`
if isFramework then
foldername = "#{FRAMEWORK_SCHEME}_#{foldername}"
else
foldername = "#{IPA_SCHEME}_#{foldername}"
end
`mkdir ../build/#{foldername}`
# 創(chuàng)建描述文件,一行代表一個信息:
# 工程名稱
# 軟件名稱
# 版本號
# build號
# 創(chuàng)建時間
# 運(yùn)行環(huán)境
if isFramework then
`echo '#{FRAMEWORK_SCHEME}' > ../build/#{foldername}/index.link`
`echo '#{FRAMEWORK_SCHEME}' >> ../build/#{foldername}/index.link`
`echo '#{FRAMEWORK_VERSION}' >> ../build/#{foldername}/index.link`
`echo '#{FRAMEWORK_BUILD}' >> ../build/#{foldername}/index.link`
else
`echo '#{IPA_SCHEME}' > ../build/#{foldername}/index.link`
`echo '#{IPA_NAME}' >> ../build/#{foldername}/index.link`
`echo '#{IPA_VERSION}' >> ../build/#{foldername}/index.link`
`echo '#{IPA_BUILD}' >> ../build/#{foldername}/index.link`
end
package_time = `date '+%Y-%m-%d %H:%M:%S' | tr -d '\n'`
`echo '#{package_time}' >> ../build/#{foldername}/index.link`
if isTestEnv && isAppStore then #這里是聯(lián)調(diào)
`echo 'joinDebug' >> ../build/#{foldername}/index.link`
elsif isTestEnv then
`echo 'test' >> ../build/#{foldername}/index.link`
elsif isAppStore then
`echo 'appstore' >> ../build/#{foldername}/index.link`
else
`echo 'product' >> ../build/#{foldername}/index.link`
end
if isFramework then
`echo 'framework' >> ../build/#{foldername}/index.link`
else
`echo 'application' >> ../build/#{foldername}/index.link`
end
# 將ipa或者framework文件復(fù)制到該目錄中
if isFramework then
`mv ../build/#{FRAMEWORK_SCHEME}.framework ../build/#{foldername}/`
`cp -r ../#{FRAMEWORK_SCHEME}/#{FRAMEWORK_SCHEME}.bundle ../build/#{foldername}/`
else
`mv ../build/#{IPA_SCHEME}.ipa ../build/#{foldername}/d.ipa`
genPlist("#{TEST_SERVER_URL}/#{foldername}", "d.ipa", "#{IPA_NAME}", "#{IPA_VERSION}", "#{IPA_BUNDLEID}", "../build/#{foldername}/d.plist")
end
`upload_app ../build/#{foldername} iPhone`
end
default_platform :ios
platform :ios do
before_all do
# 設(shè)置全局變量
ENV['FASTLANE_XCODE_LIST_TIMEOUT'] = '600'
if "#{IPA_NAME}" == "" then
IPA_NAME = getBuildSetting("#{IPA_SCHEME}", 'CFBundleDisplayName')
end
IPA_VERSION = getBuildSetting("#{IPA_SCHEME}", 'CFBundleShortVersionString')
if "#{IPA_BUILD}" == "" then
IPA_BUILD = getBuildSetting("#{IPA_SCHEME}", 'CFBundleVersion')
end
cocoapods(
repo_update: false
)
end
desc "打測試包"
lane :test do |options|
genIPA(1)
uploadTestServer(true, false, false)
end
desc "打生產(chǎn)包"
lane :product do |options|
genIPA(0)
uploadTestServer(false, false, false)
end
desc "打appstore包"
lane :appstore do |options|
genIPA(0)
uploadTestServer(false, true, false)
end
after_all do |lane|
end
error do |lane, exception|
end
end
Appfile信息參考如下
app_identifier "" # The bundle identifier of your app
apple_id "david.yi@chinapnr.com" # Your Apple email address
team_id "Y3697NHB4K" # Developer Portal Team ID
創(chuàng)建Jenkins項(xiàng)目
配置全局參數(shù)如下
參數(shù)名稱 | 參數(shù)類型 | 默認(rèn)值 | 描述 | 參數(shù)值 | 備注 |
---|---|---|---|---|---|
RN_VERSION | 字符參數(shù) | 無 | RN bundle 版本號 | ? | 選中下方的“Trim the string”選項(xiàng) |
RN_BUILD_TYPE | 選項(xiàng)參數(shù) | 無 | RN 打包類型(Baseline:基線包劈狐,Extra:增量包) | Baseline/Extra | ? |
GIT_BRANCH_RN | Git Parameter | 無 | RN 項(xiàng)目的 git 地址 | ? | 1."Parameter Type" 選擇"Branch or Tag", 2."Advanced"中 "Use repository" 填入RN 項(xiàng)目git 地址 |
BUILD_IOS_IPA | 布爾值參數(shù) | 無 | 是否構(gòu)建iOS應(yīng)用 | ? | 如不勾選罐孝,默認(rèn)只構(gòu)建RN項(xiàng)目 |
GIT_BRANCH_IOS | Git Parameter | 無 | iOS 項(xiàng)目的 git 地址 | ? | 1."Parameter Type" 選擇"Branch or Tag", 2."Advanced"中 "Use repository" 填入 iOS 項(xiàng)目git 地址 |
IPA_EXPORT_ENV | Extend choice parameter | development | ios 打包類型 | development,app-store,ad-hoc | 三種,默認(rèn)是 development ,如果打的是 appStore 包,無論選什么,都會強(qiáng)制改為 app-store |
ENVIRONMENT_CHOICE | 選項(xiàng)參數(shù) | test | 環(huán)境標(biāo)志位 | test/product/preproduct/appstore | 測試/生產(chǎn)/準(zhǔn)生產(chǎn)/App Store(生產(chǎn)環(huán)境) |
IPA_NAME | hidden parameter | 無 | 應(yīng)用名稱 | 無 | 應(yīng)用APP顯示名稱 |
WORKSPACE | hidden parameter | 無 | 工程名稱 | 無 | 工程名稱 |
IPA_SCHEME | hidden parameter | 無 | 工程中要打包的目標(biāo)名稱 | 無 | 工程中要打包的目標(biāo)名稱 |
IPA_BUNDLEID | hidden parameter | 無 | 工程bundleID | 無 | bundleID |
IPA_CODESIGN_TEST | hidden parameter | 無 | codesign | 無 | 只要不是 AppStore 肥缔,codesign 都是這一個 |
IPA_CODESIGN_APPSTORE | hidden parameter | 無 | appstore codesign | 無 | appstore codesign |
TEST_SERVER_URL | hidden parameter | http://oa2pro.chinapnr.com/mobile/CI/iPhone/ | 測試服務(wù)器的地址 | 無 | 打包成功后應(yīng)用上傳地址 |
podUpdate | 布爾值參數(shù) | 無 | 是否要清除緩存莲兢,更新pod | 無 | 除非SDK有更新,此項(xiàng)不需要勾選 |
如圖
源碼管理
分別設(shè)置RN與iOS的git信息
設(shè)置App Store賬號信息
直接選擇app store賬號
添加RN打包腳本
#打包RN包
cd "rn"
export rnPath="./ios/RNBundle"
if [ -d "${rnPath}" ]; then
rm -r ${rnPath}
mkdir ${rnPath}
else
mkdir ${rnPath}
fi
npm install
react-native bundle --entry-file index.js --bundle-output ${rnPath}/index.ios.bundle --platform ios --assets-dest ${rnPath} --dev false
if [! -f "${rnPath}/index.ios.bundle"]; then
echo "index.ios.bundle 生成失敗"
exit 1001
else
echo "生成成功"
fi
echo "{'rn_version':'${RN_VERSION}','rn_build':'${BUILD_NUMBER}'}" >> ${rnPath}/rn_info.json
添加iOS打包腳本
#清pod緩存
if [ "$podUpdate" = "true" ]; then
#是否要清除緩存续膳,更新pod
pod cache clean --all
pod repo update
fi
#打包IPA
if [ "$BUILD_IOS_IPA" = "true" ]; then
if [ "$IPA_EXPORT_ENV" = "app-store" ]; then
export IPA_CODESIGN=$IPA_CODESIGN_APPSTORE
else
export IPA_CODESIGN=$IPA_CODESIGN_TEST
fi
export IPA_BUNDLEID=$IPA_BUNDLEID
export IPA_BUILD=$BUILD_NUMBER
export IPA_ENV=$IPA_EXPORT_ENV
cd "./rn/ios"
export
if [ "$IPA_EXPORT_ENV" = "app-store" ]; then
result=`fastlane sigh -u $FASTLANE_ID -a $IPA_BUNDLEID --app-store true -q profile.mobileprovision`
export IPA_PROVISION=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
export IPA_PROVISION_SPECIFIER=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
elif [ "$IPA_EXPORT_ENV" = "ad-hoc" ]; then
result=`fastlane sigh -u $FASTLANE_ID -a $IPA_BUNDLEID --adhoc true -q profile.mobileprovision`
export IPA_PROVISION=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
export IPA_PROVISION_SPECIFIER=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
elif [ "$IPA_EXPORT_ENV" = "development" ]; then
result=`fastlane sigh -u $FASTLANE_ID -a $IPA_BUNDLEID --development true -q profile.mobileprovision`
export IPA_PROVISION=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
export IPA_PROVISION_SPECIFIER=`security cms -D -i profile.mobileprovision | sed -n -e '/<key>Name/,/string>/p' | sed -n '/string>/p' | awk -F '>' '{print $2}' | awk -F '<' '{print $1}'`
fi
if [ "$ENVIRONMENT_CHOICE" = "test" ]; then
result=`fastlane test`
echo 'test'
elif [ "$ENVIRONMENT_CHOICE" = "product" ]; then
echo 'product'
result=`fastlane product`
elif [ "$ENVIRONMENT_CHOICE" = "appstore" ]; then
echo 'app_Store'
result=`fastlane appstore`
fi
fi
上傳RN bundle
#壓縮RN包并上傳
cd ./rn/ios/RNBundle
echo "開始壓縮"
export RN_ZIP_NAME="${IPA_SCHEME}_v${RN_VERSION}_b${BUILD_NUMBER}_ios_${RN_BUILD_TYPE}.zip"
export RN_BUILD_TYPE=${RN_BUILD_TYPE}
zip -r "${RN_ZIP_NAME}" . -x ".*" -x "__MACOSX" ".DS_Store"
cd ../..
# 創(chuàng)建打包目錄
export foldername="${IPA_SCHEME}_$(date +%s | tr -d '\n')"
mkdir ./${foldername}
# 創(chuàng)建描述文件改艇,一行代表一個信息:
echo "${IPA_SCHEME}" > ./${foldername}/index.link # 工程名稱
echo "${IPA_NAME}" >> ./${foldername}/index.link # 軟件包名稱
echo "${RN_VERSION}" >> ./${foldername}/index.link # 版本號
echo "${BUILD_NUMBER}" >> ./${foldername}/index.link # build號
export package_time=$(date +'%Y-%m-%d %T')
echo "${package_time}" >> ./${foldername}/index.link # 創(chuàng)建時間
if RN_BUILD_TYPE="BaseLine"; then
echo "baseLine" >> ./${foldername}/index.link # 基線包
else
echo "extra" >> ./${foldername}/index.link # 差量包
fi
echo "application" >> ./${foldername}/index.link
echo "${RN_ZIP_NAME}" >> ./${foldername}/index.link #文件夾名稱
# 上傳RN包
mv ./ios/RNBundle/${RN_ZIP_NAME} ./${foldername}/${RN_ZIP_NAME}
upload_app ./${foldername} ReactNative_I
# 移除文件夾
rm -rf ./${foldername}
rm -f ./"${foldername}.zip"
cd ..
rm -rf ./rn
構(gòu)建項(xiàng)目
構(gòu)建成功
構(gòu)建成功后根據(jù)構(gòu)建的項(xiàng)目名稱,build號下載
RN下載地址
IPA下載地址