最近研究了一下iOS的自動(dòng)化編譯,,目的是為了簡化測試和開發(fā)的同學(xué)溝通協(xié)調(diào)的次數(shù),,實(shí)現(xiàn)測試同學(xué)可以隨時(shí)從網(wǎng)頁操作編譯SVN最新源碼并打包ipa進(jìn)行測試。
具體思路是通過從配置文件讀取需要編譯的項(xiàng)目配置列表展示在網(wǎng)頁上,,測試同學(xué)選擇需要編譯的項(xiàng)目,,確定后將選擇項(xiàng)目的相關(guān)參數(shù)傳入shell腳本運(yùn)行,編譯完成自動(dòng)跳轉(zhuǎn)至下載頁面,。
主要流程包括:
- Shell腳本的編寫。通過xcodebuild和xcrun實(shí)現(xiàn)自動(dòng)編譯并打包,。
- PHP調(diào)用腳本,。開啟OS X自帶的Apache服務(wù)器,,編寫PHP來調(diào)用shell。
編寫shell腳本
自動(dòng)編譯其實(shí)就是使用了xcodebuild 的相關(guān)命令來實(shí)現(xiàn)編譯功能,,再使用xcrun 來將app打包成ipa,。
xcodebuild 的官方文檔見這里。
用到的關(guān)鍵命令
編譯workspace
xcodebuild -workspace workspacename -scheme schemename -configuration [-configuration configurationname] clean build SYMROOT=(SYMROOT)
編譯project
xcodebuild -target targetname -configuration [-configuration configurationname] clean build SYMROOT=(SYMROOT)
查看配置信息
xcodebuild -list
xcrun打包ipa
xcrun -sdk iphoneos PackageApplication -v projectName.app -o ipaName.ipa
完整的shell腳本稍長放在文章的最后,,該腳本改自BashShell,。
需要注意,腳本中的路徑最好使用絕對(duì)路徑,。
配置Apache和PHP
啟動(dòng)Apache
啟動(dòng):sudo apachectl start
停止:sudo apachectl stop
重啟:sudo apachectl restart
文件根目錄系統(tǒng)級(jí)的根目錄
http://localhosts/
對(duì)應(yīng)的文件目錄是:
/Library/WebServer/Documents/
系統(tǒng)級(jí)根目錄默認(rèn)沒有開啟目錄列表,,開啟方法:
編輯 /etc/apache2/httpd.conf 文件
搜索找到 <Directory "/Library/WebServer/Documents">
將 Options Multiviews 修改為 Options Indexes Multiviews
用戶級(jí)根目錄
另一個(gè) Web 根目錄默認(rèn)是 ~/Sites ,10.9 中你需要手動(dòng)創(chuàng)建這個(gè)Sites目錄,。
檢查這個(gè)目錄下是否有 username.conf 文件
/etc/apache2/users/
如果沒有,,則需要新建一個(gè),username 需要是你的賬戶名字,,建議使用終端創(chuàng)建這個(gè)文件:
cd /etc/apache2/users
sudo vi username.conf
貼入以下內(nèi)容,,注意修改 username 為你的賬戶名字
<Directory "/Users/username/Sites/">
Options Indexes MultiViews FollowSymLinksAllowOverride AllOrder allow,denyAllow from allRequire all granted
</Directory>
這個(gè)文件的權(quán)限應(yīng)該是:
-rw-r--r-- 1 root wheel 298 Jun 28 16:47 username.conf
如果不是,請(qǐng)修改
sudo chmod 644 username.conf
編輯 /etc/apache2/httpd.conf 文件,,刪除下列這些代碼前的注釋符號(hào): #
Include /private/etc/apache2/extra/httpd-userdir.conf
LoadModule authz_core_module libexec/apache2/mod_authz_core.soLoadModule authz_host_module libexec/apache2/mod_authz_host.soLoadModule userdir_module libexec/apache2/mod_userdir.so
編輯 /etc/apache2/extra/httpd-userdir.conf 文件,,刪除下列這些代碼前的注釋符號(hào): #
Include /private/etc/apache2/users/*.conf
重啟 Apache
sudo apachectl restart
這時(shí),這個(gè)網(wǎng)址應(yīng)該已經(jīng)可以用了:
http://localhost/~username/
PHP調(diào)用shell腳本
這里主要用到了PHP的system命令:system($cmd)
PHP調(diào)用shell的權(quán)限是比較低的,,我們的shell里會(huì)需要?jiǎng)?chuàng)建文件及文件夾的權(quán)限,,解決辦法是通過命令行將PHP文件所在目錄及目錄下的所有文件都提升權(quán)限,否則腳本會(huì)報(bào)權(quán)限錯(cuò)誤,。具體步驟如下:
- 打開目錄
/private/etc/apache2
打開文件 httpd.conf
找到
User _www
Group _www
修改_www為你的登錄用戶名
User <登錄用戶名>
從命令行重啟Apache
sudo apachectl restart
提升網(wǎng)站目錄權(quán)限,。因?yàn)槲业木W(wǎng)站根目錄就是上文提到的用戶的Sites文件,因此執(zhí)行以下命令
sudo chmod 775 ~/Sites
sudo chmod 775 ~/Sites/*
好了,,權(quán)限問題解決了,。當(dāng)滿懷信心看到從網(wǎng)頁調(diào)用腳本輸出信息的時(shí)候,結(jié)果又報(bào)了無法找到證書的錯(cuò)誤,,OMG,,但從終端調(diào)用腳本就可以成功,起初以為權(quán)限不夠?qū)е聼o法調(diào)用證書,,繞了一大圈后發(fā)現(xiàn)這個(gè)問題只是因?yàn)?code>鑰匙串中的證書一般安裝在登錄 下,,只需要移動(dòng)到系統(tǒng) 下就行了。
關(guān)于從SVN倉庫獲取源碼的部分就不寫了,,既然都可以調(diào)用腳本了,,這部分就也很簡單了。
這個(gè)過程還是比較折騰的,,希望這篇文章能夠save your time :)
最后奉上相關(guān)文件的源碼,。
Shell腳本文件(buildtool.sh)
#!/bin/sh
export LC_ALL=zh_CN.GB2312;export LANG=zh_CN.GB2312
username=用戶名
###############配置項(xiàng)目名稱和路徑等相關(guān)參數(shù)
projectName=$1 #項(xiàng)目所在目錄的名稱
isWorkSpace=$2 #判斷是用的workspace還是直接project,,workspace設(shè)置為true,否則設(shè)置為false
projectDir=/Users/${username}/workspace/projects/$3/ #項(xiàng)目所在目錄的絕對(duì)路徑
buildConfig=$4 #編譯的方式,默認(rèn)為Release,還有Debug等
###############配置下載的文件名稱和路徑等相關(guān)參數(shù)
wwwIPADir=/Users/${username}/Sites/$projectName #html,,ipa,,icon,plist最后所在的目錄絕對(duì)路徑
url="http://localhost/${projectName}" #下載路徑
##########################################################################################
##############################以下部分為自動(dòng)生產(chǎn)部分,,不需要手動(dòng)修改############################
##########################################################################################
####################### FUCTION START #######################
replaceString(){
local inputString=$1
result=${inputString//(/}
result=${result//)/}
echo $result
}
date_Y_M_D_W_T()
{
WEEKDAYS=(星期日 星期一 星期二 星期三 星期四 星期五 星期六)
WEEKDAY=$(date +%w)
DT="$(date +%Y年%m月%d日) ${WEEKDAYS[$WEEKDAY]} $(date "+%H:%M:%S")"
echo "$DT"
}
####################### FUCTION END #######################
###Log的路徑,如果發(fā)現(xiàn)log里又亂碼請(qǐng)?jiān)诮K端執(zhí)行:export LC_ALL=zh_CN.GB2312;export LANG=zh_CN.GB2312
logDir=/Users/${username}/workspace/xcodebuild
mkdir -pv $logDir
logPath=$logDir/$projectName-$buildConfig.log
echo "~~~~~~~~~~~~~~~~~~~開始編譯~~~~~~~~~~~~~~~~~~~" >>$logPath
loginInfo=`who am i`
loginUser=`echo $loginInfo |awk '{print $1}'`
echo "登陸用戶:$loginUser" >>$logPath
loginDate=`echo $loginInfo |awk '{print $3,$4,$5}'`
echo "登陸時(shí)間:$loginDate" >>$logPath
loginServer=`echo $loginInfo |awk '{print $6}'`
if [ -n "$loginServer" ]; then
echo "登陸用戶IP:$(replaceString $loginServer)" >>$logPath
else
echo "登陸用戶IP:localhost(127.0.0.1)" >>$logPath
fi
if [ -d "$logDir" ]; then
echo "${logDir}文件目錄存在"
else
echo "${logDir}文件目錄不存在,創(chuàng)建${logDir}目錄成功"
echo "創(chuàng)建${logDir}目錄成功" >>$logPath
fi
echo "<br />"
###############檢查html等文件放置目錄是否存在,,不存在就創(chuàng)建
echo "開始時(shí)間:$(date_Y_M_D_W_T)" >>$logPath
echo "項(xiàng)目名稱:$projectName" >>$logPath
echo "編譯模式:$buildConfig" >>$logPath
echo "開始目錄檢查........" >>$logPath
if [ -d "$wwwIPADir" ]; then
echo "文件目錄存在" >>$logPath
else
echo "文件目錄不存在" >>$logPath
mkdir -pv $wwwIPADir
echo "創(chuàng)建${wwwIPADir}目錄成功" >>$logPath
fi
###############進(jìn)入項(xiàng)目目錄
rm -rf ./build
buildAppToDir=/Users/${username}/workspace/build/$projectName #編譯打包完成后.archive .ipa文件存放的目錄
###############獲取版本號(hào),bundleID
infoPlist="${projectDir}${projectName}/$projectName-Info.plist"
bundleDisplayName=`/usr/libexec/PlistBuddy -c "Print CFBundleDisplayName" $infoPlist`
bundleVersion=`/usr/libexec/PlistBuddy -c "Print CFBundleShortVersionString" $infoPlist`
bundleIdentifier=`/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" $infoPlist`
bundleBuildVersion=`/usr/libexec/PlistBuddy -c "Print CFBundleVersion" $infoPlist`
###############在網(wǎng)頁上顯示的名字和bundleDisplayName一致
appName=$bundleDisplayName
echo "$bundleDisplayName"
###############開始編譯app
if $isWorkSpace ; then #判斷編譯方式
echo "開始編譯workspace...." >>$logPath
echo "$projectDir$projectName.xcworkspace"
xcodebuild -workspace ${projectDir}$projectName.xcworkspace -scheme $projectName -configuration $buildConfig clean build SYMROOT=$buildAppToDir
else
echo "開始編譯target...." >>$logPath
cd ${projectDir}
xcodebuild -target $projectName -configuration $buildConfig clean build SYMROOT=$buildAppToDir
fi
#判斷編譯結(jié)果
if test $? -eq 0
then
echo "~~~~~~~~~~~~~~~~~~~編譯成功~~~~~~~~~~~~~~~~~~~"
else
echo "~~~~~~~~~~~~~~~~~~~編譯失敗~~~~~~~~~~~~~~~~~~~" >>$logPath
echo "\n" >>$logPath
exit 1
fi
###############開始打包成.ipa
ipaName=`echo $projectName | tr "[:upper:]" "[:lower:]"` #將項(xiàng)目名轉(zhuǎn)小寫
appDir=$buildAppToDir/$buildConfig-iphoneos #app所在路徑
echo "開始打包$projectName.xcarchive成$projectName.ipa....." >>$logPath
xcrun -sdk iphoneos PackageApplication -v $appDir/$projectName.app -o $appDir/$ipaName.ipa #將app打包成ipa
###############開始拷貝到目標(biāo)下載目錄
iconName="icon.png" #icon名稱
iconSize=100 #icon大小
#unzipAppDir=$appDir/$projectName.app
unzipAppDir=$projectDir
iconImages=($(find $unzipAppDir -path "$buildAppToDir" -prune -o -type f -size +1k -name "*[iI]con*.png" |xargs ls -lSar| grep ^-)) #查找?guī)con或icon的圖標(biāo),取最大的圖片,忽略build目錄,,按大小排序輸出
#iconImages=($(find $unzipAppDir -size +1k -name "*[iI]con*.png")) #查找?guī)con或icon的圖標(biāo),,取最大的圖片
iconImagesLength=${#iconImages[@]} #獲取數(shù)組的count
cp -f -p ${iconImages[iconImagesLength-1]} $wwwIPADir/$iconName #拷貝icon.png文件
#檢查文件是否存在
if [ -f "$appDir/$ipaName.ipa" ]
then
echo "打包$ipaName.ipa成功." >>$logPath
else
echo "打包$ipaName.ipa失敗." >>$logPath
exit 1
fi
cp -f -p $appDir/$ipaName.ipa $wwwIPADir/$ipaName.ipa #拷貝ipa文件
echo "復(fù)制$ipaName.ipa到${wwwIPADir}成功" >>$logPath
###############計(jì)算文件大小和最后更新時(shí)間
fileSize=`stat $appDir/$ipaName.ipa |awk '{if($8!=4096){size=size+$8;}} END{print "文件大小:", size/1024/1024,"M"}'`
lastUpdateDate=`stat $appDir/$ipaName.ipa | awk '{print "最后更新時(shí)間:",$13,$14,$15,$16}'`
echo "$fileSize" >>$logPath
echo "$lastUpdateDate" >>$logPath
plistDir=${wwwIPADir}/$ipaName.plist #plist文件的路徑
htmlDir=${wwwIPADir}/index.html #html文件的路徑
###############生成PLIST文件
cat << EOF > $plistDir
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>items</key>
<array>
<dict>
<key>assets</key>
<array>
<dict>
<key>kind</key>
<string>software-package</string>
<key>url</key>
<string>$url/$ipaName.ipa</string>
</dict>
</array>
<key>metadata</key>
<dict>
<key>bundle-identifier</key>
<string>$bundleIdentifier</string>
<key>bundle-version</key>
<string>$bundleVersion</string>
<key>kind</key>
<string>software</string>
<key>title</key>
<string>$appName</string>
</dict>
</dict>
</array>
</dict>
</plist>
EOF
echo "生成plist文件到$plistDir成功" >>$logPath
###############生成html下載頁面
cat << EOF > $htmlDir
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www./1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta id="viewport" name="viewport" content="width=device-width; height=device-height; initial-scale=1.0; "/>
<title>安裝$appName</title>
<style type="text/css">
</style>
</head>
<body>
<h2>$appName</h2>
<img src="./$iconName" width=$iconSize height = $iconSize>
<ul>
<li><h2><a href="itms-services://?action=download-manifest&url=$url/$ipaName.plist">手機(jī)安裝$appName(V$bundleVersion.$bundleBuildVersion)</a></h2></li>
<li><h2><a href="$url/$ipaName.ipa">電腦下載IPA包</a></h2></li>
</ul>
<p>
$fileSize
<p>
$lastUpdateDate
</body>
</html>
EOF
echo "生成html文件到$htmlDir成功" >>$logPath
echo "結(jié)束時(shí)間:$(date_Y_M_D_W_T)" >>$logPath
echo "~~~~~~~~~~~~~~~~~~~結(jié)束編譯~~~~~~~~~~~~~~~~~~~" >>$logPath
echo "~~~~~~~~~~~~~~~~~~~結(jié)束編譯,處理成功~~~~~~~~~~~~~~~~~~~"
echo "\n" >>$logPath
echo "$url"
配置文件格式(data.json)
[
{
"name":"項(xiàng)目展示的名稱1",
"projectname":"項(xiàng)目名稱1",
"isworkspace":"是否是workspace",
"foldername":"項(xiàng)目文件夾名1",
"buildconfig":"Release/Debug/其他自定義編譯名稱"
},
{
"name":"項(xiàng)目展示的名稱2",
"projectname":"項(xiàng)目名稱2",
"isworkspace":"是否是workspace",
"foldername":"項(xiàng)目文件夾名2",
"buildconfig":"Release/Debug/其他自定義編譯名稱"
}
]
PHP文件(index.php)
<html>
<head>
<title>iOS應(yīng)用打包</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body style="text-align:center">
<h1>請(qǐng)選擇編譯項(xiàng)目</h1>
<form name="form1" method="post" action="">
<?php
$filename ='data.json';
$jsonstring = file_get_contents($filename);
$jsondecode = json_decode($jsonstring, true);
for ($i= 0;$i< count($jsondecode); $i++) {
$name = $jsondecode[$i]["name"];
echo "<label> <input type='radio' name='radio' value='$i'> ${name} </label> <br />";
}
if($_POST) {
$value = $_POST['radio'];
$name = $jsondecode[$value]["name"];
$projectname = $jsondecode[$value]["projectname"];
$isworkspace = $jsondecode[$value]["isworkspace"];
$foldername = $jsondecode[$value]["foldername"];
$buildconfig = $jsondecode[$value]["buildconfig"];
echo '<br />即將編譯:',$name;
echo '<br />編譯完成自動(dòng)跳轉(zhuǎn)至下載頁面<br /><br /><br />';
$cmd = "./buildtool.sh $projectname $isworkspace $foldername $buildconfig";
$url = system($cmd);
echo "<script language=\"javascript\">";
echo "location.href=\"$url\"";
echo "</script>";
}
?>
<br />
<input type="submit" name="Submit" value="提交" />
</form>
</body>
</html>
|