378 lines
15 KiB
C#
378 lines
15 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using TH1_Logic.Editor.HybridCLR;
|
|
using TH1_Logic.Editor.YooAssetTools;
|
|
using UnityEditor;
|
|
using UnityEditor.Build;
|
|
using UnityEditor.Build.Reporting;
|
|
using UnityEngine;
|
|
using DiagnosticsProcess = System.Diagnostics.Process;
|
|
using DiagnosticsProcessStartInfo = System.Diagnostics.ProcessStartInfo;
|
|
|
|
namespace TH1_Logic.Editor
|
|
{
|
|
public static class TH1MigrationCommandLine
|
|
{
|
|
private const string BuildDirArg = "-th1SmokeBuildDir";
|
|
private const string DefaultBuildSubDirectory = "TH1/CodexSmoke";
|
|
private const string ExeName = "TOHOTOPIA.exe";
|
|
|
|
[MenuItem("Tools/TH1/iOS Migration/Command Line/Prepare And Build Windows Smoke")]
|
|
public static void PrepareAndBuildWindowsSmoke()
|
|
{
|
|
RunBatchAction(() =>
|
|
{
|
|
ConfigureWindowsIl2CppSmokeSettings();
|
|
PrepareCurrentPlatform(true);
|
|
|
|
var exePath = BuildWindowsSmokePlayer();
|
|
Debug.Log($"[TH1.Migration.CLI] Windows smoke build OK: {exePath}");
|
|
});
|
|
}
|
|
|
|
[MenuItem("Tools/TH1/iOS Migration/Command Line/Prepare Current Platform")]
|
|
public static void PrepareCurrentPlatformMenu()
|
|
{
|
|
RunBatchAction(() => PrepareCurrentPlatform(true));
|
|
}
|
|
|
|
public static void PrepareCurrentPlatform(bool developmentBuild)
|
|
{
|
|
AssetDatabase.SaveAssets();
|
|
|
|
TH1HybridCLRBuildTools.ConfigureHotfixSettings();
|
|
TH1YooAssetBuildTools.ConfigureDefaultPackageCollector();
|
|
TH1HybridCLRBuildTools.GenerateAll();
|
|
|
|
if (!TH1HybridCLRBuildTools.BuildAndCopyHotfixArtifacts(developmentBuild))
|
|
{
|
|
throw new BuildFailedException("[TH1.Migration.CLI] Build hotfix dll failed.");
|
|
}
|
|
|
|
TH1YooAssetBuildTools.BuildBuiltinDefaultPackage();
|
|
AssetDatabase.SaveAssets();
|
|
AssetDatabase.Refresh();
|
|
|
|
var blockers = TH1MigrationBuildStatus.GetBuildBlockingMessages(EditorUserBuildSettings.activeBuildTarget);
|
|
if (blockers.Count > 0)
|
|
{
|
|
throw new BuildFailedException(
|
|
"[TH1.Migration.CLI] Build preparation still has blocking errors:\n" +
|
|
string.Join("\n", blockers));
|
|
}
|
|
|
|
Debug.Log("[TH1.Migration.CLI] Prepare current platform OK.");
|
|
}
|
|
|
|
private static void ConfigureWindowsIl2CppSmokeSettings()
|
|
{
|
|
const BuildTargetGroup group = BuildTargetGroup.Standalone;
|
|
const BuildTarget target = BuildTarget.StandaloneWindows64;
|
|
|
|
if (EditorUserBuildSettings.activeBuildTarget != target)
|
|
{
|
|
EditorUserBuildSettings.SwitchActiveBuildTarget(group, target);
|
|
}
|
|
|
|
PlayerSettings.SetScriptingBackend(group, ScriptingImplementation.IL2CPP);
|
|
EditorUserBuildSettings.development = true;
|
|
EditorUserBuildSettings.allowDebugging = false;
|
|
PlayerSettings.usePlayerLog = true;
|
|
PlayerSettings.SetStackTraceLogType(LogType.Log, StackTraceLogType.ScriptOnly);
|
|
PlayerSettings.SetStackTraceLogType(LogType.Warning, StackTraceLogType.ScriptOnly);
|
|
PlayerSettings.SetStackTraceLogType(LogType.Error, StackTraceLogType.ScriptOnly);
|
|
PlayerSettings.SetStackTraceLogType(LogType.Assert, StackTraceLogType.ScriptOnly);
|
|
PlayerSettings.SetStackTraceLogType(LogType.Exception, StackTraceLogType.ScriptOnly);
|
|
|
|
Debug.Log("[TH1.Migration.CLI] Configured Windows IL2CPP smoke build settings.");
|
|
}
|
|
|
|
private static string BuildWindowsSmokePlayer()
|
|
{
|
|
var outputRoot = GetSmokeBuildDirectory();
|
|
CleanSmokeBuildDirectory(outputRoot);
|
|
|
|
var outputExe = Path.Combine(outputRoot, ExeName);
|
|
var scenes = EditorBuildSettings.scenes
|
|
.Where(scene => scene.enabled)
|
|
.Select(scene => scene.path)
|
|
.ToArray();
|
|
|
|
if (scenes.Length == 0)
|
|
{
|
|
throw new BuildFailedException("[TH1.Migration.CLI] No enabled scenes in EditorBuildSettings.");
|
|
}
|
|
|
|
var options = new BuildPlayerOptions
|
|
{
|
|
scenes = scenes,
|
|
locationPathName = outputExe,
|
|
target = BuildTarget.StandaloneWindows64,
|
|
targetGroup = BuildTargetGroup.Standalone,
|
|
options = BuildOptions.Development
|
|
};
|
|
|
|
var report = BuildPipeline.BuildPlayer(options);
|
|
var summary = report.summary;
|
|
if (summary.result != BuildResult.Succeeded)
|
|
{
|
|
throw new BuildFailedException(
|
|
$"[TH1.Migration.CLI] Build failed: {summary.result}, errors={summary.totalErrors}, warnings={summary.totalWarnings}");
|
|
}
|
|
|
|
var playerExe = FindPlayerExecutable(outputRoot, outputExe);
|
|
if (string.IsNullOrEmpty(playerExe))
|
|
{
|
|
BuildExportedVisualStudioSolution(outputRoot);
|
|
playerExe = FindPlayerExecutable(outputRoot, outputExe);
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(playerExe))
|
|
{
|
|
throw new BuildFailedException(
|
|
$"[TH1.Migration.CLI] Build succeeded but no runnable player exe was found under {outputRoot}.");
|
|
}
|
|
|
|
return playerExe;
|
|
}
|
|
|
|
private static string GetSmokeBuildDirectory()
|
|
{
|
|
var argValue = GetCommandLineValue(BuildDirArg);
|
|
if (!string.IsNullOrEmpty(argValue))
|
|
{
|
|
return Path.GetFullPath(argValue);
|
|
}
|
|
|
|
return Path.GetFullPath(Path.Combine(GetProjectRoot(), DefaultBuildSubDirectory));
|
|
}
|
|
|
|
private static string FindPlayerExecutable(string outputRoot, string expectedExe)
|
|
{
|
|
if (File.Exists(expectedExe)) return expectedExe;
|
|
if (!Directory.Exists(outputRoot)) return string.Empty;
|
|
|
|
var exeFiles = Directory.GetFiles(outputRoot, "*.exe", SearchOption.AllDirectories)
|
|
.Where(path =>
|
|
{
|
|
if (path.IndexOf("Il2CppOutputProject", StringComparison.OrdinalIgnoreCase) >= 0) return false;
|
|
var fileName = Path.GetFileName(path);
|
|
if (fileName.IndexOf("CrashHandler", StringComparison.OrdinalIgnoreCase) >= 0) return false;
|
|
if (fileName.Equals("il2cpp.exe", StringComparison.OrdinalIgnoreCase)) return false;
|
|
if (fileName.Equals("UnityLinker.exe", StringComparison.OrdinalIgnoreCase)) return false;
|
|
if (fileName.Equals("bee_backend.exe", StringComparison.OrdinalIgnoreCase)) return false;
|
|
if (fileName.Equals("Analytics.exe", StringComparison.OrdinalIgnoreCase)) return false;
|
|
if (fileName.Equals("createdump.exe", StringComparison.OrdinalIgnoreCase)) return false;
|
|
return true;
|
|
})
|
|
.OrderBy(path => path.Length)
|
|
.ToArray();
|
|
|
|
return exeFiles.FirstOrDefault() ?? string.Empty;
|
|
}
|
|
|
|
private static void BuildExportedVisualStudioSolution(string outputRoot)
|
|
{
|
|
var solutionPath = Directory.GetFiles(outputRoot, "*.sln", SearchOption.TopDirectoryOnly)
|
|
.OrderBy(path => path.Length)
|
|
.FirstOrDefault();
|
|
if (string.IsNullOrEmpty(solutionPath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var msBuildPath = FindMSBuildPath();
|
|
if (string.IsNullOrEmpty(msBuildPath))
|
|
{
|
|
throw new BuildFailedException(
|
|
$"[TH1.Migration.CLI] Unity exported a Visual Studio solution but MSBuild was not found: {solutionPath}");
|
|
}
|
|
|
|
Debug.Log($"[TH1.Migration.CLI] Unity exported Visual Studio solution, building it with MSBuild: {solutionPath}");
|
|
var windowsSdkVersion = FindLatestWindowsSdkVersion();
|
|
var platformToolset = FindLatestPlatformToolset(msBuildPath);
|
|
var retargetArgs = string.Empty;
|
|
if (!string.IsNullOrEmpty(windowsSdkVersion))
|
|
{
|
|
retargetArgs += $" /p:WindowsTargetPlatformVersion={windowsSdkVersion}";
|
|
}
|
|
|
|
if (!string.IsNullOrEmpty(platformToolset))
|
|
{
|
|
retargetArgs += $" /p:PlatformToolset={platformToolset}";
|
|
}
|
|
|
|
RunProcess(
|
|
msBuildPath,
|
|
$"\"{solutionPath}\" /m /p:Configuration=Debug /p:Platform=x64{retargetArgs} /verbosity:minimal",
|
|
outputRoot);
|
|
}
|
|
|
|
private static string FindMSBuildPath()
|
|
{
|
|
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
|
var vsWherePath = Path.Combine(programFilesX86, "Microsoft Visual Studio", "Installer", "vswhere.exe");
|
|
if (File.Exists(vsWherePath))
|
|
{
|
|
var output = RunProcess(vsWherePath, "-latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\\**\\Bin\\MSBuild.exe", GetProjectRoot(), false);
|
|
var path = output
|
|
.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)
|
|
.FirstOrDefault(File.Exists);
|
|
if (!string.IsNullOrEmpty(path))
|
|
{
|
|
return path;
|
|
}
|
|
}
|
|
|
|
var programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
|
|
var candidates = new[]
|
|
{
|
|
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Community", "MSBuild", "Current", "Bin", "MSBuild.exe"),
|
|
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Professional", "MSBuild", "Current", "Bin", "MSBuild.exe"),
|
|
Path.Combine(programFiles, "Microsoft Visual Studio", "2022", "Enterprise", "MSBuild", "Current", "Bin", "MSBuild.exe"),
|
|
Path.Combine(programFilesX86, "Microsoft Visual Studio", "2022", "BuildTools", "MSBuild", "Current", "Bin", "MSBuild.exe")
|
|
};
|
|
|
|
return candidates.FirstOrDefault(File.Exists) ?? string.Empty;
|
|
}
|
|
|
|
private static string FindLatestWindowsSdkVersion()
|
|
{
|
|
var programFilesX86 = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
|
|
var sdkLibRoot = Path.Combine(programFilesX86, "Windows Kits", "10", "Lib");
|
|
if (!Directory.Exists(sdkLibRoot))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return Directory.GetDirectories(sdkLibRoot)
|
|
.Select(Path.GetFileName)
|
|
.Where(name => Version.TryParse(name?.TrimEnd('.'), out _))
|
|
.OrderByDescending(name => Version.Parse(name.TrimEnd('.')))
|
|
.FirstOrDefault() ?? string.Empty;
|
|
}
|
|
|
|
private static string FindLatestPlatformToolset(string msBuildPath)
|
|
{
|
|
var current = new FileInfo(msBuildPath).Directory;
|
|
while (current != null)
|
|
{
|
|
var toolsetRoot = Path.Combine(current.FullName, "Microsoft", "VC", "v170", "Platforms", "x64", "PlatformToolsets");
|
|
if (Directory.Exists(toolsetRoot))
|
|
{
|
|
return Directory.GetDirectories(toolsetRoot)
|
|
.Select(Path.GetFileName)
|
|
.Where(name => name != null && name.StartsWith("v", StringComparison.OrdinalIgnoreCase))
|
|
.OrderByDescending(name => name)
|
|
.FirstOrDefault() ?? string.Empty;
|
|
}
|
|
|
|
current = current.Parent;
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string RunProcess(string fileName, string arguments, string workingDirectory, bool throwOnError = true)
|
|
{
|
|
var process = new DiagnosticsProcess
|
|
{
|
|
StartInfo = new DiagnosticsProcessStartInfo
|
|
{
|
|
FileName = fileName,
|
|
Arguments = arguments,
|
|
WorkingDirectory = workingDirectory,
|
|
UseShellExecute = false,
|
|
CreateNoWindow = true,
|
|
RedirectStandardOutput = true,
|
|
RedirectStandardError = true
|
|
}
|
|
};
|
|
|
|
process.Start();
|
|
var output = process.StandardOutput.ReadToEnd();
|
|
var error = process.StandardError.ReadToEnd();
|
|
process.WaitForExit();
|
|
|
|
if (!string.IsNullOrWhiteSpace(output))
|
|
{
|
|
Debug.Log(output);
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(error))
|
|
{
|
|
Debug.LogWarning(error);
|
|
}
|
|
|
|
if (throwOnError && process.ExitCode != 0)
|
|
{
|
|
throw new BuildFailedException(
|
|
$"[TH1.Migration.CLI] Process failed ({process.ExitCode}): {fileName} {arguments}\n{output}\n{error}");
|
|
}
|
|
|
|
return output;
|
|
}
|
|
|
|
private static string GetCommandLineValue(string name)
|
|
{
|
|
var args = Environment.GetCommandLineArgs();
|
|
for (var i = 0; i < args.Length - 1; i++)
|
|
{
|
|
if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return args[i + 1];
|
|
}
|
|
}
|
|
|
|
return string.Empty;
|
|
}
|
|
|
|
private static string GetProjectRoot()
|
|
{
|
|
return Directory.GetParent(Application.dataPath)?.FullName ?? Application.dataPath;
|
|
}
|
|
|
|
private static void CleanSmokeBuildDirectory(string outputRoot)
|
|
{
|
|
var fullOutputRoot = Path.GetFullPath(outputRoot);
|
|
var defaultSafeRoot = Path.GetFullPath(Path.Combine(GetProjectRoot(), "TH1"));
|
|
if (!fullOutputRoot.StartsWith(defaultSafeRoot, StringComparison.OrdinalIgnoreCase) &&
|
|
string.IsNullOrEmpty(GetCommandLineValue(BuildDirArg)))
|
|
{
|
|
throw new BuildFailedException($"[TH1.Migration.CLI] Refuse to clean unexpected smoke build dir: {fullOutputRoot}");
|
|
}
|
|
|
|
if (Directory.Exists(fullOutputRoot))
|
|
{
|
|
Directory.Delete(fullOutputRoot, true);
|
|
}
|
|
|
|
Directory.CreateDirectory(fullOutputRoot);
|
|
}
|
|
|
|
private static void RunBatchAction(System.Action action)
|
|
{
|
|
try
|
|
{
|
|
action();
|
|
if (Application.isBatchMode)
|
|
{
|
|
EditorApplication.Exit(0);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Debug.LogError($"[TH1.Migration.CLI] Failed:\n{e}");
|
|
if (Application.isBatchMode)
|
|
{
|
|
EditorApplication.Exit(1);
|
|
return;
|
|
}
|
|
|
|
throw;
|
|
}
|
|
}
|
|
}
|
|
}
|