这几天搞了个自动打包的工具,开始的时候也是胡搞乱搞一头雾水,一顿查资料啊、翻看别人写的博客啊,终于搞出来了。但是现在回头看,感觉这东西其实就一层窗户纸,捅破之后,也挺简单的~

公司的项目太大,打一次包真的是慢的一匹... 那么这个打包工具就应运而生了,每天定时执行:先自动更新svn,再打Bundle,再打包,最后邮件通知一下打包结果~

整体的思路就是通过命令行去执行 更新、打Bundle、打包,然后考虑到mac和windows的电脑使用的命令不一样,那么就用python来做个整合,让它在mac和windows上都可以运行~

ps : 因为苹果开发账号过期了... 这篇博客mac上打包就写到打出xcode工程为止 ....

准备工作:

1. python、Unity

2. litJson(打包配置用,这里图方便就用了json,也可以用xml 或者 自己的excel倒数据的工具)

3. 一个py脚本,存储工具运行需要的各种配置(Unity安装位置啊、项目路径啊、 blablabla 具体如下:)

config_autoPackage.py


# 本地Unity.exe位置
unity_exe = 'F:/unity/Editor/Unity.exe'
unity_app = '/Applications/Unity/Unity.app/Contents/MacOS/Unity'# unity工程根目录
project_path = 'E:/U_Project/MyProject'
project_path_mac = "/Volumes/'MacOS HD Data'/MyProject"# bundleLog位置
bundle_log = 'C:/Users/hp/Desktop/AutoPackageTest/unity_bundle_log.log'
bundle_log_mac = '/Users/xiaosu/Desktop/AutoPackageTest/unity_bundle_log.log'# packageLog位置
package_log = 'C:/Users/hp/Desktop/AutoPackageTest/unity_package_log.log'
package_log_mac = '/Users/xiaosu/Desktop/AutoPackageTest/unity_package_log.log'# 调用打bundle接口
bundle_fuc = 'AutoPackage.BuildBundle'
# 调用打包接口
package_fuc = 'AutoPackage.BuildApk'
# 需要revert的外链1
external_link1 = 'Assets\SharedAssets'
# 需要revert的外链2
external_link2 = 'Assets\VarietyStore'

一、自动SVN更新(git同理)

主要的命令就是 svn update,但是害怕出现各种意料之外的比如说冲突(命令行执行更新SVN的时候,一旦遇到冲突,就会给出选项让你手动键入解决方案,这时候进程就卡在 “选择题” 这儿了 ... 所以一定尽可能避免冲突),所以我选择在update之前先clearup 再 revert (我们公司有打包机专门打包用的,一般不会在打包机上对逻辑和资源进行修改,所以才这么操作),保证万无一失之后再update。

代码如下:

AutoUpdateSVN.py

import os
import config_autoPackage
import platform__cmdCleanup = ' && svn cleanup && svn revert --recursive .'
__cmdUpdate = ' && svn update'class P:if_mac = Falseget_dic_cmd = ''def start_update():m_platform = platform.system()print('Current platform : ' + m_platform)if m_platform == 'Darwin':P.if_mac = Trueelse:P.if_mac = False__prepare_cleanup_SVN()def __prepare_cleanup_SVN():print('开始cleanup')if P.if_mac:P.get_dic_cmd = "cd / && cd " + config_autoPackage.project_path_macelse:path1 = config_autoPackage.project_path.split('/')[0]path2 = config_autoPackage.project_path.split(':/')[1]print("盘符: " + path1)print("路径: " + path2)P.get_dic_cmd = path1 + " && cd / && cd " + path2cmd = P.get_dic_cmd + __cmdCleanupprint(cmd)t = os.system(cmd)print("prepare_cleanup_SVN  :" + str(t))__prepare_revert_SVN()def __prepare_revert_SVN():print('开始revert')cmd = '%s && svn revert %s -R && svn revert %s -R' % \(P.get_dic_cmd, config_autoPackage.external_link1,     config_autoPackage.external_link2)print(cmd)t = os.system(cmd)print("prepare_revert_SVN :" + str(t))__update_SVN()def __update_SVN():print('开始update')cmd = P.get_dic_cmd + __cmdUpdateprint(cmd)t = os.system(cmd)if t == 0:return Trueelse:return Falseif __name__ == '__main__':start_update()

代码可能写的比较乱(python新手,见谅见谅),思路就是通过命令行切换到对应的目录下,然后按顺序执行clearup,revert,update。

        需要注意的几点:

1. 多条命令一起调用的时候 用 && 隔开,这样会逐条执行而不会同时执行。

