mirror of
https://github.com/xiaopeng12138/MaiDXR.git
synced 2024-12-18 20:05:52 +01:00
881 lines
32 KiB
C#
881 lines
32 KiB
C#
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Oculus SDK License Agreement (the "License");
|
|
* you may not use the Oculus SDK except in compliance with the License,
|
|
* which is provided at the time of installation or download, or which
|
|
* otherwise accompanies this software in either electronic or hard copy form.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://developer.oculus.com/licenses/oculussdk/
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, the Oculus SDK
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#if USING_XR_MANAGEMENT && (USING_XR_SDK_OCULUS || USING_XR_SDK_OPENXR)
|
|
#define USING_XR_SDK
|
|
#endif
|
|
|
|
#if UNITY_2020_1_OR_NEWER
|
|
#define REQUIRES_XR_SDK
|
|
#endif
|
|
|
|
// Unit made a change that broke `BuildOptions.AcceptExternalModificationsToPlayer`
|
|
// Thread detailing issue: https://forum.unity.com/threads/oculus-build-invalidoperationexception-the-build-target-does-not-support-build-appending.994930/
|
|
#if UNITY_2020_1_OR_NEWER || UNITY_2019_4_OR_NEWER
|
|
#define DONT_USE_BUILD_OPTIONS_EXTERNAL_MODIFICATIONS_FLAG
|
|
#endif
|
|
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using System;
|
|
using System.IO;
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Threading;
|
|
|
|
/// <summary>
|
|
/// Allows Oculus to build apps from the command line.
|
|
/// </summary>
|
|
[InitializeOnLoadAttribute]
|
|
partial class OculusBuildApp : EditorWindow
|
|
{
|
|
static void SetPCTarget()
|
|
{
|
|
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.StandaloneWindows)
|
|
{
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows);
|
|
}
|
|
#if !USING_XR_SDK && !REQUIRES_XR_SDK
|
|
UnityEditorInternal.VR.VREditor.SetVREnabledOnTargetGroup(BuildTargetGroup.Standalone, true);
|
|
#pragma warning disable 618
|
|
PlayerSettings.virtualRealitySupported = true;
|
|
#pragma warning restore 618
|
|
#endif
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
static void SetAndroidTarget()
|
|
{
|
|
EditorUserBuildSettings.androidBuildSubtarget = MobileTextureSubtarget.ASTC;
|
|
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
|
|
|
|
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
|
|
{
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
|
|
}
|
|
|
|
#if !USING_XR_SDK && !REQUIRES_XR_SDK
|
|
UnityEditorInternal.VR.VREditor.SetVREnabledOnTargetGroup(BuildTargetGroup.Standalone, true);
|
|
#pragma warning disable 618
|
|
PlayerSettings.virtualRealitySupported = true;
|
|
#pragma warning restore 618
|
|
#endif
|
|
AssetDatabase.SaveAssets();
|
|
}
|
|
|
|
#if UNITY_EDITOR_WIN && UNITY_ANDROID
|
|
// Build setting constants
|
|
const string REMOTE_APK_PATH = "/data/local/tmp";
|
|
const float USB_TRANSFER_SPEED_THRES = 25.0f;
|
|
const float USB_3_TRANSFER_SPEED = 32.0f;
|
|
const float TRANSFER_SPEED_CHECK_THRESHOLD = 4.0f;
|
|
const int NUM_BUILD_AND_RUN_STEPS = 9;
|
|
const int BYTES_TO_MEGABYTES = 1048576;
|
|
|
|
// Build window variables (not saved)
|
|
GUIStyle windowStyle;
|
|
GUIStyle calloutStyle;
|
|
string currConnectedDevice;
|
|
Vector2 scrollViewPos;
|
|
|
|
// Build window variables (saved)
|
|
static bool saveKeystorePasswords;
|
|
static bool isRunOnDevice;
|
|
static string outputApkPath;
|
|
|
|
// Progress bar variables
|
|
static int totalBuildSteps;
|
|
static int currentStep;
|
|
static string progressMessage;
|
|
|
|
// Build setting varaibles
|
|
static string gradlePath;
|
|
static string jdkPath;
|
|
static string androidSdkPath;
|
|
static string applicationIdentifier;
|
|
static bool isDevelopmentBuild;
|
|
static string productName;
|
|
static string dataPath;
|
|
|
|
static string gradleTempExport;
|
|
static string gradleExport;
|
|
static bool showCancel;
|
|
static bool buildInProgress;
|
|
|
|
static DirectorySyncer.CancellationTokenSource syncCancelToken;
|
|
static Process gradleBuildProcess;
|
|
static Thread buildThread;
|
|
|
|
static bool? apkOutputSuccessful;
|
|
|
|
[MenuItem("Oculus/OVR Build/OVR Build APK... %#k", false, 20)]
|
|
static void Init()
|
|
{
|
|
EditorWindow.GetWindow<OculusBuildApp>(false, "OVR Build APK", true);
|
|
OnBuildComplete();
|
|
}
|
|
|
|
[MenuItem("Oculus/OVR Build/OVR Build APK And Run %k", false, 21)]
|
|
static void InitAndRun()
|
|
{
|
|
var window = EditorWindow.GetWindow<OculusBuildApp>(false, "OVR Build APK", true);
|
|
isRunOnDevice = true; // make sure the "And Run" part of the menu name is true
|
|
window.StartBuild();
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
isRunOnDevice = EditorPrefs.GetBool("OVRBuild_RunOnDevice", false);
|
|
saveKeystorePasswords = EditorPrefs.GetBool("OVRBuild_SaveKeystorePasswords", false);
|
|
if (saveKeystorePasswords)
|
|
{
|
|
if (EditorPrefs.HasKey("OVRBuild_KeystorePassword"))
|
|
PlayerSettings.Android.keystorePass = EditorPrefs.GetString("OVRBuild_KeystorePassword");
|
|
if (EditorPrefs.HasKey("OVRBuild_KeyAliasPassword"))
|
|
PlayerSettings.Android.keyaliasPass = EditorPrefs.GetString("OVRBuild_KeyAliasPassword");
|
|
}
|
|
outputApkPath = EditorPrefs.GetString("OVRBuild_BuiltAPKPath", "");
|
|
|
|
CheckADBDevices(out currConnectedDevice);
|
|
}
|
|
|
|
private void OnDisable()
|
|
{
|
|
EditorPrefs.SetBool("OVRBuild_RunOnDevice", isRunOnDevice);
|
|
EditorPrefs.SetBool("OVRBuild_SaveKeystorePasswords", saveKeystorePasswords);
|
|
if (saveKeystorePasswords)
|
|
{
|
|
EditorPrefs.SetString("OVRBuild_KeystorePassword", PlayerSettings.Android.keystorePass);
|
|
EditorPrefs.SetString("OVRBuild_KeyAliasPassword", PlayerSettings.Android.keyaliasPass);
|
|
}
|
|
else
|
|
{
|
|
EditorPrefs.DeleteKey("OVRBuild_KeystorePassword");
|
|
EditorPrefs.DeleteKey("OVRBuild_KeyAliasPassword");
|
|
}
|
|
EditorPrefs.SetString("OVRBuild_BuiltAPKPath", outputApkPath);
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (windowStyle == null)
|
|
{
|
|
windowStyle = new GUIStyle();
|
|
windowStyle.margin = new RectOffset(10, 10, 10, 10);
|
|
}
|
|
|
|
if (calloutStyle == null)
|
|
{
|
|
calloutStyle = new GUIStyle(EditorStyles.label);
|
|
calloutStyle.richText = true;
|
|
calloutStyle.wordWrap = true;
|
|
}
|
|
|
|
// Fix progress bar window size
|
|
minSize = new Vector2(500, 305);
|
|
|
|
float oldLabelWidth = EditorGUIUtility.labelWidth;
|
|
EditorGUIUtility.labelWidth = 160;
|
|
|
|
EditorGUILayout.BeginVertical(windowStyle);
|
|
|
|
GUILayout.BeginHorizontal(EditorStyles.helpBox);
|
|
GUILayout.BeginVertical();
|
|
EditorGUILayout.LabelField("Builds created in the <b>OVR Build APK</b> window are identical to Unity-built APKs, but use the Gradle cache to only touch changed files, resulting in shorter build times.",
|
|
calloutStyle);
|
|
|
|
#if UNITY_2021_1_OR_NEWER
|
|
if (EditorGUILayout.LinkButton("Documentation"))
|
|
#else
|
|
if (GUILayout.Button("Documentation", GUILayout.ExpandWidth(false)))
|
|
#endif
|
|
{
|
|
Application.OpenURL("https://developer.oculus.com/documentation/unity/unity-build-android-tools/");
|
|
}
|
|
GUILayout.EndVertical();
|
|
GUILayout.EndHorizontal();
|
|
EditorGUILayout.Space(15f);
|
|
|
|
scrollViewPos = EditorGUILayout.BeginScrollView(scrollViewPos, GUIStyle.none, GUI.skin.verticalScrollbar);
|
|
using (new EditorGUI.DisabledScope(buildInProgress))
|
|
{
|
|
EditorGUILayout.BeginHorizontal();
|
|
outputApkPath = EditorGUILayout.TextField("Built APK Path", outputApkPath);
|
|
if (GUILayout.Button("Browse...", GUILayout.Width(80)))
|
|
DisplayAPKPathDialog();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
|
|
PlayerSettings.Android.bundleVersionCode = EditorGUILayout.IntField(new GUIContent("Version Number",
|
|
"Builds uploaded to the Oculus storefront are required to have incrementing version numbers.\nThis value is exposed to players."),
|
|
PlayerSettings.Android.bundleVersionCode, GUILayout.Width(220));
|
|
|
|
EditorGUI.BeginChangeCheck();
|
|
bool isAutoIncrement = PlayerPrefs.GetInt(OVRGradleGeneration.prefName, 0) != 0;
|
|
isAutoIncrement = EditorGUILayout.ToggleLeft(new GUIContent("Auto-Increment?",
|
|
"If true, version number will be automatically incremented after every successful build."),
|
|
isAutoIncrement, EditorStyles.miniLabel, GUILayout.Width(120));
|
|
if (EditorGUI.EndChangeCheck())
|
|
OVRGradleGeneration.ToggleUtilities();
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorGUILayout.Space(15f);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
using (new EditorGUI.DisabledScope(string.IsNullOrEmpty(currConnectedDevice)))
|
|
{
|
|
isRunOnDevice = EditorGUILayout.Toggle("Install & Run on Device?", isRunOnDevice);
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.LabelField(string.IsNullOrEmpty(currConnectedDevice) ? "No device connected" : $"Device: {currConnectedDevice}");
|
|
}
|
|
if (GUILayout.Button("Refresh", GUILayout.Width(80)))
|
|
CheckADBDevices(out currConnectedDevice);
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
EditorUserBuildSettings.development = EditorGUILayout.Toggle(new GUIContent("Development Build?",
|
|
"Development builds allow you to debug scripts. However, they're slightly slower, and they're not allowed on the Oculus storefront."),
|
|
EditorUserBuildSettings.development);
|
|
|
|
EditorGUILayout.Space(15f);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
saveKeystorePasswords = EditorGUILayout.Toggle(new GUIContent("Save Keystore Passwords?",
|
|
"These values are also found in Project Settings > Player > [Android] > Publishing Settings > Project Keystore.\nStoring passwords is convenient, but reduces security."),
|
|
saveKeystorePasswords);
|
|
if (GUILayout.Button("Select Keystore...", GUILayout.Width(150)))
|
|
SettingsService.OpenProjectSettings("Project/Player");
|
|
EditorGUILayout.EndHorizontal();
|
|
|
|
if (saveKeystorePasswords)
|
|
{
|
|
EditorGUI.indentLevel++;
|
|
|
|
EditorGUILayout.LabelField("Keystore Path", PlayerSettings.Android.keystoreName);
|
|
PlayerSettings.Android.keystorePass = EditorGUILayout.PasswordField("Keystore Password", PlayerSettings.Android.keystorePass);
|
|
|
|
EditorGUILayout.LabelField("Key Alias Name", PlayerSettings.Android.keyaliasName);
|
|
PlayerSettings.Android.keyaliasPass = EditorGUILayout.PasswordField("Alias Password", PlayerSettings.Android.keyaliasPass);
|
|
|
|
EditorGUI.indentLevel--;
|
|
}
|
|
}
|
|
EditorGUILayout.EndScrollView();
|
|
EditorGUILayout.Space(10);
|
|
|
|
EditorGUILayout.BeginHorizontal();
|
|
GUILayout.FlexibleSpace();
|
|
|
|
//better to perform these at end of GUI
|
|
bool shouldCancel = false;
|
|
bool shouldBuild = false;
|
|
if (showCancel)
|
|
shouldCancel = GUILayout.Button("Cancel", GUILayout.Height(30), GUILayout.Width(100));
|
|
else
|
|
{
|
|
using (new EditorGUI.DisabledScope(buildInProgress))
|
|
{
|
|
shouldBuild = GUILayout.Button("Build", GUILayout.Height(30), GUILayout.Width(100));
|
|
}
|
|
}
|
|
GUILayout.FlexibleSpace();
|
|
EditorGUILayout.EndHorizontal();
|
|
EditorGUILayout.Space(10);
|
|
|
|
// Show progress bar
|
|
Rect progressRect = EditorGUILayout.GetControlRect(GUILayout.Height(25));
|
|
float progress = currentStep / (float)totalBuildSteps;
|
|
EditorGUI.ProgressBar(progressRect, progress, progressMessage);
|
|
|
|
EditorGUIUtility.labelWidth = oldLabelWidth;
|
|
EditorGUILayout.EndVertical();
|
|
|
|
if (shouldBuild)
|
|
StartBuild();
|
|
else if (shouldCancel)
|
|
CancelBuild();
|
|
}
|
|
|
|
void Update()
|
|
{
|
|
// Force window update if not in focus to ensure progress bar still updates
|
|
var window = EditorWindow.focusedWindow;
|
|
if (window != null && window.ToString().Contains("OculusBuildApp"))
|
|
{
|
|
Repaint();
|
|
}
|
|
}
|
|
|
|
void DisplayAPKPathDialog()
|
|
{
|
|
string fileName = "build.apk";
|
|
string path = "";
|
|
if (!string.IsNullOrEmpty(outputApkPath))
|
|
{
|
|
try
|
|
{
|
|
path = Path.GetDirectoryName(outputApkPath);
|
|
fileName = Path.GetFileName(outputApkPath);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
//do nothing, we just have a malformed apkPath and should accept defaults
|
|
}
|
|
}
|
|
|
|
outputApkPath = EditorUtility.SaveFilePanel("APK Path", path, fileName, "apk");
|
|
Repaint();
|
|
}
|
|
|
|
void CancelBuild()
|
|
{
|
|
SetProgressBarMessage("Canceling . . .");
|
|
|
|
if (syncCancelToken != null)
|
|
{
|
|
syncCancelToken.Cancel();
|
|
}
|
|
|
|
if (apkOutputSuccessful.HasValue && apkOutputSuccessful.Value)
|
|
{
|
|
buildThread.Abort();
|
|
OnBuildComplete();
|
|
}
|
|
|
|
if (gradleBuildProcess != null && !gradleBuildProcess.HasExited)
|
|
{
|
|
var cancelThread = new Thread(delegate ()
|
|
{
|
|
CancelGradleBuild();
|
|
});
|
|
cancelThread.Start();
|
|
}
|
|
}
|
|
|
|
void CancelGradleBuild()
|
|
{
|
|
Process cancelGradleProcess = new Process();
|
|
string arguments = "-Xmx1024m -classpath \"" + gradlePath +
|
|
"\" org.gradle.launcher.GradleMain --stop";
|
|
var processInfo = new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
|
|
FileName = jdkPath,
|
|
Arguments = arguments,
|
|
RedirectStandardInput = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardError = true,
|
|
RedirectStandardOutput = true,
|
|
};
|
|
|
|
cancelGradleProcess.StartInfo = processInfo;
|
|
cancelGradleProcess.EnableRaisingEvents = true;
|
|
|
|
cancelGradleProcess.OutputDataReceived += new DataReceivedEventHandler(
|
|
(s, e) =>
|
|
{
|
|
if (e != null && e.Data != null && e.Data.Length != 0)
|
|
{
|
|
UnityEngine.Debug.LogFormat("Gradle: {0}", e.Data);
|
|
}
|
|
}
|
|
);
|
|
|
|
apkOutputSuccessful = false;
|
|
|
|
cancelGradleProcess.Start();
|
|
cancelGradleProcess.BeginOutputReadLine();
|
|
cancelGradleProcess.WaitForExit();
|
|
|
|
OnBuildComplete();
|
|
}
|
|
|
|
public void StartBuild()
|
|
{
|
|
showCancel = false;
|
|
buildInProgress = true;
|
|
|
|
InitializeProgressBar(NUM_BUILD_AND_RUN_STEPS);
|
|
IncrementProgressBar("Exporting Unity Project . . .");
|
|
|
|
apkOutputSuccessful = null;
|
|
syncCancelToken = null;
|
|
gradleBuildProcess = null;
|
|
|
|
UnityEngine.Debug.Log("OVRBuild: Starting Unity build ...");
|
|
|
|
SetupDirectories();
|
|
|
|
// 1. Get scenes to build in Unity, and export gradle project
|
|
var buildResult = UnityBuildPlayer();
|
|
|
|
if (buildResult.summary.result == UnityEditor.Build.Reporting.BuildResult.Succeeded)
|
|
{
|
|
// Set static variables so build thread has updated data
|
|
showCancel = true;
|
|
gradlePath = OVRConfig.Instance.GetGradlePath();
|
|
jdkPath = OVRConfig.Instance.GetJDKPath();
|
|
androidSdkPath = OVRConfig.Instance.GetAndroidSDKPath();
|
|
applicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
|
|
isDevelopmentBuild = EditorUserBuildSettings.development;
|
|
#if UNITY_2019_3_OR_NEWER
|
|
productName = "launcher";
|
|
#else
|
|
productName = Application.productName;
|
|
#endif
|
|
dataPath = Application.dataPath;
|
|
|
|
buildThread = new Thread(delegate ()
|
|
{
|
|
OVRBuildRun();
|
|
});
|
|
buildThread.Start();
|
|
return;
|
|
}
|
|
else if (buildResult.summary.result == UnityEditor.Build.Reporting.BuildResult.Cancelled)
|
|
{
|
|
UnityEngine.Debug.Log("Build cancelled.");
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Debug.Log("Build failed.");
|
|
}
|
|
OnBuildComplete();
|
|
}
|
|
|
|
private UnityEditor.Build.Reporting.BuildReport UnityBuildPlayer()
|
|
{
|
|
// Unity introduced a possible bug with Unity 2020.1.10f1 that causes an exception
|
|
// when building with the option 'BuildOptions.AcceptExternalModificationsToPlayer'
|
|
// In order to maintain the same logic, we are using `EditorUserBuildSettings.exportAsGoogleAndroidProject`
|
|
#if DONT_USE_BUILD_OPTIONS_EXTERNAL_MODIFICATIONS_FLAG
|
|
bool previousExportAsGoogleAndroidProject = EditorUserBuildSettings.exportAsGoogleAndroidProject;
|
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = true;
|
|
#endif
|
|
try
|
|
{
|
|
var sceneList = GetScenesToBuild();
|
|
|
|
var buildOptions = BuildOptions.None;
|
|
if (isDevelopmentBuild)
|
|
buildOptions |= (BuildOptions.Development | BuildOptions.AllowDebugging);
|
|
if (isRunOnDevice)
|
|
buildOptions |= BuildOptions.AutoRunPlayer;
|
|
#if !DONT_USE_BUILD_OPTIONS_EXTERNAL_MODIFICATIONS_FLAG
|
|
buildOptions |= BuildOptions.AcceptExternalModificationsToPlayer;
|
|
#endif
|
|
|
|
var buildPlayerOptions = new BuildPlayerOptions
|
|
{
|
|
scenes = sceneList.ToArray(),
|
|
locationPathName = gradleTempExport,
|
|
target = BuildTarget.Android,
|
|
options = buildOptions
|
|
};
|
|
|
|
var buildResult = BuildPipeline.BuildPlayer(buildPlayerOptions);
|
|
|
|
UnityEngine.Debug.Log(UnityBuildPlayerSummary(buildResult));
|
|
|
|
return buildResult;
|
|
}
|
|
finally
|
|
{
|
|
#if DONT_USE_BUILD_OPTIONS_EXTERNAL_MODIFICATIONS_FLAG
|
|
EditorUserBuildSettings.exportAsGoogleAndroidProject = previousExportAsGoogleAndroidProject;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
private static string UnityBuildPlayerSummary(UnityEditor.Build.Reporting.BuildReport report)
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
|
|
sb.Append($"Unity Build Player: Build {report.summary.result} ({report.summary.totalSize} bytes) in {report.summary.totalTime.TotalSeconds:0.00}s");
|
|
|
|
foreach (var step in report.steps)
|
|
{
|
|
sb.AppendLine();
|
|
if (step.depth > 0)
|
|
{
|
|
sb.Append(new String('-', step.depth));
|
|
sb.Append(' ');
|
|
}
|
|
sb.Append($"{step.name}: {step.duration:g}");
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
|
|
private static void OVRBuildRun()
|
|
{
|
|
// 2. Process gradle project
|
|
IncrementProgressBar("Processing gradle project . . .");
|
|
if (ProcessGradleProject())
|
|
{
|
|
// 3. Build gradle project
|
|
IncrementProgressBar("Starting gradle build . . .");
|
|
if (BuildGradleProject())
|
|
{
|
|
CopyAPK();
|
|
|
|
// 4. Deploy and run
|
|
if (isRunOnDevice)
|
|
DeployAPK();
|
|
}
|
|
}
|
|
|
|
OnBuildComplete();
|
|
}
|
|
|
|
private static bool BuildGradleProject()
|
|
{
|
|
gradleBuildProcess = new Process();
|
|
string arguments = "-Xmx4096m -classpath \"" + gradlePath + "\" org.gradle.launcher.GradleMain --profile ";
|
|
if (isDevelopmentBuild)
|
|
arguments += "assembleDebug -x validateSigningDebug";
|
|
else
|
|
arguments += "assembleRelease -x verifyReleaseResources";
|
|
|
|
#if UNITY_2019_3_OR_NEWER
|
|
var gradleProjectPath = gradleExport;
|
|
#else
|
|
var gradleProjectPath = Path.Combine(gradleExport, productName);
|
|
#endif
|
|
|
|
var processInfo = new System.Diagnostics.ProcessStartInfo
|
|
{
|
|
WorkingDirectory = gradleProjectPath,
|
|
WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal,
|
|
FileName = jdkPath,
|
|
Arguments = arguments,
|
|
RedirectStandardInput = true,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardError = true,
|
|
RedirectStandardOutput = true,
|
|
};
|
|
|
|
gradleBuildProcess.StartInfo = processInfo;
|
|
gradleBuildProcess.EnableRaisingEvents = true;
|
|
|
|
DateTime gradleStartTime = System.DateTime.Now;
|
|
DateTime gradleEndTime = System.DateTime.MinValue;
|
|
|
|
gradleBuildProcess.Exited += new System.EventHandler(
|
|
(s, e) =>
|
|
{
|
|
UnityEngine.Debug.Log("Gradle: Exited");
|
|
}
|
|
);
|
|
|
|
gradleBuildProcess.OutputDataReceived += new DataReceivedEventHandler(
|
|
(s, e) =>
|
|
{
|
|
if (e != null && e.Data != null &&
|
|
e.Data.Length != 0 &&
|
|
(e.Data.Contains("BUILD") || e.Data.StartsWith("See the profiling report at:")))
|
|
{
|
|
UnityEngine.Debug.LogFormat("Gradle: {0}", e.Data);
|
|
if (e.Data.Contains("SUCCESSFUL"))
|
|
{
|
|
string buildFlavor = isDevelopmentBuild ? "debug" : "release";
|
|
UnityEngine.Debug.LogFormat("APK Build Completed: {0}",
|
|
Path.Combine(gradleExport, $"build\\outputs\\apk\\{buildFlavor}", productName + $"-{buildFlavor}.apk").Replace("/", "\\"));
|
|
if (!apkOutputSuccessful.HasValue)
|
|
{
|
|
apkOutputSuccessful = true;
|
|
}
|
|
gradleEndTime = System.DateTime.Now;
|
|
}
|
|
else if (e.Data.Contains("FAILED"))
|
|
{
|
|
apkOutputSuccessful = false;
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
gradleBuildProcess.ErrorDataReceived += new DataReceivedEventHandler(
|
|
(s, e) =>
|
|
{
|
|
if (e != null && e.Data != null &&
|
|
e.Data.Length != 0)
|
|
{
|
|
UnityEngine.Debug.LogErrorFormat("Gradle: {0}", e.Data);
|
|
}
|
|
apkOutputSuccessful = false;
|
|
}
|
|
);
|
|
|
|
gradleBuildProcess.Start();
|
|
gradleBuildProcess.BeginOutputReadLine();
|
|
IncrementProgressBar("Building gradle project . . .");
|
|
|
|
gradleBuildProcess.WaitForExit();
|
|
|
|
// Add a timeout for if gradle unexpectedlly exits or errors out
|
|
Stopwatch timeout = new Stopwatch();
|
|
timeout.Start();
|
|
while (apkOutputSuccessful == null)
|
|
{
|
|
if (timeout.ElapsedMilliseconds > 5000)
|
|
{
|
|
UnityEngine.Debug.LogError("Gradle has exited unexpectedly.");
|
|
apkOutputSuccessful = false;
|
|
}
|
|
System.Threading.Thread.Sleep(100);
|
|
}
|
|
|
|
return apkOutputSuccessful.HasValue && apkOutputSuccessful.Value;
|
|
}
|
|
|
|
private static bool ProcessGradleProject()
|
|
{
|
|
DateTime syncStartTime = System.DateTime.Now;
|
|
DateTime syncEndTime = System.DateTime.MinValue;
|
|
|
|
try
|
|
{
|
|
var ps = System.Text.RegularExpressions.Regex.Escape("" + Path.DirectorySeparatorChar);
|
|
// ignore files .gradle/** build/** foo/.gradle/** and bar/build/**
|
|
var ignorePattern = string.Format("^([^{0}]+{0})?(\\.gradle|build){0}", ps);
|
|
|
|
var syncer = new DirectorySyncer(gradleTempExport,
|
|
gradleExport, ignorePattern);
|
|
|
|
syncCancelToken = new DirectorySyncer.CancellationTokenSource();
|
|
var syncResult = syncer.Synchronize(syncCancelToken.Token);
|
|
syncEndTime = System.DateTime.Now;
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
UnityEngine.Debug.Log("OVRBuild: Processing gradle project failed with exception: " +
|
|
e.Message);
|
|
return false;
|
|
}
|
|
|
|
if (syncCancelToken.IsCancellationRequested)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private static List<string> GetScenesToBuild()
|
|
{
|
|
var sceneList = new List<string>();
|
|
foreach (var scene in EditorBuildSettings.scenes)
|
|
{
|
|
// Enumerate scenes in project and check if scene is enabled to build
|
|
if (scene.enabled)
|
|
{
|
|
sceneList.Add(scene.path);
|
|
}
|
|
}
|
|
return sceneList;
|
|
}
|
|
|
|
public static bool CopyAPK()
|
|
{
|
|
string buildFlavor = isDevelopmentBuild ? "debug" : "release";
|
|
string apkPathLocal = Path.Combine(gradleExport, productName, $"build\\outputs\\apk\\{buildFlavor}", productName + $"-{buildFlavor}.apk");
|
|
if (File.Exists(apkPathLocal))
|
|
{
|
|
try
|
|
{
|
|
File.Copy(apkPathLocal, outputApkPath, true);
|
|
UnityEngine.Debug.Log($"OVRBuild: Output APK generated at {outputApkPath}");
|
|
Process.Start("explorer.exe", Path.GetDirectoryName(outputApkPath));
|
|
return true;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public static bool DeployAPK()
|
|
{
|
|
// Create new instance of ADB Tool
|
|
var adbTool = new OVRADBTool(androidSdkPath);
|
|
|
|
if (adbTool.isReady)
|
|
{
|
|
string apkPathLocal;
|
|
string buildFlavor = isDevelopmentBuild ? "debug" : "release";
|
|
string gradleExportFolder = Path.Combine(gradleExport, productName, $"build\\outputs\\apk\\{buildFlavor}");
|
|
|
|
// Check to see if gradle output directory exists
|
|
gradleExportFolder = gradleExportFolder.Replace("/", "\\");
|
|
if (!Directory.Exists(gradleExportFolder))
|
|
{
|
|
UnityEngine.Debug.LogError("Could not find the gradle project at the expected path: " + gradleExportFolder);
|
|
return false;
|
|
}
|
|
|
|
// Search for output APK in gradle output directory
|
|
apkPathLocal = Path.Combine(gradleExportFolder, productName + $"-{buildFlavor}.apk");
|
|
if (!System.IO.File.Exists(apkPathLocal))
|
|
{
|
|
UnityEngine.Debug.LogError(string.Format("Could not find {0} in the gradle project.", productName + $"-{buildFlavor}.apk"));
|
|
return false;
|
|
}
|
|
|
|
string output, error;
|
|
DateTime timerStart;
|
|
|
|
// Ensure that the Oculus temp directory is on the device by making it
|
|
IncrementProgressBar("Making Temp directory on device");
|
|
string[] mkdirCommand = { "-d shell", "mkdir -p", REMOTE_APK_PATH };
|
|
if (adbTool.RunCommand(mkdirCommand, null, out output, out error) != 0) return false;
|
|
|
|
// Push APK to device, also time how long it takes
|
|
timerStart = System.DateTime.Now;
|
|
IncrementProgressBar("Pushing APK to device . . .");
|
|
string[] pushCommand = { "-d push", "\"" + apkPathLocal + "\"", REMOTE_APK_PATH };
|
|
if (adbTool.RunCommand(pushCommand, null, out output, out error) != 0) return false;
|
|
|
|
// Calculate the transfer speed and determine if user is using USB 2.0 or 3.0
|
|
// Only bother informing the user on non-trivial transfers, as for very short
|
|
// periods of time, things like process creation overhead can dwarf the actual
|
|
// transfer time.
|
|
TimeSpan pushTime = System.DateTime.Now - timerStart;
|
|
bool trivialPush = pushTime.TotalSeconds < TRANSFER_SPEED_CHECK_THRESHOLD;
|
|
long? apkSize = (trivialPush ? (long?)null : new System.IO.FileInfo(apkPathLocal).Length);
|
|
double? transferSpeed = (apkSize / pushTime.TotalSeconds) / BYTES_TO_MEGABYTES;
|
|
bool informLog = transferSpeed.HasValue && transferSpeed.Value < USB_TRANSFER_SPEED_THRES;
|
|
UnityEngine.Debug.Log("OVRADBTool: Push Success");
|
|
|
|
// Install the APK package on the device
|
|
IncrementProgressBar("Installing APK . . .");
|
|
string apkPath = REMOTE_APK_PATH + "/" + productName + "-debug.apk";
|
|
apkPath = apkPath.Replace(" ", "\\ ");
|
|
string[] installCommand = { "-d shell", "pm install -r", apkPath };
|
|
|
|
timerStart = System.DateTime.Now;
|
|
if (adbTool.RunCommand(installCommand, null, out output, out error) != 0) return false;
|
|
TimeSpan installTime = System.DateTime.Now - timerStart;
|
|
UnityEngine.Debug.Log("OVRADBTool: Install Success");
|
|
|
|
// Start the application on the device
|
|
IncrementProgressBar("Launching application on device . . .");
|
|
#if UNITY_2019_3_OR_NEWER
|
|
string playerActivityName = "\"" + applicationIdentifier + "/com.unity3d.player.UnityPlayerActivity\"";
|
|
#else
|
|
string playerActivityName = "\"" + applicationIdentifier + "/" + applicationIdentifier + ".UnityPlayerActivity\"";
|
|
#endif
|
|
string[] appStartCommand = { "-d shell", "am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -S -f 0x10200000 -n", playerActivityName };
|
|
if (adbTool.RunCommand(appStartCommand, null, out output, out error) != 0) return false;
|
|
UnityEngine.Debug.Log("OVRADBTool: Application Start Success");
|
|
|
|
IncrementProgressBar("Success!");
|
|
|
|
// If the user is using a USB 2.0 cable, inform them about improved transfer speeds and estimate time saved
|
|
if (informLog)
|
|
{
|
|
float usb3Time = apkSize.Value / (USB_3_TRANSFER_SPEED * BYTES_TO_MEGABYTES); // `informLog` can't be true if `apkSize` is null.
|
|
UnityEngine.Debug.Log(string.Format("OVRBuild has detected slow transfer speeds. A USB 3.0 cable is recommended to reduce the time it takes to deploy your project by approximatly {0:0.0} seconds", pushTime.TotalSeconds - usb3Time));
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Debug.LogError("Could not find the ADB executable in the specified Android SDK directory.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private static void OnBuildComplete()
|
|
{
|
|
showCancel = false;
|
|
buildInProgress = false;
|
|
currentStep = 0;
|
|
SetProgressBarMessage("Waiting to build . . .", false);
|
|
}
|
|
|
|
private static bool CheckADBDevices(out string connectedDeviceName)
|
|
{
|
|
// Check if there are any ADB devices connected before starting the build process
|
|
var adbTool = new OVRADBTool(OVRConfig.Instance.GetAndroidSDKPath());
|
|
connectedDeviceName = null;
|
|
|
|
if (adbTool.isReady)
|
|
{
|
|
List<string> devices = adbTool.GetDevices();
|
|
if (devices.Count == 0)
|
|
{
|
|
UnityEngine.Debug.LogError("No ADB devices connected. Connect a device to this computer to run APK.");
|
|
return false;
|
|
}
|
|
else if (devices.Count > 1)
|
|
{
|
|
UnityEngine.Debug.LogError("Multiple ADB devices connected. Disconnect extra devices from this computer to run APK.");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
connectedDeviceName = devices[0];
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UnityEngine.Debug.LogError("OVR ADB Tool failed to initialize. Check the Android SDK path in [Edit -> Preferences -> External Tools]");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static void SetupDirectories()
|
|
{
|
|
gradleTempExport = Path.Combine(Path.Combine(Application.dataPath, "../Temp"), "OVRGradleTempExport");
|
|
gradleExport = Path.Combine(Path.Combine(Application.dataPath, "../Temp"), "OVRGradleExport");
|
|
if (!Directory.Exists(gradleExport))
|
|
{
|
|
Directory.CreateDirectory(gradleExport);
|
|
}
|
|
}
|
|
|
|
private static void InitializeProgressBar(int stepCount)
|
|
{
|
|
currentStep = 0;
|
|
totalBuildSteps = stepCount;
|
|
}
|
|
|
|
private static void IncrementProgressBar(string message)
|
|
{
|
|
currentStep++;
|
|
progressMessage = message;
|
|
UnityEngine.Debug.Log("OVRBuild: " + message);
|
|
}
|
|
|
|
private static void SetProgressBarMessage(string message, bool log = true)
|
|
{
|
|
progressMessage = message;
|
|
if (log)
|
|
UnityEngine.Debug.Log("OVRBuild: " + message);
|
|
}
|
|
#endif //UNITY_EDITOR_WIN && UNITY_ANDROID
|
|
}
|