iOS自動化編譯

来源:http://www.cnblogs.com/shouce/archive/2016/04/05/5353712.html
-Advertisement-
Play Games

最近研究了一下iOS的自動化編譯,目的是為了簡化測試和開發的同學溝通協調的次數,實現測試同學可以隨時從網頁操作編譯SVN最新源碼並打包ipa進行測試。 具體思路是通過從配置文件讀取需要編譯的項目配置列表展示在網頁上,測試同學選擇需要編譯的項目,確定後將選擇項目的相關參數傳入shell腳本運行,編譯完 ...


最近研究了一下iOS的自動化編譯,目的是為了簡化測試和開發的同學溝通協調的次數,實現測試同學可以隨時從網頁操作編譯SVN最新源碼並打包ipa進行測試。
具體思路是通過從配置文件讀取需要編譯的項目配置列表展示在網頁上,測試同學選擇需要編譯的項目,確定後將選擇項目的相關參數傳入shell腳本運行,編譯完成自動跳轉至下載頁面。
主要流程包括:

  1. Shell腳本的編寫。通過xcodebuild和xcrun實現自動編譯並打包。
  2. PHP調用腳本。開啟OS X自帶的Apache伺服器,編寫PHP來調用shell。

編寫shell腳本

自動編譯其實就是使用了xcodebuild的相關命令來實現編譯功能,再使用xcrun來將app打包成ipa。
xcodebuild的官方文檔見這裡

用到的關鍵命令

  • 編譯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
需要註意,腳本中的路徑最好使用絕對路徑。

配置Apache和PHP

啟動Apache

啟動:sudo apachectl start
停止:sudo apachectl stop
重啟:sudo apachectl restart

文件根目錄系統級的根目錄

http://localhosts/

對應的文件目錄是:
/Library/WebServer/Documents/

系統級根目錄預設沒有開啟目錄列表,開啟方法:
編輯 /etc/apache2/httpd.conf 文件
搜索找到 <Directory "/Library/WebServer/Documents">
Options Multiviews 修改為 Options Indexes Multiviews

用戶級根目錄

另一個 Web 根目錄預設是 ~/Sites ,10.9 中你需要手動創建這個Sites目錄。

檢查這個目錄下是否有 username.conf 文件
/etc/apache2/users/
如果沒有,則需要新建一個,username 需要是你的賬戶名字,建議使用終端創建這個文件:

cd /etc/apache2/users
sudo vi username.conf

貼入以下內容,註意修改 username 為你的賬戶名字

<Directory "/Users/username/Sites/">
Options Indexes MultiViews FollowSymLinksAllowOverride AllOrder allow,denyAllow from allRequire all granted
</Directory>

這個文件的許可權應該是:
-rw-r--r-- 1 root wheel 298 Jun 28 16:47 username.conf
如果不是,請修改
sudo chmod 644 username.conf
編輯 /etc/apache2/httpd.conf 文件,刪除下列這些代碼前的註釋符號: #

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 文件,刪除下列這些代碼前的註釋符號: #