2. 根目录下revert并不会revert外链的目录,所以代码中,外链的目录是要单独revert的。

3. 完美执行命令行之后,系统给一个返回值“0”。

二、自动打Bundle

这一块,主要思路是用命令行来调用Unity-Editor的静态方法,不必多说,上代码:

AutoBundle.py

import os
import time
import platform
import subprocess
import config_autoPackageclass P:if_mac = Falselog_path = ''def start_bundle():m_platform = platform.system()if m_platform == 'Darwin':P.if_mac = Trueelse:P.if_mac = Falseif not P.if_mac:__kill_unity()time.sleep(1)__clear_log()time.sleep(1)__start_build_bundle()__monitor_unity_log()print('Build_Bundle_Done')def __kill_unity():os.system('taskkill /IM Unity.exe /F')def __clear_log():if P.if_mac:P.log_path = config_autoPackage.bundle_log_macelse:P.log_path = config_autoPackage.bundle_logif os.path.exists(P.log_path):os.remove(P.log_path)def __start_build_bundle():if P.if_mac:cmd = '%s -projectPath %s -executeMethod %s -logFile %s -batchmode -quit' \% (config_autoPackage.unity_app, config_autoPackage.project_path_mac,config_autoPackage.bundle_fuc, config_autoPackage.bundle_log_mac)else:cmd = '%s -projectPath %s -executeMethod %s -logFile %s -batchmode -quit' \% (config_autoPackage.unity_exe, config_autoPackage.project_path,config_autoPackage.bundle_fuc, config_autoPackage.bundle_log)print(cmd)subprocess.Popen(cmd)def __monitor_unity_log():print("__monitor_unity_log")pos = 0while True:if os.path.exists(P.log_path):breakelse:time.sleep(0.1)while True:fd = open(P.log_path, 'r', encoding='utf-8')if 0 != pos:fd.seek(pos, 0)while True:line = fd.readline()pos = fd.tell()if 'BatchMode: Unity has not been activated' in line:print('失败 :Unity 需要重新激活!!! ')fd.close()returnif 'Exiting batchmode successfully' in line:print('Bundle 成功 :Exiting batchmode successfully')fd.close()returnif 'Scripts have compiler errors.' in line:print('代码编译错误!!!')fd.close()returnif line.strip():print(line)else:breakfd.close()if __name__ == "__main__":start_bundle()

C#部分的代码放到 下面~

三、自动Package

这一块和上面打Bundle一样,py方面就是调用命令行,上代码:

AutoBundle.py

import os
import time
import platform
import subprocess
import config_autoPackageclass P:if_mac = Falselog_path = ''def start_package():m_platform = platform.system()if m_platform == 'Darwin':P.if_mac = Trueelse:P.if_mac = Falseif not P.if_mac:__kill_unity()time.sleep(1)__clean_log()time.sleep(1)__start_build_package()__monitor_unity_log()def __kill_unity():os.system('taskkill /IM Unity.exe /F')def __clean_log():if P.if_mac:P.log_path = config_autoPackage.bundle_log_macelse:P.log_path = config_autoPackage.bundle_logif os.path.exists(P.log_path):os.remove(P.log_path)def __start_build_package():if P.if_mac:cmd = '%s -projectPath %s -logFile %s -executeMethod %s -batchmode -quit' % \(config_autoPackage.unity_app, config_autoPackage.project_path_mac,P.log_path, config_autoPackage.package_fuc)else:cmd = '%s -projectPath %s -logFile %s -executeMethod %s -batchmode -quit' % \(config_autoPackage.unity_exe, config_autoPackage.project_path,P.log_path, config_autoPackage.package_fuc)print("run :  " + cmd)subprocess.Popen(cmd)def __monitor_unity_log():pos = 0while True:if os.path.exists(P.log_path):breakelse:time.sleep(0.1)while True:fd = open(P.log_path, 'r', encoding='UTF-8')if 0 != pos:fd.seek(pos, 0)while True:line = fd.readline()pos = fd.tell()if line.strip():print(line)if 'is an incorrect path for a scene file: Build Failed' in line:print('打包失败 :错误的场景路径 看Log!!!')fd.close()returnif 'Scripts have compiler errors.' in line:print('代码编译错误!!!')fd.close()returnif 'DisplayProgressNotification: Build Failed' in line:print('打包失败 看Log!!!')fd.close()returnif 'There is no Json at' in line:print('目标路径 缺少配置Json文件!!!')fd.close()returnif 'Exiting batchmode successfully' in line:print('Package成功 : Exiting batchmode successfully')fd.close()returnelse:breakfd.close()if __name__ == '__main__':start_package()

