// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using UnityEditor;
using UnityEditor.Build.Reporting;
using UnityEngine;
namespace XRTool.Util
{
public enum BuildTargets
{
NoHand = 0,
Hand = 1,
Both = 2,
None = 3
}
///
/// 打包窗口
///
public class BuildDeployWindow : EditorWindow
{
private Vector2 scrollPos;
private static BuildConfig buildConf;
///
public IEnumerable Scenes { get; set; }
public string outputPath;
public bool isAutoUpdateVersion = true;
private bool isOpenOutPath = true;
private string realPath;
private string serverPath;
public BuildOptions buildOptions = BuildOptions.None;
private string[] suffix = new string[] { ".exe", ".apk", "Project", };
public string appName;
private int buildIndex = 2;
public const string initVersion = "1.0.0";
private string allSymbol;
private bool isAutoInstall = true;
private string androidSDPath = "sdcard/Android/data/";
private BuildTargets buildTargets;
[MenuItem("XRTools/Build Window", false, 0)]
public static void OpenWindow()
{
// Dock it next to the Scene View.
var window = GetWindow(typeof(SceneView));
window.titleContent = new GUIContent("Build Window");
window.Show();
}
private void OnEnable()
{
buildTargets = BuildTargets.None;
PlayerSettings.companyName = "shadowcreator";
buildIndex = 2;
if (buildConf == null)
{
if (BuildConfigMgr.Instance.IsInit)
{
buildConf = BuildConfig.Instance;
}
else
{
UnityLog.LogError("配置初始化失败!");
}
//buildConf = AssetDatabase.LoadAssetAtPath("Assets/GameData/BuildConf/BuildConfig.asset");
}
titleContent = new GUIContent("Build Window");
minSize = new Vector2(512, 256);
CheckVersion();
if (buildConf == null)
{
UnityLog.LogError("buildConf is null");
}
//UnityLog.Log(buildConf);
}
private void CheckVersion()
{
string version = PlayerSettings.bundleVersion;
string[] curversion = version.Split('.');
if (curversion.Length < 3)
{
PlayerSettings.bundleVersion = initVersion;
Debug.Log("版本号初始化:" + initVersion);
}
}
///
/// 更新版本号,在打包时调用此函数
///
public string UpdateGameVersion()
{
string version = PlayerSettings.bundleVersion;
string[] curversion = version.Split('.');
if (curversion.Length < 3)
{
Debug.LogError("请规范版本号:" + initVersion);
return PlayerSettings.bundleVersion;
}
///测试版本,程序自测版本
if (buildConf.buildType == BuildType.Test)
{
return PlayerSettings.bundleVersion;
}
int startIndex = (int)buildConf.buildVersion;
int ver = int.Parse(curversion[startIndex]) + 1;
curversion[startIndex] = ver.ToString();
if (buildConf.buildVersion != BuildVersion.Build)
{
for (int i = startIndex + 1; i < curversion.Length; i++)
{
curversion[i] = "0";
}
}
string newVersion = curversion[0];
for (int i = 1; i < curversion.Length; i++)
{
newVersion += "." + curversion[i];
}
return newVersion;
}
private void ChangeHand()
{
if (buildTargets == BuildTargets.Hand)
{
PlayerSettings.productName = BuildConfig.Instance.appProName + "WithHand";
BuildConfig.Instance.useHand = true;
}
else if (buildTargets == BuildTargets.NoHand)
{
PlayerSettings.productName = BuildConfig.Instance.appProName + "NoHand";
BuildConfig.Instance.useHand = false;
}
else
{
PlayerSettings.productName = BuildConfig.Instance.appProName;
BuildConfig.Instance.useHand = false;
}
if (buildTargets != BuildTargets.None)
{
PlayerSettings.applicationIdentifier = "com." + PlayerSettings.companyName + "." + PlayerSettings.productName;
}
BuildConfig.Instance.appPackName = PlayerSettings.applicationIdentifier;
}
///
/// 属性展示和同步
///
private void OnGUI()
{
//buildConf = EditorGUI.ObjectField(new Rect(0, 5, 600, 20), "BuildSetting", buildConf, typeof(BuildConfig), true) as BuildConfig;
if (!buildConf)
{
return;
}
scrollPos = GUI.BeginScrollView(new Rect(0, 30, position.width, position.height),
scrollPos, new Rect(0, 0, 1920, 2160));
BuildConfig.Instance.appProName = EditorGUILayout.TextField("应用名称", BuildConfig.Instance.appProName);
BuildConfig.Instance.appPackName = EditorGUILayout.TextField("包名", BuildConfig.Instance.appPackName);
buildConf.buildVersion = (BuildVersion)EditorGUILayout.EnumPopup("版本类型", buildConf.buildVersion);
buildConf.buildType = (BuildType)EditorGUILayout.EnumPopup("发布模式", buildConf.buildType);
buildConf.assetsType = (AssetsType)EditorGUILayout.EnumPopup("资源访问方式", buildConf.assetsType);
buildConf.buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("运行的平台", buildConf.buildTarget);
buildConf.playType = (PlayType)EditorGUILayout.EnumPopup("运行的模式", buildConf.playType);
//buildConf.buildTargetGroup = (BuildTargetGroup)EditorGUILayout.EnumPopup("发布的平台", buildConf.buildTargetGroup);
buildConf.otherSymbol = EditorGUILayout.TextField("宏", buildConf.otherSymbol);
allSymbol = EditorGUILayout.TextField("全部宏", allSymbol);
if (string.IsNullOrEmpty(allSymbol))
{
allSymbol = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup);
}
isAutoUpdateVersion = EditorGUILayout.Toggle("更新版本", isAutoUpdateVersion);
isOpenOutPath = EditorGUILayout.Toggle("打开输出路径", isOpenOutPath);
if (buildConf.buildTarget == BuildTarget.Android)
{
isAutoInstall = EditorGUILayout.Toggle("自动安装", isAutoInstall);
}
EditorGUILayout.LabelField("Version", PlayerSettings.bundleVersion);
EditorGUILayout.LabelField("发布时间", buildConf.buildTime);
outputPath = EditorGUILayout.TextField("输出路径", outputPath);
if (string.IsNullOrEmpty(outputPath))
{
string path = Application.dataPath;
path = path.Substring(0, path.LastIndexOf("/"));
outputPath = path;
}
realPath = EditorGUILayout.TextField("完整路径", realPath);
serverPath = EditorGUILayout.TextField("服务器地址", serverPath);
androidSDPath = EditorGUILayout.TextField("安卓sd卡路径", androidSDPath);
if (string.IsNullOrEmpty(realPath))
{
realPath = Path.Combine(outputPath, buildConf.buildTarget.ToString(), buildConf.playType.ToString(), Application.productName);
}
buildOptions = (BuildOptions)EditorGUILayout.EnumPopup("发布设置", buildOptions);
var bd = buildTargets;
buildTargets = (BuildTargets)EditorGUILayout.EnumPopup("发布操作方式", buildTargets);
if (bd != buildTargets)
{
ChangeHand();
}
if (GUILayout.Button("重置版本号", GUILayout.Width(200)))
{
buildConf.appVersion = initVersion;
PlayerSettings.bundleVersion = buildConf.appVersion;
}
if (GUILayout.Button("强制升级版本", GUILayout.Width(200)))
{
PlayerSettings.bundleVersion = UpdateGameVersion();
}
if (GUILayout.Button("同步设置", GUILayout.Width(200)))
{
SetDef();
}
if (GUILayout.Button("清空编辑器日志", GUILayout.Width(200)))
{
UnityLog.ClearAllLog();
}
if (GUILayout.Button("打开编辑器缓存路径", GUILayout.Width(200)))
{
OpenDirectory(Application.persistentDataPath);
}
if (buildConf.buildTarget == BuildTarget.Android && GUILayout.Button("安装", GUILayout.Width(200)))
{
if (!string.IsNullOrEmpty(appName) && !string.IsNullOrEmpty(realPath))
{
InstallApp(Path.Combine(realPath, appName));
}
}
if (buildConf.buildTarget == BuildTarget.Android && GUILayout.Button("卸载", GUILayout.Width(200)))
{
if (!string.IsNullOrEmpty(appName) && !string.IsNullOrEmpty(realPath))
{
UnInstallApp();
}
}
if (GUILayout.Button("清空Androd日志", GUILayout.Width(200)))
{
if (Directory.Exists(realPath + "/LogData"))
{
Directory.Delete(realPath + "/LogData", true);
}
}
if (GUILayout.Button("拷贝缓存数据", GUILayout.Width(150)))
{
string resPath = "sdcard/ShadowCreate/MRStores/";
string logPath = realPath + "/ShadowCreate/MRStores/";
string[] files = new string[] { "DeviceDownInfo.xml" };
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
for (int i = 0; i < files.Length; i++)
{
string tmpPath = logPath + "/" + files[i];
if (File.Exists(tmpPath))
{
File.Delete(tmpPath);
}
Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd));
string adb = "adb pull " + resPath + files[i] + " " + logPath;
newThread.Start(adb);
}
OpenDirectory(logPath);
}
if (GUILayout.Button("拷贝日志到本地", GUILayout.Width(150)))
{
string resPath = androidSDPath + Application.identifier + "/files/LogData/";
string logPath = realPath + "/LogData/" + buildConf.buildType.ToString() +
"_" + PlayerSettings.bundleVersion + "/" + Application.version;
string[] files = new string[] { "error.txt", "except.txt", "log.txt" };
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
for (int i = 0; i < files.Length; i++)
{
string tmpPath = logPath + "/" + files[i];
if (File.Exists(tmpPath))
{
File.Delete(tmpPath);
}
Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd));
string adb = "adb pull " + resPath + files[i] + " " + logPath;
newThread.Start(adb);
}
OpenDirectory(logPath);
}
if (GUILayout.Button("推送服务器地址", GUILayout.Width(150)))
{
string targetPath = androidSDPath + Application.identifier + "/files/";
Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd));
string adb = "adb push " + serverPath + " " + targetPath;
newThread.Start(adb);
}
if (GUILayout.Button("发布", GUILayout.Width(150)))
{
if (buildTargets != BuildTargets.Both)
{
DisPlayBuild(buildTargets);
}
else
{
///先安装手势版本,再安装无手势版本
DisPlayBuild(BuildTargets.Hand);
isAutoUpdateVersion = false;
DisPlayBuild(BuildTargets.NoHand);
}
}
GUI.EndScrollView();
//GUI.EndScrollView();
if (GUI.changed)
{
UpdateSetting();
}
}
public void DisPlayBuild(BuildTargets buildTargets)
{
string lastBuildTime = buildConf.buildTime;
buildConf.buildTime = DateTime.Now.ToString();
EditorUtility.SetDirty(buildConf);
this.buildTargets = buildTargets;
ChangeHand();
///设置宏编译指令
SetDef();
string lastVersion = PlayerSettings.bundleVersion;
UpdateSetting();
string newVersion = isAutoUpdateVersion ? UpdateGameVersion() : lastVersion;
PlayerSettings.bundleVersion = newVersion;
appName = Application.productName + "_" + buildConf.buildType.ToString() +
"_" + PlayerSettings.bundleVersion + "_" + DateTime.Now.ToString("MMddHHmm") + suffix[buildIndex];
bool isSucess = PlayerBuild(Path.Combine(realPath, appName));
if (!isSucess)
{
PlayerSettings.bundleVersion = lastVersion;
buildConf.buildTime = lastBuildTime;
EditorUtility.SetDirty(buildConf);
}
else
{
if (isOpenOutPath)
{
OpenDirectory(realPath);
}
if (buildConf.buildTarget == BuildTarget.Android && isAutoInstall)
{
InstallApp(Path.Combine(realPath, appName));
}
}
Debug.Log("当前版本" + PlayerSettings.bundleVersion);
}
private void InstallApp(string realPath)
{
if (File.Exists(realPath))
{
Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd));
string cmd = "adb install -r -d " + realPath;
newThread.Start(cmd);
}
else
{
UnityLog.LogError(realPath + " 不存在");
}
}
private void UnInstallApp()
{
Thread newThread = new Thread(new ParameterizedThreadStart(RunCmd));
string cmd = "adb uninstall " + Application.identifier;
newThread.Start(cmd);
}
private void UpdateSetting()
{
if (buildConf.buildTarget == BuildTarget.Android)
{
buildConf.buildTargetGroup = BuildTargetGroup.Android;
if (EditorUserBuildSettings.exportAsGoogleAndroidProject)
{
buildIndex = 2;
}
else
{
buildIndex = 1;
}
}
if (buildConf.buildTarget == BuildTarget.iOS)
{
buildConf.buildTargetGroup = BuildTargetGroup.iOS;
buildIndex = 2;
}
if (buildConf.buildTarget == BuildTarget.StandaloneWindows)
{
buildConf.buildTargetGroup = BuildTargetGroup.Standalone;
buildIndex = 0;
}
//buildConf.buildType.ToString(), buildConf.playType.ToString(),
realPath = Path.Combine(outputPath, Application.productName, buildConf.buildTarget.ToString(), buildConf.playType.ToString());
}
private bool PlayerBuild(string path)
{
if (EditorUserBuildSettings.activeBuildTarget != buildConf.buildTarget)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(buildConf.buildTargetGroup, buildConf.buildTarget);
}
BuildReport buildReport = Build(path);
bool success = buildReport != null && buildReport.summary.result == BuildResult.Succeeded;
return success;
}
private BuildReport Build(string path)
{
Scenes = EditorBuildSettings.scenes.Where(scene => scene.enabled).Select(scene => scene.path);
EditorUtility.DisplayProgressBar("Build Pipeline", "Gathering Build Data...", 0.25f);
BuildReport buildReport = default;
try
{
buildReport = BuildPipeline.BuildPlayer(
Scenes.ToArray(),
path,
EditorUserBuildSettings.activeBuildTarget, buildOptions);
}
catch (Exception e)
{
Debug.LogError($"{e.Message}\n{e.StackTrace}");
}
EditorUtility.ClearProgressBar();
return buildReport;
}
private void SetDef()
{
string lastSym = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup);
Debug.Log(lastSym);
///设置指令用;分割
string symbol = string.Format("buildType_{0};assetsType_{1};buildPlatform_{2};playType_{3}",
buildConf.buildType, buildConf.assetsType, buildConf.buildTarget, buildConf.playType).ToUpper() + ";" + buildConf.otherSymbol;
if (symbol != lastSym)
{
Debug.Log(lastSym + "当前宏编译配置:" + symbol);
PlayerSettings.SetScriptingDefineSymbolsForGroup(buildConf.buildTargetGroup, symbol);
}
allSymbol = symbol;
}
///
/// 弹出指定的文件窗口,仅windows和mac使用
///
///
public static void OpenDirectory(string path)
{
if (string.IsNullOrEmpty(path)) return;
if (!Directory.Exists(path))
{
UnityEngine.Debug.LogError("No Directory: " + path);
return;
}
//Application.dataPath 只能在主线程中获取
int lastIndex = Application.dataPath.LastIndexOf("/");
string shellPath = Application.dataPath.Substring(0, lastIndex) + "/Shell/";
// 新开线程防止锁死
Thread newThread = new Thread(new ParameterizedThreadStart(CmdOpenDirectory));
newThread.Start(path);
}
private static void CmdOpenDirectory(object obj)
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
#if UNITY_EDITOR_WIN
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c start " + obj.ToString();
#elif UNITY_EDITOR_OSX
p.StartInfo.FileName = "bash";
string shPath = shellPath + "openDir.sh";
p.StartInfo.Arguments = shPath + " " + obj.ToString();
#endif
//UnityEngine.Debug.Log(p.StartInfo.Arguments);
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardInput = true;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.RedirectStandardError = true;
p.StartInfo.CreateNoWindow = true;
p.Start();
p.WaitForExit();
p.Close();
}
private static void RunCmd(object command)
{
//例Process
System.Diagnostics.Process p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe"; //确定程序名
p.StartInfo.Arguments = "/c " + command.ToString(); //确定程式命令行
p.StartInfo.UseShellExecute = false; //Shell的使用
p.StartInfo.RedirectStandardInput = true; //重定向输入
p.StartInfo.RedirectStandardOutput = true; //重定向输出
p.StartInfo.RedirectStandardError = true; //重定向输出错误
p.StartInfo.CreateNoWindow = false; //设置置不显示示窗口
p.Start();
//return p.StandardOutput.ReadToEnd(); //输出出流取得命令行结果果
}
}
}