Include /private/etc/apache2/users/*.conf
重啟 Apache
sudo apachectl restart
這時,這個網址應該已經可以用了:
http://localhost/~username/

PHP調用shell腳本

這裡主要用到了PHP的system命令:system($cmd)
PHP調用shell的許可權是比較低的,我們的shell里會需要創建文件及文件夾的許可權,解決辦法是通過命令行將PHP文件所在目錄及目錄下的所有文件都提升許可權,否則腳本會報許可權錯誤。具體步驟如下:

  1. 打開目錄 /private/etc/apache2
  2. 打開文件 httpd.conf
    找到

    User _www 
    Group _www

    修改_www為你的登錄用戶名

    User <登錄用戶名>
  3. 從命令行重啟Apache

    sudo apachectl restart
  4. 提升網站目錄許可權。因為我的網站根目錄就是上文提到的用戶的Sites文件,因此執行以下命令

    sudo chmod 775 ~/Sites
    sudo chmod 775 ~/Sites/*

好了,許可權問題解決了。當滿懷信心看到從網頁調用腳本輸出信息的時候,結果又報了無法找到證書的錯誤,OMG,但從終端調用腳本就可以成功,起初以為許可權不夠導致無法調用證書,繞了一大圈後發現這個問題只是因為鑰匙串中的證書一般安裝在登錄下,只需要移動到系統下就行了。

移動證書

關於從SVN倉庫獲取源碼的部分就不寫了,既然都可以調用腳本了,這部分就也很簡單了。
這個過程還是比較折騰的,希望這篇文章能夠save your time :)

最後奉上相關文件的源碼。


Shell腳本文件(buildtool.sh)

#!/bin/sh

export LC_ALL=zh_CN.GB2312;export LANG=zh_CN.GB2312

username=用戶名
###############配置項目名稱和路徑等相關參數
projectName=$1 #項目所在目錄的名稱
isWorkSpace=$2  #判斷是用的workspace還是直接project,workspace設置為true,否則設置為false
projectDir=/Users/${username}/workspace/projects/$3/ #項目所在目錄的絕對路徑
buildConfig=$4 #編譯的方式,預設為Release,還有Debug等

###############配置下載的文件名稱和路徑等相關參數
wwwIPADir=/Users/${username}/Sites/$projectName #html,ipa,icon,plist最後所在的目錄絕對路徑
url="http://localhost/${projectName}" #下載路徑

##########################################################################################
##############################以下部分為自動生產部分,不需要手動修改############################
##########################################################################################

####################### 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的路徑,如果發現log里又亂碼請在終端執行: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 "登陸時間:$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}文件目錄不存在,創建${logDir}目錄成功"
    echo "創建${logDir}目錄成功" >>$logPath
fi

echo "<br />"
###############檢查html等文件放置目錄是否存在,不存在就創建
echo "開始時間:$(date_Y_M_D_W_T)" >>$logPath
echo "項目名稱:$projectName" >>$logPath
echo "編譯模式:$buildConfig" >>$logPath
echo "開始目錄檢查........" >>$logPath

if [ -d "$wwwIPADir" ]; then
    echo "文件目錄存在" >>$logPath
else 
    echo "文件目錄不存在" >>$logPath
    mkdir -pv $wwwIPADir
    echo "創建${wwwIPADir}目錄成功" >>$logPath
fi

###############進入項目目錄

rm -rf ./build
buildAppToDir=/Users/${username}/workspace/build/$projectName #編譯打包完成後.archive .ipa文件存放的目錄

###############獲取版本號,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`
###############在網頁上顯示的名字和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
#判斷編譯結果
if test $? -eq 0
then
echo "~~~~~~~~~~~~~~~~~~~編譯成功~~~~~~~~~~~~~~~~~~~"
else
echo "~~~~~~~~~~~~~~~~~~~編譯失敗~~~~~~~~~~~~~~~~~~~" >>$logPath
echo "\n" >>$logPath
exit 1
fi

###############開始打包成.ipa
ipaName=`echo $projectName | tr "[:upper:]" "[:lower:]"` #將項目名轉小寫
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

###############開始拷貝到目標下載目錄
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 ^-)) #查找帶Icon或icon的圖標,取最大的圖片,忽略build目錄,按大小排序輸出
#iconImages=($(find $unzipAppDir -size +1k -name "*[iI]con*.png")) #查找帶Icon或icon的圖標,取最大的圖片
iconImagesLength=${#iconImages[@]} #獲取數組的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 "複製$ipaName.ipa到${wwwIPADir}成功" >>$logPath

###############計算文件大小和最後更新時間
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 "最後更新時間:",$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.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      <html xmlns="http://www.w3.org/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&amp;url=$url/$ipaName.plist">手機安裝$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 "結束時間:$(date_Y_M_D_W_T)" >>$logPath
echo "~~~~~~~~~~~~~~~~~~~結束編譯~~~~~~~~~~~~~~~~~~~" >>$logPath
echo "~~~~~~~~~~~~~~~~~~~結束編譯,處理成功~~~~~~~~~~~~~~~~~~~"
echo "\n" >>$logPath

echo "$url"

配置文件格式(data.json)

[
    {
        "name":"項目展示的名稱1",
        "projectname":"項目名稱1",
        "isworkspace":"是否是workspace",
        "foldername":"項目文件夾名1",
        "buildconfig":"Release/Debug/其他自定義編譯名稱"
    },
    {
        "name":"項目展示的名稱2",
        "projectname":"項目名稱2",
        "isworkspace":"是否是workspace",
        "foldername":"項目文件夾名2",
        "buildconfig":"Release/Debug/其他自定義編譯名稱"
    }
]

PHP文件(index.php)

<html>
    <head>
        <title>iOS應用打包</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    </head>
    <body style="text-align:center">
        <h1>請選擇編譯項目</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 />編譯完成自動跳轉至下載頁面<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>

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 通常對於無刷新提交表單,我們都是運用ajax實現的。前段時間跟著老大瞭解到另一種無刷新提交表單的方法,是利用iframe框架實現的。現在整理出來分享給大家。 第一種: (html頁面) <!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset= ...
  • 本節教程將介紹如何用DeviceOne簡單而高效的完成一個新聞頁面。 導入項目 數據模板分離MVVM模型 自定義事件 展示新聞 九宮格展示 將要學習的demo效果圖如下所示 1. 導入完整項目 本節示例demo請參考下載地址,可以導入到設計器中學習。 為了方便大家理解頁面結構,請參考下圖圖中紅框所示 ...
  • 流程式控制制 順序、分支、迴圈 順序結構 代碼一行一行從上往下執行並解析 分支結構 if語句 switch語句 if語句 單分支 if(條件表達式){ //語句塊 } 含義:當條件表達式為真的時候就執行裡面的語句塊 示例: 雙分支: if(條件表達式){ //語句塊1 }else{ //語句塊2 } 含... ...
  • 問題引發:最近在整理DOM系列的一些知識點,發現在DOM的某些介面API中,存在一些我想不通的現象。就隨便舉個例子吧:DOM文檔模型中的文本節點,可以通過nodeValue或data屬性訪問文本節點的文本內容,而且在更新data的時候nodeValue也即時更新,反之亦然。不光是data或nodeV ...
  • 前些日子用到了eval()處理json數據,習慣於每次添加'('+json+')'處理數據,也沒去深究為什麼這麼做,剛好同事問我這個問題,瞬間啞口無言,只會如何操作,卻講不出原因,這不符合咱程式員嚴謹的工作態度,仔細思考了一會,簡略的談談吧。 可能會隨時腦洞,望各位見諒。 1.什麼是json? JS ...
  • document.write()和window.alert() 1、window.document.write(字元串或者是變數名) 作用:它會在body標簽內輸出內容 說明: window代表當前瀏覽器視窗 它是頂級對象 因為它是頂級對象可以省略不寫 document 代表當前文檔 它既是wind... ...
  • 作者:禪樓望月( http://www.cnblogs.com/yaoyinglong ) JavaScript里也可以像Java等面向對象的語言世界里創建自定義的類型,但是由於JavaScript中不支持使用class關鍵字來創建自定義的類型,因此我們只能另闢蹊徑……下麵我們一起來看在JavaSc ...
  • 一,代碼。 二,輸出。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...