C#部分的代码:AutoPackage.cs

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using LitJson;public class AutoPackage : EditorWindow
{static readonly string jsonPath = "C:/Users/hp/Desktop/AutoPackageTest/BuildPackageConfig.txt";static BuildPlayerOptions m_buildOption;public static void BuildApk(){Debug.Log("Build Apk ... ");ReadBuildConfig();BuildPipeline.BuildPlayer(m_buildOption);}public static void BuildBundle(){Debug.Log("Build Bundle ... ");Framework.Resource.CustomBundleBuilder.CallBuildByCMD();}private static void ReadBuildConfig(){Debug.Log("Start Read Build Config ... ");m_buildOption = new BuildPlayerOptions();JsonDataEx jd;if (File.Exists(jsonPath)){string str = File.ReadAllText(jsonPath);jd = JsonMapper.ToObject<JsonDataEx>(str);}else{Debug.LogError("There is no Json at : " + jsonPath);return;#region ForTest//jd = new JsonDataEx();//jd["productName"] = "test";//jd["companyName"] = "justTEST";//jd["packagePath"] = "C:/Users/hp/Desktop/AutoPackageTest/";//jd["packageName"] = "test.apk";//jd["version"] = "1.0.0";//jd["scenePath"] = "Assets/";//jd["defineInAndriod"] = "fuck1;fuck2";//jd["defineInIOS"] = "fuck3;fuck4";//jd["targetPlatform"] = "A";//jd["senceList"] = new JsonDataEx//{//    "1.unity",//    "2.unity"//};//File.WriteAllText("C:/Users/hp/Desktop/AutoPackageTest/jsonConfig.txt", jd.ToJson());#endregion};//名字Debug.Log("包名 :" + jd["productName"]);PlayerSettings.productName = jd["productName"].ToString();Debug.Log("公司名 :" + jd["companyName"]);PlayerSettings.companyName = jd["companyName"].ToString();//打包版本号Debug.Log("打包版本 :" + jd["version"]);PlayerSettings.bundleVersion = jd["version"].ToString();//添加宏定义Debug.Log("安卓宏定义 : " + jd["defineInAndriod"]);PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, jd["defineInAndriod"].ToString());Debug.Log("苹果宏定义 : " + jd["defineInIOS"]);PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.iOS, jd["defineInIOS"].ToString());//打包需要的各个场景Debug.Log("场景路径 : " + jd["scenePath"]);string scenePath = jd["scenePath"].ToString();List<string> tmp = new List<string>();for (int i = 0; i < jd["senceList"].Count; i++){Debug.Log("需打入场景 :" + jd["senceList"][i].ToString());tmp.Add(scenePath + jd["senceList"][i].ToString());}m_buildOption.scenes = tmp.ToArray();//打包平台Debug.Log("目标平台 : " + jd["targetPlatform"]);m_buildOption.target = jd["targetPlatform"].ToString() == "A" ? BuildTarget.Android : BuildTarget.iOS;//打包路径 + 包名Debug.Log("包路径 : " + jd["packagePath"] + jd["packageName"]);m_buildOption.locationPathName = jd["packagePath"].ToString() + jd["packageName"].ToString();//屏幕方向PlayerSettings.defaultInterfaceOrientation = UIOrientation.LandscapeLeft;//打包环境ApiCompatibilityLevel api = ApiCompatibilityLevel.NET_2_0;string apis = jd["APILevel"].ToString();if (!String.IsNullOrEmpty(apis)){if (apis.Contains("2_0_S")){api = ApiCompatibilityLevel.NET_2_0_Subset;}else if (apis.Contains("2_0")){api = ApiCompatibilityLevel.NET_2_0;}else if (apis.Contains("4_6")){api = ApiCompatibilityLevel.NET_4_6;}else if (apis.Contains("Micro")){api = ApiCompatibilityLevel.NET_Micro;}else if (apis.Contains("Web")){api = ApiCompatibilityLevel.NET_Web;}}Debug.Log("API兼容 :" + api.ToString());PlayerSettings.SetApiCompatibilityLevel(jd["targetPlatform"].ToString() == "A" ? BuildTargetGroup.Android : BuildTargetGroup.iOS, api);//Andriod 设置AndroidTargetDevice dev = AndroidTargetDevice.ARMv7;string devices = jd["AndroidTarget"].ToString();if (!String.IsNullOrEmpty(devices)){if (devices.Contains("FAT")){dev = AndroidTargetDevice.FAT;}else if (devices.Contains("ARMv7")){dev = AndroidTargetDevice.ARMv7;}else if (devices.Contains("x86")){dev = AndroidTargetDevice.x86;}}Debug.Log("AndroidTargetDevice : " + dev.ToString());PlayerSettings.Android.targetDevice = dev;int bundleVersionCode = 63374379;int.TryParse(jd["AndroidBundleVersion"].ToString(), out bundleVersionCode);Debug.Log("AndroidBundleVersion : " + bundleVersionCode);PlayerSettings.Android.bundleVersionCode = bundleVersionCode;AndroidSdkVersions sdkV = AndroidSdkVersions.AndroidApiLevel19;string version = jd["AndroidSdkMinVersion"].ToString();try{sdkV = (AndroidSdkVersions)Enum.Parse(typeof(AndroidSdkVersions), version);}catch{Debug.Log("读取AndroidSdkVersions失败, 默认:AndroidApiLevel19");}Debug.Log("AndroidSdkMinVersion : " + sdkV.ToString());PlayerSettings.Android.minSdkVersion = sdkV;Debug.Log("keystoreName : " + jd["keystoreName"]);PlayerSettings.Android.keystoreName = jd["keystoreName"].ToString();Debug.Log("keyaliasName : " + jd["keyaliasName"]);PlayerSettings.Android.keyaliasName = jd["keyaliasName"].ToString();//密码Debug.Log("keyaliasPass : " + jd["keyaliasPass"]);PlayerSettings.keyaliasPass = jd["keyaliasPass"].ToString();Debug.Log("keystorePass : " + jd["keystorePass"]);PlayerSettings.keystorePass = jd["keystorePass"].ToString();Debug.Log("Read Build Config End !");}
}///JsonData拓展类
public class JsonDataEx : LitJson.JsonData
{public new LitJson.JsonData this[string key]{get{try{return (this as JsonData)[key];}catch (Exception e){Debug.LogError("The KEY : " + key + " 出错!!!  ||  " + e);return "";}}set{(this as JsonData)[key] = value;}}
}

上面这段代码逻辑还是很清晰的,就是通过 Editor下的静态方法来打Bundle,和Package。

其中读打Bundle的配置工具是事先同事写好了的,代码量不小就不贴在这里了。

打包的配置我整合在了json里,差不多这些应该够了,根据自己的需要再增删改一波就可以实用了~

四、总结

激动的心,颤抖的手,小李第一篇博客有没有!

工作马上3年了,第一次写博客分享代码,记录心得,还是有点小紧脏~~~

这次写自动打包,下次搞一个新手版的socket吧,学习使用~

咳咳,扯远了....

1. 实际工作中,还可以用Jekins或者各种定时任务类的工具来定时触发主脚本,每天一个无人看守版测试包,在每个流程失败的节点发送邮件告知 工作人员哪里出了问题,或者打包成功。

2. 调用Unity命令行的时候, -batchmode 挺好用,可以在不打开Unity窗口界面的情况下,在后台去调用你让它执行的方法,很大的提升了运行效率,但是如果有大量资源需要加载的话,放心还是挺慢的~ 可以通过log去看,十几万行的log会告诉你,Unity都干了啥~  红红火火恍恍惚惚哈 ~

3. Unity给出的log的编码格式是 utf-8,可能会出现python解析不了的情况,解决办法就是:

fd = open( xxxxxx ,'r', encoding='utf-8')

4. 之前运行的时候遇到过一个让人很头疼的问题,一直报错提示编码格式解析不出来,而且每次都是在解析到中文行之后就报错,究其原因,是因为在读log的时候,用pos来定位读到了哪,pos += len(line),然后在下次循环的时候 fd.seek(pos, 0) 来接着读。但是!!! len(line)在遇到了中文的时候 返回的长度是不准确的!!!这样导致 下次循环再读的时候,起始位置没在一个完整的字开头的位置,所以就解析出错了。最后将pos的赋值 改成了 fd.tell() 让python给我一个准确的已读的尾巴位置,问题迎刃而解~

五、参考 及 API

1.《基于python脚本,实现Unity全平台的自动打包》: https://www.cnblogs.com/zblade/p/9298905.html

2. Unity 官方命令行的 API :https://docs.unity3d.com/560/Documentation/Manual/CommandLineArguments.html

U3D 自动更新/打Bundle/打包相关推荐

  1. linux shell 脚本 svn自动更新项目并且打包 、发布、备份

    codePath="/root/gitproject/"; project=$1; if [ ! -n "$1" ] ;then projectName=&qu ...

  2. vue 实现app项目版本迭代自动更新 热更新

    需求: app打开时自动检测是否是最新版本,如果不是出现弹框,点击升级 效果图: 实现步骤: 一,准备工作 声明一个bol值VersionFlag控制版本升级弹窗的开关,默认关闭 Website: 更 ...

  3. uniapp 实现app自动更新

    需求概述: 最近遇到的需求,扫码核验的app需要在线自动升级安装(因app简单上不了应用市场,所以调研用在线更新的办法) 第一步:首先需要一个可以更新和获取数据的接口(后端) 比如第一次打包时的版本名 ...

  4. electron打包可选择安装位置,可自动更新

    Electron打包调参软件(windows版) ----------------------------------可选安装位置,可自动更新,手动更新 一:引包:electron,electron- ...

  5. Winform 打包 混淆 自动更新

    路径: 最终的解决方案是,ConfuserEx+Installshield+AutoUpdater.NET,ConfuserEx做代码混淆工作,Installshield可以解决注册表的问题,Auto ...

  6. uniapp打包安卓APP实现自动更新(更新app)

    先列出需要注意的地方,避免新手朋友们出错 如果uniapp运行app报错:ReferenceError: plus is not defined ,普通浏览器里没有plus环境,只有HBuilder真 ...

  7. Unity热更新系列之一: bundle打包和打包策略

    资源对于手游来说其重要性是不言而喻的,bundle的打包策略最终影响的是资源的加载以及内存,所以要根据项目的实际情况去平衡你的bundle的粒度和大小.对于粒度,因为项目起初用的是Unity5.3.8 ...

  8. 一招解决BS转CS模式:浏览终端开发-Electron集成打包、本地配置文件及自动更新

    将普通的网页转换为桌面应用并兼容现在的H5,基本的思路都是打包封装谷歌公司的开源版Chromium 使其充当与本地应用通讯的媒介: 成本比较低的是electron  CefShap(C#)  至于bl ...

  9. vue前端打包更新后客户端自动更新

    vue单页面网页端 项目前言(缓存) 项目之前已经通过webpack打包的时候都会把静态资源文件名加个哈希后缀,且index.html中引入的时候也加了相应的哈希后缀,这样每次打包都会是最新的,ind ...

最新文章

  1. R语言dplyr包使用recode函数进行数据列内容编码、转换实战:类似于pandas中的map函数(例如,将内容从字符串映射到数值)
  2. IQueryable和IQueryProvider初尝
  3. 自学python需要下载什么软件-一个零基础学习Python应该知道的学习步骤与规划
  4. 斯坦福大学CS224d基础1:线性代数回顾 Linear Algebra - review
  5. Dubbo暴露服务过程
  6. mysql倍增表的内容,mysql - DATEDIFF不会在触发器内倍增 - SO中文参考 - www.soinside.com...
  7. c语言二维图形变换程序,【计算机图形学】3-2 二维几何变换根本代码
  8. 统计带头结点的单向链表的个数并存放在形参n所指的单元中。 欢迎评论 指点。
  9. kafka python client:PyKafka vs kafka-python
  10. ansys中模态扩展是什么意思_ansys模态分析步骤
  11. 计算机类的竞赛一般多会报,2017自主招生报考条件建议65问,涵盖所有问题!...
  12. 我模仿了一个自助装机的页面,可是有一个功能一直实现不了。请求高手帮我看看。
  13. 如何像打王者荣耀一样励志学习
  14. 使用动态规划求解算法问题的五大特点总结(附基于Python的参考代码)
  15. 云文件共享服务器,云文件共享服务器软件
  16. Scala的安装与配置
  17. java.net.ConnectException: Connection refused: no further information【已解决】
  18. fatfs读写csv文件
  19. javascript 全栈_什么是JavaScript? 全栈编程语言
  20. 未在服务器上找到sql安装程序文件,MS SQL Server 2000/以前的某个程序安装已在安装计算机上创建挂起的文件操作。...

热门文章

  1. 爱普生Epson L15158 一体机驱动
  2. 2022年起重机司机(限桥式起重机)理论题库及答案
  3. MYSQL函数 group_concat巨坑!!!
  4. 13个免费的FLASH资源网站
  5. 用RDA进行微生物环境因子分析
  6. 张量分解(二):CP分解
  7. 如何调试程序及调试程序基本步骤、方法详解
  8. idea格式化代码快捷键 快捷键: Ctrl+Shift+Alt+L
  9. DC中如何使用策略下发功能管理域内的主机
  10. 【AutoGPT】LangChain 快速入门指南(中文版)