Jenkins中的iOS打包任務(wù)設(shè)計

Jenkins持續(xù)集成Unity游戲項目

Jenkins的安裝部署和配置

Jenkins中的Android打包任務(wù)設(shè)計

Jenkins中的iOS打包任務(wù)設(shè)計

Jenkins中的測試任務(wù)設(shè)計

iOS打包需要的流程

打包結(jié)構(gòu)
打包結(jié)構(gòu)

整個打包流程與Android打包是類似的。

難點主要集中在插入必要的FrameWork谷异,修改Plist

在Unity5.x上Unity官方已經(jīng)幫我們提供了一套API用來對Xcode工程的處理傀履。并且提供了[PostProcessBuild]這個標(biāo)簽來完成插入工作。

在Unity4.x上使用這套API可以去下載Unity官方提供的代碼 https://bitbucket.org/Unity-Technologies/xcodeapi

Unity導(dǎo)出前的準(zhǔn)備

手上的項目分多個地區(qū),所以在導(dǎo)出前會有不同的資源切換和資源刪除

代碼如下:

static void ExportiOSHFProject_Online() {
        getCMDArgs();
        PlayerSettings.defaultInterfaceOrientation = UIOrientation.AutoRotation;
        PlayerSettings.productName = "xxxx"; //設(shè)置韓服的Name
        //設(shè)置Deployment Target
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, "TWSDK;EXCLUDE_SHARESDK;KOREAN");
        if (OnlySetScriptingDefineSymbols)
        {
            Debug.Log("本次打開Unity只是設(shè)置全局宏,打包請再次調(diào)用");
            return;
        }
        string[] NeedToDel = {""};//指定要刪除的目錄
        doDelWithRelativePath(NeedToDel);
        _LanguageBar.ToTraditionForAntBuild(_LanguageBar.UNIRES_KOREA); //切換語言版本
        ExportProject(ExportPath, BuildTarget.iOS, BuildOptions.None);
    }
    static void ExportProject(string path,BuildTarget target,BuildOptions options)
    {

        if (path.Length != 0)
        {
            foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
            {
                if (!scene.enabled) continue;
                levels.Add(scene.path);
            }

            float time = Time.realtimeSinceStartup;
            AssetDatabase.Refresh();
            try
            {
                BuildPipeline.BuildPlayer(levels.ToArray(), path, target, options);
            }
            catch (System.Exception m)
            {
                Debug.LogError(m.Message);
            }
            time = Time.realtimeSinceStartup - time;
            Debug.Log("打包完成,共計耗時" + time);
        }

    }

踩坑注意點

  • 1.OnlySetScriptingDefineSymbols
    這個是在命令行打包時候外部傳過來的值厘托,為了判斷是來導(dǎo)出工程還是設(shè)置宏。
    本處這樣設(shè)置是因為贩虾,在一開始我的打包腳本也是用宏來區(qū)分執(zhí)行切換哪部分資源的,
    但是后來出現(xiàn)了一個奇怪的問題沥阱,如果我剛剛Checkout出來的Unity默認(rèn)宏是A的話缎罢, 但是我想打包的是B。
    我直接設(shè)置PlayerSettings.SetScriptingDefineSymbolsForGroup為B考杉,
    確實非Editor和非[PostProcessBuild]標(biāo)簽的腳本都被編譯了為B策精。
    但是在我Unity內(nèi)部打包腳本的宏沒有切換,也就導(dǎo)致了我Unity腳本都切換了為B崇棠,但是我資源等切換都還是停留在A上咽袜,
    所以這里設(shè)置為第一次打開Unity設(shè)置PlayerSettings.SetScriptingDefineSymbolsForGroup為B,然后關(guān)閉再打開Unity這樣所有的腳本都被切換到B了枕稀。
    這個問題我自己被坑了挺久询刹,當(dāng)然也有可能是自己的設(shè)計有問題谜嫉,希望大家有更加好的解決辦法,或則是我的錯誤凹联,希望大家能分享一下沐兰。

  • 2.BuildPipeline.BuildPlayer()
    options ios需要切換到BuildOptions.None,這個參數(shù)我也并沒有特別的明白蔽挠,但是如果選擇其他或則與Android的類似住闯,會出現(xiàn)些問題。

