Jenkins中的iOS打包任务设计

Jenkins持续集成Unity游戏项目

Jenkins的安装部署和配置

Jenkins中的Android打包任务设计

Jenkins中的iOS打包任务设计

Jenkins中的测试任务设计

iOS打包需要的流程

打包结构
打包结构

整个打包流程与Android打包是类似的。

难点主要集中在插入必要的FrameWork,修改Plist

在Unity5.x上Unity官方已经帮我们提供了一套API用来对Xcode工程的处理。并且提供了[PostProcessBuild]这个标签来完成插入工作。

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

Unity导出前的准备

手上的项目分多个地区,所以在导出前会有不同的资源切换和资源删除

代码如下:

static void ExportiOSHFProject_Online() {
        getCMDArgs();
        PlayerSettings.defaultInterfaceOrientation = UIOrientation.AutoRotation;
        PlayerSettings.productName = "xxxx"; //设置韩服的Name
        //设置Deployment Target
        PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, "TWSDK;EXCLUDE_SHARESDK;KOREAN");
        if (OnlySetScriptingDefineSymbols)
        {
            Debug.Log("本次打开Unity只是设置全局宏,打包请再次调用");
            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
    这个是在命令行打包时候外部传过来的值,为了判断是来导出工程还是设置宏。
    本处这样设置是因为,在一开始我的打包脚本也是用宏来区分执行切换哪部分资源的,
    但是后来出现了一个奇怪的问题,如果我刚刚Checkout出来的Unity默认宏是A的话, 但是我想打包的是B。
    我直接设置PlayerSettings.SetScriptingDefineSymbolsForGroup为B,
    确实非Editor和非[PostProcessBuild]标签的脚本都被编译了为B。
    但是在我Unity内部打包脚本的宏没有切换,也就导致了我Unity脚本都切换了为B,但是我资源等切换都还是停留在A上,
    所以这里设置为第一次打开Unity设置PlayerSettings.SetScriptingDefineSymbolsForGroup为B,然后关闭再打开Unity这样所有的脚本都被切换到B了。
    这个问题我自己被坑了挺久,当然也有可能是自己的设计有问题,希望大家有更加好的解决办法,或则是我的错误,希望大家能分享一下。

  • 2.BuildPipeline.BuildPlayer()
    options ios需要切换到BuildOptions.None,这个参数我也并没有特别的明白,但是如果选择其他或则与Android的类似,会出现些问题。

插入FrameWork和Plis

接下来肯定是大家最关注的点了,我把代码直接贴出来吧,隐去了设置的的参数,宏因为是公司项目有些修改,可以根据自己的来修改。

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", "直播相关");
            plist.root.SetString("NSAppleMusicUsageDescription", "直播相关");

#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", "允許授權后遊戲體驗會更豐富");

#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", "允許授權后遊戲體驗會更豐富");
#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");//关闭自动证书管理
#elif TWSDK && KOREAN
            proj.SetTargetAttributes("ProvisioningStyle", "Manual");//关闭自动证书管理
#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地方没有什么的,都非常的简单就直接根据需求进行设置就可以。

插入Framework就是全是坑了

  • 1.插入Framework
    如果你是直接在Unity5.x版本直接引用内部带有的Api来插入的时候,如果版本比较低,最新的.tdb动态库是插入不会成功的.
    即使在Xcode中会显示,但是在编译时候还是会通不过的。
    本人的Unity版本是5.2.4是必然不行的,所以可以使用Unity官方托管在Bitbucket的最新版本代码来使用,下载它的代码,然后修改命名空间直接使用就可以。
    最新的已经支持了.tdb的动态库添加,在添加动态库时候请选择PBXSourceTree.Sdk。
    动态库引用时候需要填写路径:"/usr/bin/“+“libicucore.tbd”

  • 2.自动证书管理
    这个我们的项目比较特殊,在国服的iOS我们有AppleID,有账号密码,所以在XcodeBuild时候可以直接进行签名等。
    但是台服韩服等是有发行商给开发证书等,所以如果直接设置为自动管理证书无法签名会报错。
    因为这个原因我也自己研究了一下.xcproject,其实这个是个文件夹,里面有配置文件可以直接修改。
    最早的时候,我是直接写了一个Py脚本来修改这个,后来发现最新的API上有支持这个。
    >proj.SetTargetAttributes("ProvisioningStyle", "Manual");
    这样既可修改为关闭自动证书管理,如果不修改这个ProvisioningStyle,Xcode会直接默认你是自动管理证书的。

  • 3.Iphone图标管理
    Unity直接导出Xcode之后,在ICON界面,你会发现,自己的图标并不齐全,所以直接提交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,所以他们会有自己的静态库等,需要我们引用。
    所以这个也可以在我们的Unity导出脚本中使用。
    自己如果直接通过AddFileToBuild去添加一个文件夹,你会发现这个文件夹永远是蓝色的而不是以一个Group的形式,被引入到Xcode工程中。
    但是如果说你先引入文件下的framework的话,你会发现文件夹已经直接被添加为Group了。
    所以直接封装了XcodeDirectoryProcessor这个对文件夹的引用。
    添加代码下载在这里

  • 5.OC代码修改
    我们的项目直接被解耦的比较好,所以不需要直接修改Xcode导出的代码,直接添加中间bridge的代码就好。
    事实上如果有这个需求,可以直接写脚本对导出的OC代码进行替换。
    理论上每次导出的OC代码是一样的,如果不同版本的就不保证一定相等。

签名

我盟直接使用Jenkins作为框架,直接安准XcodeBuild的插件来完成签名等工作,由于Jenkins直接都是配置一下就好了,基本没有坑,我也是直接一次调整通过,所以就不再重复。

提交IPA

我们是导出Ipa提交到发行商的网上,所以直接写脚本提交了,没有什么可以参考性,但是如果想直接提交到Appstore可以使用XcodeBuild但是我没有经验,应该也是比较简单的配置。

总结

至此基于Jenkins的Unity自动化构建Android和iOS文章都已近写完了。
从进公司到现在的一个半月多时间,全公司基本没有人对此方面有经验的情况下,通过自己一个人踩坑,一步步完成了整个流程。
对于我来说也是学到了许多,希望大家看完我的文章也能学到。
毕竟自己才刚刚毕业才疏学浅,文中可能出现比较多的错误,代码比较简陋,希望大家不要嫌弃。

最后做个小广告~
喜欢的可以转载下我的小文章
Blog:http://www.hailantown.com

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 197,597评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,053评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 144,583评论 0 326
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,888评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,772评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,536评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,922评论 3 388
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,554评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,838评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,861评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,677评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,483评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,928评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,104评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,403评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,945评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,156评论 2 339

推荐阅读更多精彩内容