Files
AIC-Project/Client/PackageLocal/com.unity.pipeline@0.4.0-exp.1/Editor/Commands/Build/BuildConfigCommands.cs
T

338 lines
14 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Pipeline.Commands;
using UnityEditor;
using UnityEditor.Build;
using UnityEngine;
namespace Unity.Pipeline.Editor.Commands.Build
{
/// <summary>
/// Read/inspect build configuration for agents (CLI-204): enumerate targets, read/write the mutable
/// <c>EditorUserBuildSettings</c> fields, and list Build Profile assets. Scene-list management and
/// target switching are intentionally elsewhere (<c>add_scene_to_build</c>/<c>remove_scene_from_build</c>
/// from CLI-189, and <c>switch_build_target</c>).
/// </summary>
public static class BuildConfigCommands
{
[CliCommand("list_build_targets",
"List the known BuildTarget values with their group and whether build support is installed.",
MainThreadRequired = true)]
public static object ListBuildTargets()
{
var targets = new List<BuildTargetInfo>();
var seen = new HashSet<string>();
foreach (BuildTarget value in Enum.GetValues(typeof(BuildTarget)))
{
// Exclude NoTarget / internal sentinels (negative) and deprecated/obsolete values.
if ((int)value <= 0)
continue;
var name = value.ToString();
if (!seen.Add(name))
continue;
var field = typeof(BuildTarget).GetField(name);
if (field != null && field.GetCustomAttribute<ObsoleteAttribute>() != null)
continue;
BuildTargetGroup group;
try { group = BuildPipeline.GetBuildTargetGroup(value); }
catch { continue; }
bool installed;
try { installed = BuildPipeline.IsBuildTargetSupported(group, value); }
catch { installed = false; }
targets.Add(new BuildTargetInfo
{
Name = name,
DisplayName = DisplayNameFor(value),
TargetGroup = group.ToString(),
IsInstalled = installed
});
}
return targets.OrderBy(t => t.TargetGroup).ThenBy(t => t.Name).ToList();
}
[CliCommand("get_build_settings",
"Read the current build configuration from EditorUserBuildSettings / EditorBuildSettings.",
MainThreadRequired = true)]
public static object GetBuildSettings()
{
var target = EditorUserBuildSettings.activeBuildTarget;
var group = BuildPipeline.GetBuildTargetGroup(target);
return new BuildSettingsResult
{
ActiveBuildTarget = target.ToString(),
ActiveBuildTargetGroup = group.ToString(),
DevelopmentBuild = EditorUserBuildSettings.development,
AllowDebugging = EditorUserBuildSettings.allowDebugging,
ConnectWithProfiler = EditorUserBuildSettings.connectProfiler,
BuildScriptsOnly = EditorUserBuildSettings.buildScriptsOnly,
SymlinkSources = EditorUserBuildSettings.symlinkSources,
Il2CppCodeGeneration = ReadIl2CppCodeGeneration(group),
Scenes = EditorBuildSettings.scenes.Select(s => new BuildSceneEntry
{
Path = s.path,
Guid = s.guid.ToString(),
Enabled = s.enabled
}).ToList()
};
}
[CliCommand("set_build_settings",
"Set mutable EditorUserBuildSettings fields. Does NOT manage scenes (use add_scene_to_build / " +
"remove_scene_from_build) or switch target (use switch_build_target). Use dry_run to preview.",
MainThreadRequired = true)]
public static object SetBuildSettings(
[CliArg("settings", "Fields to change; omitted fields are left unchanged.")] SetBuildSettingsInput settings = null,
[CliArg("confirm", "Apply the changes. Without it the call is refused.")] bool confirm = false,
[CliArg("dry_run", "Preview the change without applying it.")] bool dryRun = false)
{
if (settings == null)
return SetBuildSettingsResult.Fail("No 'settings' object provided.");
if (!dryRun && !confirm)
return SetBuildSettingsResult.Fail(
"Refused: this operation mutates the project. Re-run with confirm=true to apply, or dry_run=true to preview the change.");
var result = new SetBuildSettingsResult { DryRun = dryRun, Success = true };
var group = BuildPipeline.GetBuildTargetGroup(EditorUserBuildSettings.activeBuildTarget);
// For each supplied field: change it (recording in Applied) only if it differs from the
// current value; otherwise record it in Skipped as a no-op.
Plan(result, "developmentBuild", settings.DevelopmentBuild, EditorUserBuildSettings.development,
v => EditorUserBuildSettings.development = v, dryRun);
Plan(result, "allowDebugging", settings.AllowDebugging, EditorUserBuildSettings.allowDebugging,
v => EditorUserBuildSettings.allowDebugging = v, dryRun);
Plan(result, "connectWithProfiler", settings.ConnectWithProfiler, EditorUserBuildSettings.connectProfiler,
v => EditorUserBuildSettings.connectProfiler = v, dryRun);
Plan(result, "buildScriptsOnly", settings.BuildScriptsOnly, EditorUserBuildSettings.buildScriptsOnly,
v => EditorUserBuildSettings.buildScriptsOnly = v, dryRun);
Plan(result, "symlinkSources", settings.SymlinkSources, EditorUserBuildSettings.symlinkSources,
v => EditorUserBuildSettings.symlinkSources = v, dryRun);
if (!string.IsNullOrWhiteSpace(settings.Il2CppCodeGeneration))
{
if (!TryParseIl2Cpp(settings.Il2CppCodeGeneration, out var requested))
return SetBuildSettingsResult.Fail($"Invalid il2CppCodeGeneration '{settings.Il2CppCodeGeneration}'. Use OptimizeSpeed | OptimizeSize.");
var current = ReadIl2CppCodeGeneration(group);
if (!string.Equals(current, requested.ToString(), StringComparison.Ordinal))
{
if (!dryRun)
PlayerSettings.SetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group), requested);
result.Applied["il2CppCodeGeneration"] = requested.ToString();
}
else
{
result.Skipped["il2CppCodeGeneration"] = current;
}
}
result.Message = dryRun
? $"Dry run — {result.Applied.Count} field(s) would change, {result.Skipped.Count} unchanged."
: $"Applied {result.Applied.Count} field(s); {result.Skipped.Count} unchanged.";
return result;
}
[CliCommand("list_build_profiles",
"List Build Profile assets in the project (Unity 6 only). Returns feature_unavailable on earlier versions.",
MainThreadRequired = true)]
public static object ListBuildProfiles()
{
if (!BuildProfiles.IsSupported)
return new { error = "Build Profiles require Unity 6 (6000.0) or later", code = "feature_unavailable" };
return BuildProfiles.List();
}
// ---- Helpers ---------------------------------------------------------------------------
static void Plan(SetBuildSettingsResult result, string name, bool? requested, bool current,
Action<bool> set, bool dryRun)
{
if (!requested.HasValue)
return;
if (requested.Value != current)
{
if (!dryRun)
set(requested.Value);
result.Applied[name] = requested.Value;
}
else
{
result.Skipped[name] = current;
}
}
static string ReadIl2CppCodeGeneration(BuildTargetGroup group)
{
try
{
return PlayerSettings.GetIl2CppCodeGeneration(NamedBuildTarget.FromBuildTargetGroup(group)).ToString();
}
catch
{
return Il2CppCodeGeneration.OptimizeSpeed.ToString();
}
}
static bool TryParseIl2Cpp(string value, out Il2CppCodeGeneration parsed)
{
return Enum.TryParse(value.Trim(), ignoreCase: true, out parsed)
&& Enum.IsDefined(typeof(Il2CppCodeGeneration), parsed);
}
/// <summary>Friendly label for common targets; falls back to the enum name.</summary>
static string DisplayNameFor(BuildTarget target)
{
switch (target)
{
case BuildTarget.StandaloneWindows: return "Windows (32-bit)";
case BuildTarget.StandaloneWindows64: return "Windows 64-bit";
case BuildTarget.StandaloneOSX: return "macOS";
case BuildTarget.StandaloneLinux64: return "Linux 64-bit";
case BuildTarget.Android: return "Android";
case BuildTarget.iOS: return "iOS";
case BuildTarget.tvOS: return "tvOS";
case BuildTarget.WebGL: return "WebGL";
case BuildTarget.WSAPlayer: return "Universal Windows Platform";
case BuildTarget.PS4: return "PlayStation 4";
case BuildTarget.PS5: return "PlayStation 5";
case BuildTarget.XboxOne: return "Xbox One";
case BuildTarget.Switch: return "Nintendo Switch";
default: return target.ToString();
}
}
}
/// <summary>
/// Reflection shim over the Unity 6 Build Profile API (<c>UnityEditor.Build.Profile.BuildProfile</c>),
/// so this assembly compiles and runs on older editors without the type. Used by
/// <c>list_build_profiles</c> and by <c>build</c>'s optional <c>profileName</c> activation.
/// </summary>
internal static class BuildProfiles
{
const string TypeName = "UnityEditor.Build.Profile.BuildProfile, UnityEditor.CoreModule";
static readonly Type s_Type = ResolveType();
public static bool IsSupported => s_Type != null;
static Type ResolveType()
{
var t = Type.GetType(TypeName);
if (t != null)
return t;
// Fall back to a full-assembly scan — the assembly-qualified name can vary across versions.
foreach (var asm in PipelineUtils.GetLoadedAssemblies())
{
t = asm.GetType("UnityEditor.Build.Profile.BuildProfile");
if (t != null)
return t;
}
return null;
}
public static List<BuildProfileInfo> List()
{
var list = new List<BuildProfileInfo>();
if (s_Type == null)
return list;
var active = GetActive();
foreach (var guid in AssetDatabase.FindAssets("t:BuildProfile"))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
var asset = AssetDatabase.LoadAssetAtPath(path, s_Type);
if (asset == null)
continue;
list.Add(new BuildProfileInfo
{
Name = System.IO.Path.GetFileNameWithoutExtension(path),
Guid = guid,
Platform = ReadPlatform(asset),
IsActive = active != null && ReferenceEquals(active, asset)
});
}
return list;
}
public static BuildProfileInfo FindByName(string name)
{
return List().FirstOrDefault(p => string.Equals(p.Name, name, StringComparison.OrdinalIgnoreCase));
}
/// <summary>Activate the named profile via the API's static setter; returns false if unavailable.</summary>
public static bool TrySetActive(BuildProfileInfo info)
{
if (s_Type == null || info == null)
return false;
var path = AssetDatabase.GUIDToAssetPath(info.Guid);
var asset = AssetDatabase.LoadAssetAtPath(path, s_Type);
if (asset == null)
return false;
// Unity 6 exposes a static settable 'activeProfile' (and/or a SetActiveBuildProfile method).
var prop = s_Type.GetProperty("activeProfile", BindingFlags.Public | BindingFlags.Static);
if (prop != null && prop.CanWrite)
{
prop.SetValue(null, asset);
return true;
}
var method = s_Type.GetMethod("SetActiveBuildProfile", BindingFlags.Public | BindingFlags.Static);
if (method != null)
{
method.Invoke(null, new[] { asset });
return true;
}
return false;
}
static UnityEngine.Object GetActive()
{
try
{
var prop = s_Type.GetProperty("activeProfile", BindingFlags.Public | BindingFlags.Static);
if (prop != null)
return prop.GetValue(null) as UnityEngine.Object;
var method = s_Type.GetMethod("GetActiveBuildProfile", BindingFlags.Public | BindingFlags.Static);
if (method != null)
return method.Invoke(null, null) as UnityEngine.Object;
}
catch { /* best effort */ }
return null;
}
static string ReadPlatform(UnityEngine.Object asset)
{
try
{
// Public property first, then the serialized backing field.
var prop = s_Type.GetProperty("buildTarget", BindingFlags.Public | BindingFlags.Instance);
if (prop != null)
return prop.GetValue(asset)?.ToString() ?? string.Empty;
var field = s_Type.GetField("m_BuildTarget", BindingFlags.NonPublic | BindingFlags.Instance);
if (field != null)
return field.GetValue(asset)?.ToString() ?? string.Empty;
}
catch { /* best effort */ }
return string.Empty;
}
}
}