插入FrameWork和Plis

接下來肯定是大家最關(guān)注的點了澳淑,我把代碼直接貼出來吧比原,隱去了設(shè)置的的參數(shù),宏因為是公司項目有些修改杠巡,可以根據(jù)自己的來修改量窘。

public class AntXcodeProjectProcess : MonoBehaviour {

    [PostProcessScene]
    public static void OnPostprocessScene()
    {
        
#if UNITY_ADS
        AdvertisementSettings.enabled = true;
        AdvertisementSettings.initializeOnStartup = false;
#endif
    }
    [PostProcessBuild(100)]
    public static void OnPostprocessBuild(BuildTarget buildTarget, string path)
    {
        if (buildTarget == BuildTarget.iOS)
        {
            //plist
            string plistPath = Path.Combine(path, "info.plist");
            PlistDocument plist = new PlistDocument ();
            plist.ReadFromFile (plistPath);
            plist.root.SetString ("CFBundleDevelopmentRegion", "zh_CN");
            plist.root.SetString("NSCameraUsageDescription", "直播相關(guān)");
            plist.root.SetString("NSAppleMusicUsageDescription", "直播相關(guān)");

#if TWSDK && !KOREAN
            plist.root.SetString("FacebookAppID", "");
            plist.root.SetString("FacebookDisplayName","");
            plist.root.CreateArray("CFBundleURLTypes").AddDict().CreateArray("CFBundleURLSchemes").AddString("");
            string[] LSApplicationQueriesSchemesValueList = { "starcoin", "fbapi", "", "", "", "", "", "" ,"","","","","" ,""};
            PlistElementArray LSApplicationQueriesSchemesArray = plist.root.CreateArray("LSApplicationQueriesSchemes");
            for (int i = 0; i < LSApplicationQueriesSchemesValueList.Length; i++) {
                LSApplicationQueriesSchemesArray.AddString(LSApplicationQueriesSchemesValueList[i]);
            }
            plist.root.SetString("NSPhotoLibraryUsageDescription", "允許授權(quán)后遊戲體驗會更豐富");

#elif TWSDK && KOREAN
            plist.root.SetString("FacebookAppID", "");
            plist.root.SetString("FacebookDisplayName", "");
            plist.root.CreateArray("CFBundleURLTypes").AddDict().CreateArray("CFBundleURLSchemes").AddString("");
            string[] LSApplicationQueriesSchemesValueList = { "starcoin", "fbapi", "", "", "", "", "", "" ,"","","","","" ,""};
            PlistElementArray LSApplicationQueriesSchemesArray = plist.root.CreateArray("LSApplicationQueriesSchemes");
            for (int i = 0; i < LSApplicationQueriesSchemesValueList.Length; i++) {
                LSApplicationQueriesSchemesArray.AddString(LSApplicationQueriesSchemesValueList[i]);
            }
            plist.root.SetString("NSPhotoLibraryUsageDescription", "允許授權(quán)后遊戲體驗會更豐富");
#endif
            PlistElementDict dictTransportSecurity = plist.root ["NSAppTransportSecurity"].AsDict ();
            dictTransportSecurity.SetBoolean("NSAllowsArbitraryLoads",true);

#if !EXCLUDE_SHARESDK
            plist.root.CreateArray("LSApplicationQueriesSchemes").AddString("weixin"); // 微信分享
            plist.root.CreateArray("CFBundleURLTypes").AddDict().CreateArray("CFBundleURLSchemes").AddString("");
#endif
            plist.WriteToFile (plistPath);
            
            //project
            string projPath = PBXProject.GetPBXProjectPath(path);
            PBXProject proj = new PBXProject();
            proj.ReadFromString(File.ReadAllText(projPath));
            string target = proj.TargetGuidByName("Unity-iPhone");
#if TWSDK && !KOREAN
            proj.SetTargetAttributes("ProvisioningStyle", "Manual");//關(guān)閉自動證書管理
#elif TWSDK && KOREAN
            proj.SetTargetAttributes("ProvisioningStyle", "Manual");//關(guān)閉自動證書管理
#endif

            //add common framework start
            proj.AddFrameworkToProject(target, "ReplayKit.framework", true);
            proj.AddFrameworkToProject(target, "ImageIO.framework", true);
            proj.AddFrameworkToProject(target, "Storekit.framework", false);
            proj.AddFrameworkToProject(target, "JavaScriptCore.framework", true);
            if (proj.ContainsFramework(target, "Metal.framework")){
                proj.RemoveFrameworkFromProject(target, "Metal.framework");
                proj.AddFrameworkToProject(target, "Metal.framework", true);
            }
            //add common framework end
            proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(SRCROOT)");
            proj.AddBuildProperty(target, "FRAMEWORK_SEARCH_PATHS", "$(SRCROOT)/Libraries");

            string[] addBuildProperty = { "$(SRCROOT)/Libraries", "$(SRCROOT)" };
            string[] removeBuildProperty = { "\"$(SRCROOT)/Libraries\"", "\"$(SRCROOT)\"" };
            proj.UpdateBuildProperty(target, "LIBRARY_SEARCH_PATHS", addBuildProperty, removeBuildProperty);

            proj.AddBuildProperty(target, "OTHER_LDFLAGS", "-ObjC");

            //custom frameworl start



#if SDK
            proj.AddFileToBuild(target, proj.AddFile("/usr/bin/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk));
            proj.AddFileToBuild(target, proj.AddFile("/usr/bin/libicucore.tbd", "Frameworks/libicucore.tbd", PBXSourceTree.Sdk));
#endif
#if SDKSDK
            proj.AddFrameworkToProject(target, "Security.framework", false);
            proj.AddFrameworkToProject(target, "Storekit.framework", false);
            proj.AddFrameworkToProject(target, "SafariServices.framework", false);
            proj.AddFrameworkToProject(target, "CoreData.framework", true);
            proj.AddFrameworkToProject(target, "MobileCoreServices.framework", true);
            proj.AddFrameworkToProject(target, "EventKit.framework", true);
            proj.AddFrameworkToProject(target, "EventKitUI.framework", true);
            proj.AddFrameworkToProject(target, "Social.framework", true);
            proj.AddFrameworkToProject(target, "CoreTelephony.framework", true);
            proj.AddFrameworkToProject(target, "MessageUI.framework", true);
            proj.AddFileToBuild(target, proj.AddFile("/usr/bin/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk));
            proj.AddFileToBuild(target, proj.AddFile("/usr/bin/libstdc++.tbd", "Frameworks/libstdc++.tbd", PBXSourceTree.Sdk));
#endif
            //custom frameworl end

            //custom SDKFile start
#if SDK
            XcodeDirectoryProcessor xdp = new XcodeDirectoryProcessor();
            xdp.CopyAndAddBuildToXcode(proj,target,"XcodeFiles/SDK/",path,"SDKFiles");
            xdp.CopyAndReplace("XcodeFiles/SDK/Unity-iPhone/Images.xcassets/AppIcon.appiconset",Path.Combine(path, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"));

#endif
#if TXWYSDK && !KOREAN
            Debug.Log("拷貝TXWYSDK文件夾");
            XcodeDirectoryProcessor xdp = new XcodeDirectoryProcessor();
            xdp.CopyAndAddBuildToXcode(proj,target,"XcodeFiles/TXWYSDK/",path,"SDKFiles");
            xdp.CopyAndReplace("XcodeFiles/TWSDK/Unity-iPhone/Images.xcassets/AppIcon.appiconset",Path.Combine(path, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"));
#elif TXWYSDK && KOREAN
             XcodeDirectoryProcessor xdp = new XcodeDirectoryProcessor();
            xdp.CopyAndAddBuildToXcode(proj,target,"XcodeFiles/KOREASDK/",path,"SDKFiles");
            xdp.CopyAndReplace("XcodeFiles/KOREASDK/Unity-iPhone/Images.xcassets/AppIcon.appiconset",Path.Combine(path, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"));
#endif
            //custom SDKFile end
            File.WriteAllText(projPath, proj.WriteToString());

        }
    }
}

踩坑注意點

Plist地方?jīng)]有什么的,都非常的簡單就直接根據(jù)需求進(jìn)行設(shè)置就可以忽孽。

插入Framework就是全是坑了

  • 1.插入Framework
    如果你是直接在Unity5.x版本直接引用內(nèi)部帶有的Api來插入的時候绑改,如果版本比較低,最新的.tdb動態(tài)庫是插入不會成功的.
    即使在Xcode中會顯示兄一,但是在編譯時候還是會通不過的厘线。
    本人的Unity版本是5.2.4是必然不行的,所以可以使用Unity官方托管在Bitbucket的最新版本代碼來使用出革,下載它的代碼造壮,然后修改命名空間直接使用就可以。
    最新的已經(jīng)支持了.tdb的動態(tài)庫添加骂束,在添加動態(tài)庫時候請選擇PBXSourceTree.Sdk耳璧。
    動態(tài)庫引用時候需要填寫路徑:"/usr/bin/“+“l(fā)ibicucore.tbd”

  • 2.自動證書管理
    這個我們的項目比較特殊,在國服的iOS我們有AppleID展箱,有賬號密碼旨枯,所以在XcodeBuild時候可以直接進(jìn)行簽名等。
    但是臺服韓服等是有發(fā)行商給開發(fā)證書等混驰,所以如果直接設(shè)置為自動管理證書無法簽名會報錯攀隔。
    因為這個原因我也自己研究了一下.xcproject,其實這個是個文件夾,里面有配置文件可以直接修改栖榨。
    最早的時候昆汹,我是直接寫了一個Py腳本來修改這個,后來發(fā)現(xiàn)最新的API上有支持這個婴栽。
    >proj.SetTargetAttributes("ProvisioningStyle", "Manual");
    這樣既可修改為關(guān)閉自動證書管理满粗,如果不修改這個ProvisioningStyle,Xcode會直接默認(rèn)你是自動管理證書的愚争。

  • 3.Iphone圖標(biāo)管理
    Unity直接導(dǎo)出Xcode之后映皆,在ICON界面挤聘,你會發(fā)現(xiàn),自己的圖標(biāo)并不齊全劫扒,所以直接提交AppStore可能會遇到直接被拒絕檬洞。
    所以可以自己新建工程,把ICON補齊了沟饥,然后把Images.xcassets/AppIcon.appiconset文件夾備份一下添怔。
    每次打包時候直接
    >xdp.CopyAndReplace("XcodeFiles/KOREASDK/Unity-iPhone/Images.xcassets/AppIcon.appiconset",Path.Combine(path, "Unity-iPhone/Images.xcassets/AppIcon.appiconset"));

  • 4.項目外的Framework引用
    我們常常會遇到我們的項目需要接入外部的SDK,所以他們會有自己的靜態(tài)庫等贤旷,需要我們引用广料。
    所以這個也可以在我們的Unity導(dǎo)出腳本中使用。
    自己如果直接通過AddFileToBuild去添加一個文件夾幼驶,你會發(fā)現(xiàn)這個文件夾永遠(yuǎn)是藍(lán)色的而不是以一個Group的形式艾杏,被引入到Xcode工程中。
    但是如果說你先引入文件下的framework的話盅藻,你會發(fā)現(xiàn)文件夾已經(jīng)直接被添加為Group了购桑。
    所以直接封裝了XcodeDirectoryProcessor這個對文件夾的引用。
    添加代碼下載在這里

  • 5.OC代碼修改
    我們的項目直接被解耦的比較好氏淑,所以不需要直接修改Xcode導(dǎo)出的代碼勃蜘,直接添加中間bridge的代碼就好。
    事實上如果有這個需求假残,可以直接寫腳本對導(dǎo)出的OC代碼進(jìn)行替換缭贡。
    理論上每次導(dǎo)出的OC代碼是一樣的,如果不同版本的就不保證一定相等辉懒。

簽名

我盟直接使用Jenkins作為框架阳惹,直接安準(zhǔn)XcodeBuild的插件來完成簽名等工作,由于Jenkins直接都是配置一下就好了眶俩,基本沒有坑莹汤,我也是直接一次調(diào)整通過,所以就不再重復(fù)颠印。

提交IPA

我們是導(dǎo)出Ipa提交到發(fā)行商的網(wǎng)上纲岭,所以直接寫腳本提交了,沒有什么可以參考性嗽仪,但是如果想直接提交到Appstore可以使用XcodeBuild但是我沒有經(jīng)驗荒勇,應(yīng)該也是比較簡單的配置柒莉。

總結(jié)

至此基于Jenkins的Unity自動化構(gòu)建Android和iOS文章都已近寫完了闻坚。
從進(jìn)公司到現(xiàn)在的一個半月多時間,全公司基本沒有人對此方面有經(jīng)驗的情況下兢孝,通過自己一個人踩坑窿凤,一步步完成了整個流程仅偎。
對于我來說也是學(xué)到了許多,希望大家看完我的文章也能學(xué)到雳殊。
畢竟自己才剛剛畢業(yè)才疏學(xué)淺橘沥,文中可能出現(xiàn)比較多的錯誤,代碼比較簡陋夯秃,希望大家不要嫌棄座咆。

最后做個小廣告~
喜歡的可以轉(zhuǎn)載下我的小文章
Blog:http://www.hailantown.com

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市仓洼,隨后出現(xiàn)的幾起案子介陶,更是在濱河造成了極大的恐慌,老刑警劉巖色建,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件哺呜,死亡現(xiàn)場離奇詭異,居然都是意外死亡箕戳,警方通過查閱死者的電腦和手機某残,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來陵吸,“玉大人玻墅,你說我怎么就攤上這事∽咴剑” “怎么了椭豫?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長旨指。 經(jīng)常有香客問我赏酥,道長,這世上最難降的妖魔是什么谆构? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任裸扶,我火速辦了婚禮,結(jié)果婚禮上搬素,老公的妹妹穿的比我還像新娘呵晨。我一直安慰自己,他們只是感情好熬尺,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布摸屠。 她就那樣靜靜地躺著,像睡著了一般粱哼。 火紅的嫁衣襯著肌膚如雪季二。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機與錄音胯舷,去河邊找鬼刻蚯。 笑死,一個胖子當(dāng)著我的面吹牛桑嘶,可吹牛的內(nèi)容都是我干的炊汹。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼逃顶,長吁一口氣:“原來是場噩夢啊……” “哼讨便!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起以政,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤器钟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后妙蔗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體傲霸,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年眉反,在試婚紗的時候發(fā)現(xiàn)自己被綠了昙啄。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡寸五,死狀恐怖梳凛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情梳杏,我是刑警寧澤韧拒,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站十性,受9級特大地震影響叛溢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜劲适,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一楷掉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霞势,春花似錦烹植、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至固以,卻和暖如春墩虹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工败晴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人栽渴。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓尖坤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親闲擦。 傳聞我的和親對象是個殘疾皇子慢味,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容