chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Build
|
||||
{
|
||||
public enum BuildJobState
|
||||
{
|
||||
Pending,
|
||||
Building,
|
||||
Succeeded,
|
||||
Failed,
|
||||
Cancelled,
|
||||
Skipped
|
||||
}
|
||||
|
||||
public class BuildJob
|
||||
{
|
||||
public string JobId { get; }
|
||||
public BuildJobState State { get; set; } = BuildJobState.Pending;
|
||||
public BuildTarget Target { get; set; }
|
||||
public string OutputPath { get; set; }
|
||||
public DateTime StartedAt { get; set; }
|
||||
public DateTime? CompletedAt { get; set; }
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
// Extracted from BuildReport at completion to avoid retaining the heavy native object
|
||||
public double TotalSizeMb { get; set; }
|
||||
public int TotalErrors { get; set; }
|
||||
public int TotalWarnings { get; set; }
|
||||
|
||||
public BuildJob(string jobId, BuildTarget target, string outputPath)
|
||||
{
|
||||
JobId = jobId;
|
||||
Target = target;
|
||||
OutputPath = outputPath;
|
||||
}
|
||||
|
||||
public object ToStatusResponse()
|
||||
{
|
||||
var data = new Dictionary<string, object>
|
||||
{
|
||||
["job_id"] = JobId,
|
||||
["result"] = State.ToString().ToLowerInvariant(),
|
||||
["platform"] = Target.ToString(),
|
||||
["output_path"] = OutputPath
|
||||
};
|
||||
|
||||
if (StartedAt != default)
|
||||
data["started_at"] = StartedAt.ToString("O");
|
||||
|
||||
if (CompletedAt.HasValue)
|
||||
{
|
||||
data["duration_seconds"] = (CompletedAt.Value - StartedAt).TotalSeconds;
|
||||
data["completed_at"] = CompletedAt.Value.ToString("O");
|
||||
}
|
||||
|
||||
if (State == BuildJobState.Succeeded || State == BuildJobState.Failed)
|
||||
{
|
||||
data["total_size_mb"] = TotalSizeMb;
|
||||
data["errors"] = TotalErrors;
|
||||
data["warnings"] = TotalWarnings;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(ErrorMessage))
|
||||
data["error"] = ErrorMessage;
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
public class BatchJob
|
||||
{
|
||||
public string JobId { get; }
|
||||
public BuildJobState State { get; set; } = BuildJobState.Pending;
|
||||
public List<BuildJob> Children { get; } = new();
|
||||
public int CurrentIndex { get; set; } = -1;
|
||||
|
||||
public BatchJob(string jobId)
|
||||
{
|
||||
JobId = jobId;
|
||||
}
|
||||
|
||||
public object ToStatusResponse()
|
||||
{
|
||||
int completed = 0;
|
||||
string currentBuild = null;
|
||||
var builds = new List<object>();
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
if (child.State == BuildJobState.Succeeded || child.State == BuildJobState.Failed
|
||||
|| child.State == BuildJobState.Skipped || child.State == BuildJobState.Cancelled)
|
||||
completed++;
|
||||
if (child.State == BuildJobState.Building)
|
||||
currentBuild = child.JobId;
|
||||
builds.Add(child.ToStatusResponse());
|
||||
}
|
||||
|
||||
return new Dictionary<string, object>
|
||||
{
|
||||
["job_id"] = JobId,
|
||||
["result"] = State.ToString().ToLowerInvariant(),
|
||||
["completed"] = completed,
|
||||
["total"] = Children.Count,
|
||||
["current_build"] = currentBuild,
|
||||
["builds"] = builds
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Static store for all build jobs. Note: static fields are cleared on domain reload,
|
||||
/// but this is acceptable because BuildPipeline.BuildPlayer blocks the editor thread,
|
||||
/// preventing domain reload during a build. For batch builds with platform switches,
|
||||
/// the batch scheduling happens after each build completes via EditorApplication.update
|
||||
/// callbacks (ScheduleOnNextUpdate / WaitForCompletion), so state is maintained within
|
||||
/// a single domain lifecycle.
|
||||
/// </summary>
|
||||
public static class BuildJobStore
|
||||
{
|
||||
private static readonly Dictionary<string, BuildJob> _buildJobs = new();
|
||||
private static readonly Dictionary<string, BatchJob> _batchJobs = new();
|
||||
private static BuildJob _lastCompletedJob;
|
||||
|
||||
public static string CreateJobId() => $"build-{Guid.NewGuid():N}".Substring(0, 16);
|
||||
public static string CreateBatchId() => $"batch-{Guid.NewGuid():N}".Substring(0, 16);
|
||||
|
||||
public static void AddBuildJob(BuildJob job) => _buildJobs[job.JobId] = job;
|
||||
public static void AddBatchJob(BatchJob job) => _batchJobs[job.JobId] = job;
|
||||
|
||||
public static BuildJob GetBuildJob(string jobId)
|
||||
{
|
||||
_buildJobs.TryGetValue(jobId, out var job);
|
||||
return job;
|
||||
}
|
||||
|
||||
public static BatchJob GetBatchJob(string jobId)
|
||||
{
|
||||
_batchJobs.TryGetValue(jobId, out var job);
|
||||
return job;
|
||||
}
|
||||
|
||||
public static BuildJob LastCompletedJob => _lastCompletedJob;
|
||||
|
||||
public static void SetLastCompleted(BuildJob job)
|
||||
{
|
||||
_lastCompletedJob = job;
|
||||
PruneOldJobs();
|
||||
}
|
||||
|
||||
private const int MaxRetainedJobs = 50;
|
||||
|
||||
private static void PruneOldJobs()
|
||||
{
|
||||
if (_buildJobs.Count <= MaxRetainedJobs) return;
|
||||
|
||||
var toRemove = new List<string>();
|
||||
foreach (var kvp in _buildJobs)
|
||||
{
|
||||
if (kvp.Value.State != BuildJobState.Building && kvp.Value.State != BuildJobState.Pending
|
||||
&& kvp.Value != _lastCompletedJob)
|
||||
toRemove.Add(kvp.Key);
|
||||
}
|
||||
|
||||
foreach (var key in toRemove)
|
||||
{
|
||||
_buildJobs.Remove(key);
|
||||
if (_buildJobs.Count <= MaxRetainedJobs / 2) break;
|
||||
}
|
||||
|
||||
// Also prune batch jobs whose children are all terminal
|
||||
var batchesToRemove = new List<string>();
|
||||
foreach (var kvp in _batchJobs)
|
||||
{
|
||||
var batch = kvp.Value;
|
||||
if (batch.State == BuildJobState.Building || batch.State == BuildJobState.Pending)
|
||||
continue;
|
||||
// Remove child references that were already pruned from _buildJobs
|
||||
batch.Children.RemoveAll(c => !_buildJobs.ContainsKey(c.JobId));
|
||||
if (batch.Children.Count == 0)
|
||||
batchesToRemove.Add(kvp.Key);
|
||||
}
|
||||
foreach (var key in batchesToRemove)
|
||||
_batchJobs.Remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c04470155554320bc9e1655674d6aa7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build.Reporting;
|
||||
using UnityEditor.Build;
|
||||
using MCPForUnity.Editor.Helpers;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Build
|
||||
{
|
||||
public static class BuildRunner
|
||||
{
|
||||
public static object ScheduleBuild(BuildJob job, BuildPlayerOptions options)
|
||||
{
|
||||
job.State = BuildJobState.Pending;
|
||||
BuildJobStore.AddBuildJob(job);
|
||||
|
||||
ScheduleOnNextUpdate(() =>
|
||||
RunBuildCore(job, () => BuildPipeline.BuildPlayer(options)));
|
||||
|
||||
return new PendingResponse(
|
||||
$"Build scheduled for {job.Target}. Polling for completion...",
|
||||
pollIntervalSeconds: 5.0,
|
||||
data: new { job_id = job.JobId, platform = job.Target.ToString() }
|
||||
);
|
||||
}
|
||||
|
||||
#if UNITY_6000_0_OR_NEWER
|
||||
public static object ScheduleProfileBuild(BuildJob job, BuildPlayerWithProfileOptions options)
|
||||
{
|
||||
job.State = BuildJobState.Pending;
|
||||
BuildJobStore.AddBuildJob(job);
|
||||
|
||||
ScheduleOnNextUpdate(() =>
|
||||
RunBuildCore(job, () => BuildPipeline.BuildPlayer(options)));
|
||||
|
||||
return new PendingResponse(
|
||||
$"Profile build scheduled for {job.Target}. Polling for completion...",
|
||||
pollIntervalSeconds: 5.0,
|
||||
data: new { job_id = job.JobId, platform = job.Target.ToString() }
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static BuildPlayerOptions CreateBuildOptions(
|
||||
BuildTarget target,
|
||||
string outputPath,
|
||||
string[] scenes,
|
||||
BuildOptions buildOptions,
|
||||
int subtarget)
|
||||
{
|
||||
var options = new BuildPlayerOptions
|
||||
{
|
||||
target = target,
|
||||
targetGroup = BuildTargetMapping.GetTargetGroup(target),
|
||||
locationPathName = outputPath,
|
||||
scenes = scenes ?? GetDefaultScenes(),
|
||||
options = buildOptions,
|
||||
};
|
||||
|
||||
// Subtarget is only meaningful for Standalone (Server vs Player).
|
||||
// On Android/iOS it maps to MobileTextureSubtarget (texture compression format).
|
||||
// Passing StandaloneBuildSubtarget.Player (0) for mobile platforms forces
|
||||
// a specific texture format — confirmed Unity bug IN-102413 where value 0
|
||||
// on Unity 6000+ triggers PVRTC, ignoring Player Settings.
|
||||
// Leave at platform default (0) so Unity respects Player Settings.
|
||||
if (target == BuildTarget.StandaloneWindows
|
||||
|| target == BuildTarget.StandaloneWindows64
|
||||
|| target == BuildTarget.StandaloneOSX
|
||||
|| target == BuildTarget.StandaloneLinux64)
|
||||
{
|
||||
options.subtarget = subtarget;
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
public static BuildOptions ParseBuildOptions(string[] optionNames, bool development)
|
||||
{
|
||||
var opts = BuildOptions.None;
|
||||
if (development)
|
||||
opts |= BuildOptions.Development;
|
||||
|
||||
if (optionNames == null) return opts;
|
||||
|
||||
foreach (var name in optionNames)
|
||||
{
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
case "clean_build": opts |= BuildOptions.CleanBuildCache; break;
|
||||
case "auto_run": opts |= BuildOptions.AutoRunPlayer; break;
|
||||
case "deep_profiling": opts |= BuildOptions.EnableDeepProfilingSupport; break;
|
||||
case "compress_lz4": opts |= BuildOptions.CompressWithLz4; break;
|
||||
case "strict_mode": opts |= BuildOptions.StrictMode; break;
|
||||
case "detailed_report": opts |= BuildOptions.DetailedBuildReport; break;
|
||||
case "allow_debugging": opts |= BuildOptions.AllowDebugging; break;
|
||||
case "connect_profiler": opts |= BuildOptions.ConnectWithProfiler; break;
|
||||
case "scripts_only": opts |= BuildOptions.BuildScriptsOnly; break;
|
||||
case "show_player": opts |= BuildOptions.ShowBuiltPlayer; break;
|
||||
case "include_tests": opts |= BuildOptions.IncludeTestAssemblies; break;
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
private static void RunBuildCore(BuildJob job, Func<BuildReport> buildFunc)
|
||||
{
|
||||
job.State = BuildJobState.Building;
|
||||
job.StartedAt = DateTime.UtcNow;
|
||||
|
||||
try
|
||||
{
|
||||
BuildReport report = buildFunc();
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
|
||||
// Extract summary data immediately, then let the BuildReport be GC'd
|
||||
var summary = report.summary;
|
||||
job.TotalSizeMb = Math.Round(summary.totalSize / (1024.0 * 1024.0), 2);
|
||||
job.TotalErrors = summary.totalErrors;
|
||||
job.TotalWarnings = summary.totalWarnings;
|
||||
|
||||
if (summary.result == BuildResult.Succeeded)
|
||||
{
|
||||
job.State = BuildJobState.Succeeded;
|
||||
}
|
||||
else
|
||||
{
|
||||
job.State = BuildJobState.Failed;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
job.ErrorMessage = report.SummarizeErrors();
|
||||
#else
|
||||
job.ErrorMessage = $"Build failed with result: {summary.result}";
|
||||
#endif
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
job.State = BuildJobState.Failed;
|
||||
job.CompletedAt = DateTime.UtcNow;
|
||||
job.ErrorMessage = ex.Message;
|
||||
}
|
||||
|
||||
BuildJobStore.SetLastCompleted(job);
|
||||
|
||||
// Emit console signal so the build result is visible in Unity's Console
|
||||
if (job.State == BuildJobState.Succeeded)
|
||||
{
|
||||
UnityEngine.Debug.Log(
|
||||
$"[MCP Build] Build succeeded: {job.Target} → {job.OutputPath} " +
|
||||
$"({job.TotalSizeMb} MB, {(job.CompletedAt.Value - job.StartedAt).TotalSeconds:F1}s)");
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError(
|
||||
$"[MCP Build] ✗ Build failed: {job.Target} — {job.ErrorMessage}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void ScheduleNextBatchBuild(BatchJob batch, Func<int, BuildJob> createChildBuild)
|
||||
{
|
||||
batch.CurrentIndex++;
|
||||
|
||||
if (batch.State == BuildJobState.Cancelled)
|
||||
{
|
||||
for (int i = batch.CurrentIndex; i < batch.Children.Count; i++)
|
||||
batch.Children[i].State = BuildJobState.Skipped;
|
||||
return;
|
||||
}
|
||||
|
||||
if (batch.CurrentIndex >= batch.Children.Count)
|
||||
{
|
||||
bool anyFailed = batch.Children.Any(c => c.State == BuildJobState.Failed);
|
||||
batch.State = anyFailed ? BuildJobState.Failed : BuildJobState.Succeeded;
|
||||
return;
|
||||
}
|
||||
|
||||
var child = batch.Children[batch.CurrentIndex];
|
||||
createChildBuild(batch.CurrentIndex);
|
||||
|
||||
// Safety timeout: if child never transitions out of Building/Pending after 2 hours,
|
||||
// unregister the delegate to prevent an orphaned update loop
|
||||
var watchStart = DateTime.UtcNow;
|
||||
var maxWait = TimeSpan.FromHours(2);
|
||||
|
||||
void WaitForCompletion()
|
||||
{
|
||||
if (DateTime.UtcNow - watchStart > maxWait)
|
||||
{
|
||||
EditorApplication.update -= WaitForCompletion;
|
||||
if (child.State == BuildJobState.Building || child.State == BuildJobState.Pending)
|
||||
{
|
||||
child.State = BuildJobState.Failed;
|
||||
child.ErrorMessage = "Build timed out after 2 hours.";
|
||||
}
|
||||
ScheduleNextBatchBuild(batch, createChildBuild);
|
||||
return;
|
||||
}
|
||||
|
||||
if (child.State == BuildJobState.Building || child.State == BuildJobState.Pending)
|
||||
return;
|
||||
|
||||
EditorApplication.update -= WaitForCompletion;
|
||||
ScheduleNextBatchBuild(batch, createChildBuild);
|
||||
}
|
||||
EditorApplication.update += WaitForCompletion;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Schedule an action to run on the next EditorApplication.update tick.
|
||||
/// Unlike delayCall, update callbacks are guaranteed to fire since the
|
||||
/// TransportCommandDispatcher processes commands through the same mechanism.
|
||||
/// </summary>
|
||||
private static void ScheduleOnNextUpdate(Action action)
|
||||
{
|
||||
bool executed = false;
|
||||
void RunOnce()
|
||||
{
|
||||
if (executed) return;
|
||||
executed = true;
|
||||
EditorApplication.update -= RunOnce;
|
||||
action();
|
||||
}
|
||||
EditorApplication.update += RunOnce;
|
||||
}
|
||||
|
||||
private static string[] GetDefaultScenes()
|
||||
{
|
||||
return EditorBuildSettings.scenes
|
||||
.Where(s => s.enabled)
|
||||
.Select(s => s.path)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9e1fff59ee9947f49ad112a9403b11df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Build
|
||||
{
|
||||
public static class BuildSettingsHelper
|
||||
{
|
||||
public static object ReadProperty(string property, NamedBuildTarget namedTarget)
|
||||
{
|
||||
switch (property.ToLowerInvariant())
|
||||
{
|
||||
case "product_name":
|
||||
return new { property, value = PlayerSettings.productName };
|
||||
case "company_name":
|
||||
return new { property, value = PlayerSettings.companyName };
|
||||
case "version":
|
||||
return new { property, value = PlayerSettings.bundleVersion };
|
||||
case "bundle_id":
|
||||
return new { property, value = PlayerSettings.GetApplicationIdentifier(namedTarget) };
|
||||
case "scripting_backend":
|
||||
var backend = PlayerSettings.GetScriptingBackend(namedTarget);
|
||||
return new { property, value = backend == ScriptingImplementation.IL2CPP ? "il2cpp" : "mono" };
|
||||
case "defines":
|
||||
return new { property, value = PlayerSettings.GetScriptingDefineSymbols(namedTarget) };
|
||||
case "architecture":
|
||||
var arch = PlayerSettings.GetArchitecture(namedTarget);
|
||||
string archName = arch switch { 0 => "x86_64", 1 => "arm64", 2 => "universal", _ => "unknown" };
|
||||
return new { property, value = archName, raw = arch };
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string WriteProperty(string property, string value, NamedBuildTarget namedTarget)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (property.ToLowerInvariant())
|
||||
{
|
||||
case "product_name":
|
||||
PlayerSettings.productName = value;
|
||||
return null;
|
||||
case "company_name":
|
||||
PlayerSettings.companyName = value;
|
||||
return null;
|
||||
case "version":
|
||||
PlayerSettings.bundleVersion = value;
|
||||
return null;
|
||||
case "bundle_id":
|
||||
PlayerSettings.SetApplicationIdentifier(namedTarget, value);
|
||||
return null;
|
||||
case "scripting_backend":
|
||||
var backendValue = value.ToLowerInvariant();
|
||||
if (backendValue != "il2cpp" && backendValue != "mono")
|
||||
return $"Unknown scripting_backend '{value}'. Valid: mono, il2cpp";
|
||||
var impl = backendValue == "il2cpp"
|
||||
? ScriptingImplementation.IL2CPP
|
||||
: ScriptingImplementation.Mono2x;
|
||||
PlayerSettings.SetScriptingBackend(namedTarget, impl);
|
||||
return null;
|
||||
case "defines":
|
||||
PlayerSettings.SetScriptingDefineSymbols(namedTarget, value);
|
||||
return null;
|
||||
case "architecture":
|
||||
int arch = value.ToLowerInvariant() switch
|
||||
{
|
||||
"x86_64" or "none" or "default" => 0,
|
||||
"arm64" => 1,
|
||||
"universal" => 2,
|
||||
_ => -1
|
||||
};
|
||||
if (arch < 0)
|
||||
return $"Unknown architecture '{value}'. Valid: x86_64, arm64, universal";
|
||||
PlayerSettings.SetArchitecture(namedTarget, arch);
|
||||
return null;
|
||||
default:
|
||||
return $"Unknown property '{property}'. Valid: product_name, company_name, version, bundle_id, scripting_backend, defines, architecture";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return $"Failed to set {property}: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly IReadOnlyList<string> ValidProperties = new[]
|
||||
{
|
||||
"product_name", "company_name", "version", "bundle_id",
|
||||
"scripting_backend", "defines", "architecture"
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51f7a2852819440eada31cda61039b6a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace MCPForUnity.Editor.Tools.Build
|
||||
{
|
||||
public static class BuildTargetMapping
|
||||
{
|
||||
private const string VisionOSName = "VisionOS";
|
||||
|
||||
public static bool TryResolveBuildTarget(string name, out BuildTarget target)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
target = EditorUserBuildSettings.activeBuildTarget;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (name.ToLowerInvariant())
|
||||
{
|
||||
case "windows64": target = BuildTarget.StandaloneWindows64; return true;
|
||||
case "windows": case "windows32": target = BuildTarget.StandaloneWindows; return true;
|
||||
case "osx": case "macos": target = BuildTarget.StandaloneOSX; return true;
|
||||
case "linux64": case "linux": target = BuildTarget.StandaloneLinux64; return true;
|
||||
case "android": target = BuildTarget.Android; return true;
|
||||
case "ios": target = BuildTarget.iOS; return true;
|
||||
case "webgl": target = BuildTarget.WebGL; return true;
|
||||
case "uwp": target = BuildTarget.WSAPlayer; return true;
|
||||
case "tvos": target = BuildTarget.tvOS; return true;
|
||||
default:
|
||||
if (TryParseDefinedBuildTarget(name, out target))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
target = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static BuildTargetGroup GetTargetGroup(BuildTarget target)
|
||||
{
|
||||
switch (target)
|
||||
{
|
||||
case BuildTarget.StandaloneWindows:
|
||||
case BuildTarget.StandaloneWindows64:
|
||||
case BuildTarget.StandaloneOSX:
|
||||
case BuildTarget.StandaloneLinux64:
|
||||
return BuildTargetGroup.Standalone;
|
||||
case BuildTarget.iOS: return BuildTargetGroup.iOS;
|
||||
case BuildTarget.Android: return BuildTargetGroup.Android;
|
||||
case BuildTarget.WebGL: return BuildTargetGroup.WebGL;
|
||||
case BuildTarget.WSAPlayer: return BuildTargetGroup.WSA;
|
||||
case BuildTarget.tvOS: return BuildTargetGroup.tvOS;
|
||||
default:
|
||||
if (IsVisionOSTarget(target)
|
||||
&& Enum.TryParse(VisionOSName, true, out BuildTargetGroup visionOSGroup))
|
||||
{
|
||||
return visionOSGroup;
|
||||
}
|
||||
|
||||
return BuildTargetGroup.Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
public static NamedBuildTarget GetNamedBuildTarget(BuildTarget target)
|
||||
{
|
||||
return NamedBuildTarget.FromBuildTargetGroup(GetTargetGroup(target));
|
||||
}
|
||||
|
||||
public static string TryResolveNamedBuildTarget(string name, out NamedBuildTarget namedTarget)
|
||||
{
|
||||
if (!TryResolveBuildTarget(name, out var buildTarget))
|
||||
{
|
||||
namedTarget = default;
|
||||
return GetUnknownBuildTargetMessage(name);
|
||||
}
|
||||
|
||||
var targetGroup = GetTargetGroup(buildTarget);
|
||||
if (targetGroup == BuildTargetGroup.Unknown)
|
||||
{
|
||||
namedTarget = default;
|
||||
return IsVisionOSTarget(buildTarget)
|
||||
? "VisionOS build target is available, but its BuildTargetGroup is not exposed by this Unity editor installation."
|
||||
: $"Build target group could not be resolved for target '{buildTarget}'.";
|
||||
}
|
||||
|
||||
namedTarget = NamedBuildTarget.FromBuildTargetGroup(targetGroup);
|
||||
return null;
|
||||
}
|
||||
|
||||
public static string GetUnknownBuildTargetMessage(string name)
|
||||
{
|
||||
if (string.Equals(name, "visionos", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return "VisionOS build target is not available in this Unity editor installation. "
|
||||
+ "Install the visionOS build support module or use a Unity version/configuration that exposes BuildTarget.VisionOS.";
|
||||
}
|
||||
|
||||
return $"Unknown build target: '{name}'. Valid targets: {GetValidTargetsList()}.";
|
||||
}
|
||||
|
||||
private static string GetValidTargetsList()
|
||||
{
|
||||
string validTargets = "windows64, osx, linux64, android, ios, webgl, uwp, tvos";
|
||||
if (TryParseDefinedBuildTarget(VisionOSName, out _))
|
||||
{
|
||||
validTargets += ", visionos";
|
||||
}
|
||||
|
||||
return validTargets;
|
||||
}
|
||||
|
||||
private static bool IsVisionOSTarget(BuildTarget target)
|
||||
{
|
||||
return string.Equals(target.ToString(), VisionOSName, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static bool TryParseDefinedBuildTarget(string name, out BuildTarget target)
|
||||
{
|
||||
target = default;
|
||||
if (int.TryParse(name, out _))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Enum.TryParse(name, true, out target)
|
||||
&& Enum.IsDefined(typeof(BuildTarget), target);
|
||||
}
|
||||
|
||||
public static string GetDefaultOutputPath(BuildTarget target, string productName)
|
||||
{
|
||||
string basePath = $"Builds/{target}";
|
||||
switch (target)
|
||||
{
|
||||
case BuildTarget.StandaloneWindows:
|
||||
case BuildTarget.StandaloneWindows64:
|
||||
return $"{basePath}/{productName}.exe";
|
||||
case BuildTarget.StandaloneOSX:
|
||||
return $"{basePath}/{productName}.app";
|
||||
case BuildTarget.StandaloneLinux64:
|
||||
return $"{basePath}/{productName}.x86_64";
|
||||
case BuildTarget.Android:
|
||||
return EditorUserBuildSettings.buildAppBundle
|
||||
? $"{basePath}/{productName}.aab"
|
||||
: $"{basePath}/{productName}.apk";
|
||||
case BuildTarget.iOS:
|
||||
case BuildTarget.WebGL:
|
||||
return $"{basePath}/{productName}";
|
||||
default:
|
||||
return $"{basePath}/{productName}";
|
||||
}
|
||||
}
|
||||
|
||||
public static int ResolveSubtarget(string subtarget)
|
||||
{
|
||||
if (string.IsNullOrEmpty(subtarget))
|
||||
return (int)StandaloneBuildSubtarget.Player;
|
||||
string lower = subtarget.ToLowerInvariant();
|
||||
if (lower == "server")
|
||||
return (int)StandaloneBuildSubtarget.Server;
|
||||
return (int)StandaloneBuildSubtarget.Player;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c338ede07ac74e0ab9db99a1ac8c1a87
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user