diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..ee62962 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,85 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +trim_trailing_whitespace = false +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +file_header_template = --------------------------------------------------------------\nCopyright ${CurrentDate.Year} CyberAgent, Inc.\n-------------------------------------------------------------- + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = warning +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_autodetect_indent_settings = true +resharper_enforce_line_ending_style = true +resharper_indent_preprocessor_directives = normal +resharper_use_indent_from_vs = false +resharper_wrap_lines = true + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + +[{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] +indent_style = space +indent_size = 2 + +[{*.yaml,*.yml}] +indent_style = space +indent_size = 2 + +[*.asmdef] +indent_style = space +indent_size = 4 + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6178bdb --- /dev/null +++ b/.gitignore @@ -0,0 +1,78 @@ +# This .gitignore file should be placed at the root of your Unity project directory +# +# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore +# +/[Ll]ibrary/ +/[Tt]emp/ +/[Oo]bj/ +/[Bb]uild/ +/[Bb]uilds/ +/[Ll]ogs/ +/[Uu]ser[Ss]ettings/ +/[Uu]IElementsSchema/ + +# MemoryCaptures can get excessive in size. +# They also could contain extremely sensitive data +/[Mm]emoryCaptures/ + +# Recordings can get excessive in size +/[Rr]ecordings/ + +# Uncomment this line if you wish to ignore the asset store tools plugin +# /[Aa]ssets/AssetStoreTools* + +# Autogenerated Jetbrains Rider plugin +/[Aa]ssets/Plugins/Editor/JetBrains* + +# Visual Studio cache directory +.vs/ + +# Gradle cache directory +.gradle/ + +# Autogenerated VS/MD/Consulo solution and project files +ExportedObj/ +.consulo/ +*.csproj +*.unityproj +*.sln +*.suo +*.tmp +*.user +*.userprefs +*.pidb +*.booproj +*.svd +*.pdb +*.mdb +*.opendb +*.VC.db + +# Unity3D generated meta files +*.pidb.meta +*.pdb.meta +*.mdb.meta + +# Unity3D generated file on crash reports +sysinfo.txt + +# Builds +*.apk +*.aab +*.unitypackage +*.app + +# Crashlytics generated file +crashlytics-build.properties + +# Packed Addressables +/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* + +# Temporary auto-generated Android Assets +/[Aa]ssets/[Ss]treamingAssets/aa.meta +/[Aa]ssets/[Ss]treamingAssets/aa/* + +# Others +.DS_Store +/.idea/ +*.log diff --git a/Assets/Scenes.meta b/Assets/Scenes.meta new file mode 100644 index 0000000..b823ae8 --- /dev/null +++ b/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 827adfdb1cfbd4555aee8a7c89378823 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity new file mode 100644 index 0000000..1c2ee58 --- /dev/null +++ b/Assets/Scenes/SampleScene.unity @@ -0,0 +1,222 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 3 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 0 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 0 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 3 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &519420028 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 519420032} + - component: {fileID: 519420031} + - component: {fileID: 519420029} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &519420029 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 +--- !u!20 &519420031 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 0 + m_HDR: 1 + m_AllowMSAA: 0 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 0 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &519420032 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 519420028} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 519420032} diff --git a/Assets/Scenes/SampleScene.unity.meta b/Assets/Scenes/SampleScene.unity.meta new file mode 100644 index 0000000..c1e3c88 --- /dev/null +++ b/Assets/Scenes/SampleScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 2cda990e2423bbf4892e6590ba056729 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts.meta b/Assets/Scripts.meta new file mode 100644 index 0000000..cb08a1e --- /dev/null +++ b/Assets/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ecbc3c5f2a8e245e5a9a1830ed75e0be +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor.meta b/Assets/Scripts/Editor.meta new file mode 100644 index 0000000..45f1b8d --- /dev/null +++ b/Assets/Scripts/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce6a3b2689fbc4c3cab6e1961acbdede +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs b/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs new file mode 100644 index 0000000..68409a8 --- /dev/null +++ b/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs @@ -0,0 +1,24 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.IO; +using BuildMagicEditor; +using UnityEditor; +using UnityEngine.Assertions; + +public static class BuiltinTemplate +{ + private const string Name = "BuiltinTemplate"; + + [MenuItem("BuildMagic(Dev)/Deploy Builtin Template")] + public static void DeployBuiltinTemplate() + { + var template = BuildSchemeLoader.Load(Name); + Assert.IsNotNull(template); + + var json = BuildSchemeSerializer.Serialize(template); + var path = $"Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/{Name}.json"; + File.WriteAllText(path, json); + } +} diff --git a/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs.meta b/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs.meta new file mode 100644 index 0000000..de8d8da --- /dev/null +++ b/Assets/Scripts/Editor/BuiltinTemplateDeployer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66cee7e9b41d043c79512ac5fb92ece6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/CachedEnumeration.cs b/Assets/Scripts/Editor/CachedEnumeration.cs new file mode 100644 index 0000000..1451751 --- /dev/null +++ b/Assets/Scripts/Editor/CachedEnumeration.cs @@ -0,0 +1,42 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +/// +/// 列挙型クラス +/// GetValuesの値をstatic変数にキャッシュしておく +/// +internal abstract class CachedEnumeration : Enumeration + where T : Enumeration +{ + private static T[] _cachedValues; + + protected CachedEnumeration(int id, string name) : base(id, name) + { + } + + private static T[] CachedValues => _cachedValues ??= GetValues().ToArray(); + + public static IEnumerable GetValues() => CachedValues; + + public static int GetLength() => CachedValues.Length; + + public static T GetValue(int index) => CachedValues[index]; + + public static T GetValue(string name) => CachedValues.FirstOrDefault(_ => _.Name == name); + + public static T Next(T current) + { + var index = Array.IndexOf(CachedValues, current); + if (index == -1) + throw new ArgumentException($"{current}"); + + // Next + index = (index + 1) % GetLength(); + return _cachedValues[index]; + } +} diff --git a/Assets/Scripts/Editor/CachedEnumeration.cs.meta b/Assets/Scripts/Editor/CachedEnumeration.cs.meta new file mode 100644 index 0000000..17d3eab --- /dev/null +++ b/Assets/Scripts/Editor/CachedEnumeration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a95c94f1e9e940dda66a0c6a39c2565e +timeCreated: 1718872668 \ No newline at end of file diff --git a/Assets/Scripts/Editor/Enumeration.cs b/Assets/Scripts/Editor/Enumeration.cs new file mode 100644 index 0000000..a84187f --- /dev/null +++ b/Assets/Scripts/Editor/Enumeration.cs @@ -0,0 +1,50 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +public abstract class Enumeration : IComparable +{ + protected Enumeration(int id, string name) + { + Id = id; + Name = name; + } + + public string Name { get; } + public int Id { get; } + public int CompareTo(object other) => Id.CompareTo(((Enumeration)other).Id); + + /// + /// リフレクション機能を使用しIEnumerableを返す + /// + public static IEnumerable GetValues() where T : Enumeration + { + var fields = typeof(T).GetFields(BindingFlags.Public | + BindingFlags.Static | + BindingFlags.DeclaredOnly); + + return fields.Where(f => f.FieldType == typeof(T)).Select(f => f.GetValue(null)).Cast() + .OrderBy(_ => _.Id); + } + + public override bool Equals(object obj) + { + var otherValue = obj as Enumeration; + + if (otherValue == null) + return false; + + var typeMatches = GetType() == obj.GetType(); + var valueMatches = Id.Equals(otherValue.Id); + + return typeMatches && valueMatches; + } + + public override int GetHashCode() => Id; + public override string ToString() => Name; +} diff --git a/Assets/Scripts/Editor/Enumeration.cs.meta b/Assets/Scripts/Editor/Enumeration.cs.meta new file mode 100644 index 0000000..25f6462 --- /dev/null +++ b/Assets/Scripts/Editor/Enumeration.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fa8abb18e8b94301b49065ed4b06b68f +timeCreated: 1718872687 \ No newline at end of file diff --git a/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs b/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs new file mode 100644 index 0000000..0ba34fa --- /dev/null +++ b/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs @@ -0,0 +1,50 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using BuildMagicEditor; +using UnityEditor; + +// ビルドタスクを設定で管理するために必要な属性の定義 +[GenerateBuildTaskAccessories( + "Sample Api Setting", + PropertyName = "SampleApiSetting")] +[BuildConfiguration("SampleApiSetting")] +public class SampleApiSettingBuildTask : BuildTaskBase +{ + private readonly string _url; + private readonly int _port; + + // この設定で更新で必要な値は、すべてタスクのコンストラクタ引数をとるようにしてください + public SampleApiSettingBuildTask(string url, int port) + { + _url = url; + _port = port; + } + + public override void Run(IPreBuildContext context) + { + // 保持している設定をプロジェクトに反映する実装をRun内に記述する + var setting = AssetDatabase.LoadAssetAtPath("Assets/Settings/SampleApiSettings.asset"); + if (setting != null) + { + setting.Url = _url; + setting.Port = _port; + EditorUtility.SetDirty(setting); + } + } +} + +public partial class SampleApiSettingBuildTaskConfiguration : IProjectSettingApplier +{ + void IProjectSettingApplier.ApplyProjectSetting() + { + var setting = AssetDatabase.LoadAssetAtPath("Assets/Settings/SampleApiSettings.asset"); + if (setting != null) + Value = new SampleApiSettingBuildTaskParameters + { + port = setting.Port, + url = setting.Url + }; + } +} diff --git a/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs.meta b/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs.meta new file mode 100644 index 0000000..d79349d --- /dev/null +++ b/Assets/Scripts/Editor/SampleApiSettingBuildTask.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4302a3de96ae4f19b3403bd3c6058a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Editor/SymbolSwitcher.cs b/Assets/Scripts/Editor/SymbolSwitcher.cs new file mode 100644 index 0000000..b5ceaf5 --- /dev/null +++ b/Assets/Scripts/Editor/SymbolSwitcher.cs @@ -0,0 +1,141 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; +using UnityEditor; +using UnityEngine; + +public class SymbolSwitcher : EditorWindow +{ + private const int MinColumnSize = 120; + + private static readonly SymbolData[] Symbols; + + private static readonly Dictionary<(TargetPlatform, SymbolData), bool> SymbolStatus = new(); + + private static Vector2 ScrollPos; + + static SymbolSwitcher() + { + Symbols = new SymbolData[] + { + new("BUILDMAGIC_DEVELOPER", "BUILDMAGIC_DEVELOPER") + }; + } + + private void OnGUI() + { + EditorGUILayout.BeginVertical(); + ScrollPos = EditorGUILayout.BeginScrollView(ScrollPos, GUILayout.Height(position.height)); + + EditorGUILayout.BeginHorizontal(GUILayout.ExpandWidth(true)); + + // プラットフォーム列 + using (new GUILayout.VerticalScope()) + { + var layoutOption = GUILayout.MinWidth(MinColumnSize); + EditorGUILayout.LabelField("", layoutOption); + foreach (var targetPlatform in TargetPlatform.GetValues()) + EditorGUILayout.LabelField(targetPlatform.Name, layoutOption); + } + + // チェックボックス列 + foreach (var symbol in Symbols) + using (new GUILayout.VerticalScope()) + { + EditorGUILayout.LabelField(symbol.DisplayName, GUILayout.MinWidth(MinColumnSize)); + foreach (var targetPlatform in TargetPlatform.GetValues()) + SymbolStatus[(targetPlatform, symbol)] = + EditorGUILayout.Toggle(SymbolStatus[(targetPlatform, symbol)]); + } + + EditorGUILayout.EndHorizontal(); + + GUILayout.Space(10); + + if (GUILayout.Button("Apply")) + { + ApplySymbols(); + Close(); + } + + EditorGUILayout.EndScrollView(); + EditorGUILayout.EndVertical(); + } + + [MenuItem("BuildMagic(Dev)/Open Symbol Switcher")] + public static void Open() + { + GetWindow(true, "Symbol Switcher"); + } + + private void OnEnable() + { + SymbolStatus.Clear(); + foreach (var targetPlatform in TargetPlatform.GetValues()) + foreach (var symbol in Symbols) + { + var enabled = PlayerSettings + .GetScriptingDefineSymbolsForGroup(targetPlatform.BuildTargetGroup) + .Split(';') + .Any(s => s == symbol.Value); + + SymbolStatus[(targetPlatform, symbol)] = enabled; + } + + // プラットフォーム列 + チェックボックス列 + 余白 + maxSize = new Vector2(MinColumnSize + MinColumnSize * Symbols.Length + MinColumnSize, + 300); + minSize = new Vector2(MinColumnSize + MinColumnSize * Symbols.Length + MinColumnSize, + 300); + } + + private static void ApplySymbols() + { + foreach (var targetPlatform in TargetPlatform.GetValues()) + { + var defineSymbols = PlayerSettings + .GetScriptingDefineSymbolsForGroup(targetPlatform.BuildTargetGroup) + .Split(';') + .ToList(); + + foreach (var symbolData in Symbols) + { + defineSymbols.Remove(symbolData.Value); + if (SymbolStatus[(targetPlatform, symbolData)]) + defineSymbols.Add(symbolData.Value); + } + + PlayerSettings.SetScriptingDefineSymbolsForGroup(targetPlatform.BuildTargetGroup, + string.Join(";", defineSymbols)); + } + } + + private class SymbolData + { + public SymbolData(string displayName, string value) + { + DisplayName = displayName; + Value = value; + } + + public string DisplayName { get; } + public string Value { get; } + } + + private class TargetPlatform : CachedEnumeration + { + public static readonly TargetPlatform Ios = new(0, nameof(Ios), BuildTargetGroup.iOS); + public static readonly TargetPlatform Android = new(1, nameof(Android), BuildTargetGroup.Android); + public static readonly TargetPlatform Standalone = new(2, nameof(Standalone), BuildTargetGroup.Standalone); + + private TargetPlatform(int id, string name, BuildTargetGroup buildTargetGroup) : base(id, name) + { + BuildTargetGroup = buildTargetGroup; + } + + public BuildTargetGroup BuildTargetGroup { get; } + } +} diff --git a/Assets/Scripts/Editor/SymbolSwitcher.cs.meta b/Assets/Scripts/Editor/SymbolSwitcher.cs.meta new file mode 100644 index 0000000..02f6be5 --- /dev/null +++ b/Assets/Scripts/Editor/SymbolSwitcher.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a722afaa82ea4819b41a90de1ca7fb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/SampleApiSetting.cs b/Assets/Scripts/SampleApiSetting.cs new file mode 100644 index 0000000..72e7b90 --- /dev/null +++ b/Assets/Scripts/SampleApiSetting.cs @@ -0,0 +1,17 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using UnityEngine; + +[CreateAssetMenu( + fileName = "SampleApiSettings", + menuName = "Build Magic/Samples/Sample ApiSettings")] +[Serializable] +public class SampleApiSetting : ScriptableObject +{ + [SerializeField] public string Url; + + [SerializeField] public int Port; +} diff --git a/Assets/Scripts/SampleApiSetting.cs.meta b/Assets/Scripts/SampleApiSetting.cs.meta new file mode 100644 index 0000000..8567b7b --- /dev/null +++ b/Assets/Scripts/SampleApiSetting.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d892572d916242fbb8b35228aa2dfcb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings.meta b/Assets/Settings.meta new file mode 100644 index 0000000..fac6477 --- /dev/null +++ b/Assets/Settings.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 550da18652bbb4e88830c8db8cebca4a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/SampleApiSettings.asset b/Assets/Settings/SampleApiSettings.asset new file mode 100644 index 0000000..5157b4c --- /dev/null +++ b/Assets/Settings/SampleApiSettings.asset @@ -0,0 +1,16 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8d892572d916242fbb8b35228aa2dfcb, type: 3} + m_Name: SampleApiSettings + m_EditorClassIdentifier: + Url: https://hogeapi.com + Port: 8080 diff --git a/Assets/Settings/SampleApiSettings.asset.meta b/Assets/Settings/SampleApiSettings.asset.meta new file mode 100644 index 0000000..baacc8a --- /dev/null +++ b/Assets/Settings/SampleApiSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8efbc0fe404904e7aa65726b84bac2c1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/BuildMagic.Externals/.editorconfig b/BuildMagic.Externals/.editorconfig new file mode 100644 index 0000000..ee62962 --- /dev/null +++ b/BuildMagic.Externals/.editorconfig @@ -0,0 +1,85 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 120 +tab_width = 4 +trim_trailing_whitespace = false +ij_continuation_indent_size = 8 +ij_formatter_off_tag = @formatter:off +ij_formatter_on_tag = @formatter:on +ij_formatter_tags_enabled = false +ij_smart_tabs = false +ij_visual_guides = none +ij_wrap_on_typing = false + +file_header_template = --------------------------------------------------------------\nCopyright ${CurrentDate.Year} CyberAgent, Inc.\n-------------------------------------------------------------- + +# Microsoft .NET properties +csharp_new_line_before_members_in_object_initializers = false +csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion +csharp_style_var_elsewhere = true:suggestion +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +dotnet_naming_rule.unity_serialized_field_rule.import_to_resharper = True +dotnet_naming_rule.unity_serialized_field_rule.resharper_description = Unity serialized field +dotnet_naming_rule.unity_serialized_field_rule.resharper_guid = 5f0fdb63-c892-4d2c-9324-15c80b22a7ef +dotnet_naming_rule.unity_serialized_field_rule.severity = warning +dotnet_naming_rule.unity_serialized_field_rule.style = lower_camel_case_style +dotnet_naming_rule.unity_serialized_field_rule.symbols = unity_serialized_field_symbols +dotnet_naming_style.lower_camel_case_style.capitalization = camel_case +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_accessibilities = * +dotnet_naming_symbols.unity_serialized_field_symbols.applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_applicable_kinds = unity_serialised_field +dotnet_naming_symbols.unity_serialized_field_symbols.resharper_required_modifiers = instance +dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none +dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion + +# ReSharper properties +resharper_autodetect_indent_settings = true +resharper_enforce_line_ending_style = true +resharper_indent_preprocessor_directives = normal +resharper_use_indent_from_vs = false +resharper_wrap_lines = true + +# ReSharper inspection severities +resharper_arrange_redundant_parentheses_highlighting = hint +resharper_arrange_this_qualifier_highlighting = hint +resharper_arrange_type_member_modifiers_highlighting = hint +resharper_arrange_type_modifiers_highlighting = hint +resharper_built_in_type_reference_style_for_member_access_highlighting = hint +resharper_built_in_type_reference_style_highlighting = hint +resharper_redundant_base_qualifier_highlighting = warning +resharper_suggest_var_or_type_built_in_types_highlighting = hint +resharper_suggest_var_or_type_elsewhere_highlighting = hint +resharper_suggest_var_or_type_simple_types_highlighting = hint +resharper_web_config_module_not_resolved_highlighting = warning +resharper_web_config_type_not_resolved_highlighting = warning +resharper_web_config_wrong_module_highlighting = warning + +[{*.har,*.inputactions,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] +indent_style = space +indent_size = 2 + +[{*.yaml,*.yml}] +indent_style = space +indent_size = 2 + +[*.asmdef] +indent_style = space +indent_size = 4 + +[*.{appxmanifest,asax,ascx,aspx,axaml,build,cg,cginc,compute,cs,cshtml,dtd,fs,fsi,fsscript,fsx,hlsl,hlsli,hlslinc,master,ml,mli,nuspec,paml,razor,resw,resx,shader,skin,usf,ush,vb,xaml,xamlx,xoml,xsd}] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/BuildMagic.Externals/.gitignore b/BuildMagic.Externals/.gitignore new file mode 100644 index 0000000..b8c63b5 --- /dev/null +++ b/BuildMagic.Externals/.gitignore @@ -0,0 +1,402 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +/.idea +!*.sln +!*.csproj diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisLibrary.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisLibrary.cs new file mode 100644 index 0000000..896fc44 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisLibrary.cs @@ -0,0 +1,102 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using ZLogger; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// 解析結果のキャッシュを管理する +/// +public class AnalysisLibrary +{ + private readonly ILogger _logger; + + private readonly string _path = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + "BuildMagic.BuiltinTaskGenerator", "library"); + + private readonly Dictionary _results = new(); + + public AnalysisLibrary(ILogger logger) + { + _logger = logger; + _logger.ZLogInformation($"Using library path: {_path}"); + Directory.CreateDirectory(_path); + } + + public async IAsyncEnumerable<(UnityVersion, AnalysisResult)> LoadAllAsync(Func versionFilter, + CancellationToken ct) + { + Directory.CreateDirectory(_path); + var files = Directory.GetFiles(_path); + + var versions = files.Select(f => + UnityVersion.TryParse(Path.GetFileNameWithoutExtension(f), out var version) + ? (UnityVersion?)version + : null) + .WhereNotNull().Order(); + + foreach (var version in versions.Where(versionFilter)) + { + var (result, isNew) = await GetForVersionAsync(version, false, ct); + yield return (version, result); + } + } + + public async Task<(AnalysisResult result, bool isNew)> GetForVersionAsync(UnityVersion version, bool forceCreate, + CancellationToken ct) + { + AnalysisResult? result; + var isNew = false; + if (!_results.TryGetValue(version, out result)) + { + var path = GetPath(version); + + if (!forceCreate && File.Exists(path)) result = await LoadAsync(path, ct); + + if (result == null) + { + result = new AnalysisResult(); + isNew = true; + } + + _results[version] = result; + } + + return (result, isNew); + } + + public async Task SaveAsync(UnityVersion version, CancellationToken ct) + { + await SaveAsync(version, _results[version], ct); + } + + private async Task SaveAsync(UnityVersion version, AnalysisResult data, CancellationToken ct) + { + await using var fs = File.Open(GetPath(version), FileMode.Create, FileAccess.Write); + await JsonSerializer.SerializeAsync(fs, data, cancellationToken: ct); + } + + private async Task LoadAsync(string path, CancellationToken ct) + { + await using var fs = File.Open(path, FileMode.Open, FileAccess.Read); + var result = await JsonSerializer.DeserializeAsync(fs, cancellationToken: ct); + if (result?.SerializedVersion < AnalysisResult.CurrentSerializedVersion) return null; + return result; + } + + private string GetPath(UnityVersion version) + { + return Path.Combine(_path, $"{version}.json"); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisResult.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisResult.cs new file mode 100644 index 0000000..18a5330 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AnalysisResult.cs @@ -0,0 +1,18 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// バージョンごとの解析結果を保持する +/// +public class AnalysisResult +{ + public static readonly int CurrentSerializedVersion = 4; + + public int SerializedVersion { get; set; } = CurrentSerializedVersion; + public Dictionary Categories { get; set; } = new(); +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiCategory.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiCategory.cs new file mode 100644 index 0000000..0864d40 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiCategory.cs @@ -0,0 +1,15 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// カテゴリごとのAPIリスト +/// +public class ApiCategory +{ + public List Apis { get; set; } = new(); +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiData.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiData.cs new file mode 100644 index 0000000..5f37b72 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ApiData.cs @@ -0,0 +1,53 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// AnalysisResultにおける各APIのデータ +/// +/// +/// +/// +public class ApiData( + string expectedName, + string propertyName, + string displayName, + bool isObsolete, + string setterExpression, + string? getterExpression) +{ + /// + /// Task名として期待される名前 + /// + public string ExpectedName { get; set; } = expectedName; + + /// + /// 表示名 + /// + public string PropertyName { get; set; } = propertyName; + + public string DisplayName { get; set; } = displayName; + + /// + /// Obsoleteかどうか(errorフラグが立っている場合はそもそもリストしない) + /// + public bool IsObsolete { get; set; } = isObsolete; + + /// + /// 値をセットするC#コードの文 + /// {0}, {1}... の形で各引数が補間される + /// + public string SetterExpression { get; set; } = setterExpression; + + /// + /// 値を取得するC#コードの文 + /// 代入先を{0}で示すと補間される + /// + public string? GetterExpression { get; set; } = getterExpression; + + public List Parameters { get; set; } = new(); +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/App.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/App.cs new file mode 100644 index 0000000..66fed24 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/App.cs @@ -0,0 +1,188 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using ZLogger; + +namespace BuildMagic.BuiltinTaskGenerator; + +// ReSharper disable once ClassNeverInstantiated.Global +public class App(ILogger logger, IOptions options, UnityApiAnalyzer analyzer, AnalysisLibrary library) + : ConsoleAppBase +{ + public async Task Generate( + [Option("o", "Output directory for source files")] + string outputDir = ".", + [Option("n", ".NET Framework 4.7.1 (equivalent) BCL path")] + string? netfxBcl = null, + [Option("min", "minimum version of unity (inclusive)")] + string? minVersion = null, + [Option("max", "maximum version of unity (inclusive)")] + string? maxVersion = null, + [Option("f", "forces to ignore cached analysis results")] + bool forceAnalyze = false) + { + UnityVersion minVersionParsed = new(2022, 3, 0, 'f', 1); + if (!string.IsNullOrEmpty(minVersion)) + if (!UnityVersion.TryParse(minVersion, out minVersionParsed)) + { + logger.ZLogCritical($"Failed to parse minimum version \"{minVersionParsed}\"."); + return; + } + + UnityVersion? maxVersionParsed = null; + if (!string.IsNullOrEmpty(maxVersion)) + { + if (!UnityVersion.TryParse(maxVersion, out var v)) + { + logger.ZLogCritical($"Failed to parse minimum version \"{maxVersion}\"."); + return; + } + + maxVersionParsed = v; + } + + if (minVersionParsed.Major <= 2021 && string.IsNullOrEmpty(netfxBcl)) + { + logger.ZLogCritical($"--netfx-bcl is required for processing Unity versions 2021 and earlier."); + return; + } + + logger.ZLogInformation($"Running analysis..."); + + Func versionFilter = v => + { + if (v.ReleaseType != 'f') return false; + if (v < minVersionParsed) return false; + if (maxVersionParsed.HasValue && v > maxVersionParsed) return false; + return true; + }; + + await analyzer.RunAsync(netfxBcl, + versionFilter, + forceAnalyze, + CancellationToken.None); + + Dictionary>> + knownTasksForCategories = new(); + HashSet knownVersions = new(); + + await foreach (var (version, result) in library.LoadAllAsync(versionFilter, CancellationToken.None)) + { + knownVersions.Add(version); + + foreach (var (categoryName, category) in result.Categories) + { + if (!knownTasksForCategories.TryGetValue(categoryName, out var knownTasks)) + knownTasks = knownTasksForCategories[categoryName] = new Dictionary>(); + + var filteredApis = category.Apis.Where(a => !(options.Value.GetApiOptions(a)?.Ignored ?? false)); + + foreach (var apiGrouping in filteredApis.GroupBy(a => a.ExpectedName)) + { + var expectedName = apiGrouping.Key; + + ApiData? selectedApiInCurrentVersion; + try + { + // このバージョンにおいてObsoleteではないAPIが一つなら、それを採用する + selectedApiInCurrentVersion = apiGrouping.SingleOrDefault(a => !a.IsObsolete); + } + catch (InvalidOperationException) + { + // empty=全部obsoleteの場合 + selectedApiInCurrentVersion = null; + } + + if (!knownTasks.TryGetValue(expectedName, out var tasks)) + knownTasks[expectedName] = tasks = new HashSet(); + + TaskData? matchedTask; + if (selectedApiInCurrentVersion == null) + { + // 既知のタスクのうち、シグネチャに互換性があって最も新しいものを選択する + matchedTask = tasks.Where(t => + { + return apiGrouping.Any(a => t.MatchesParameterSignature(a.Parameters)); + }).MaxBy(t => t.LatestVersion); + + if (matchedTask == null) + { + // 特殊対応:NamedBuildTargetをパラメータにもつものは優先的に採用する + if (apiGrouping.Any(d => !d.IsObsolete)) + { + ApiData? singleWithNamedBuildTarget; + try + { + singleWithNamedBuildTarget = apiGrouping.SingleOrDefault(d => + !d.IsObsolete && d.Parameters.Any(p => + p.TypeExpression == "global::UnityEditor.Build.NamedBuildTarget")); + } + catch (InvalidOperationException) + { + singleWithNamedBuildTarget = null; + } + + if (singleWithNamedBuildTarget != null) + tasks.Add(new TaskData(version, singleWithNamedBuildTarget, + options.Value.GetApiOptions(singleWithNamedBuildTarget))); + else + logger.ZLogInformation( + $"We cannot decide which API to use for {expectedName} in {version}."); + } + + continue; + } + + selectedApiInCurrentVersion = + apiGrouping.First(a => matchedTask.MatchesParameterSignature(a.Parameters)); + } + else + { + matchedTask = + tasks.FirstOrDefault(t => + t.MatchesParameterSignature(selectedApiInCurrentVersion.Parameters)); + } + + if (matchedTask != null) + // 既知のタスクとシグネチャに互換性があれば、そこに織り込む + matchedTask.VisitNewerVersion(version, selectedApiInCurrentVersion); + else + // 新設 + tasks.Add(new TaskData(version, selectedApiInCurrentVersion, + options.Value.GetApiOptions(selectedApiInCurrentVersion))); + } + } + } + + // 生成 + UnityVersionDefineConstraintBuilder defineConstraintBuilder = new(knownVersions); + + foreach (var (categoryName, category) in knownTasksForCategories) + { + StringBuilder sourceBuilder = new(); + + sourceBuilder.AppendLine("// "); + sourceBuilder.AppendLine("namespace BuildMagicEditor.BuiltIn"); + sourceBuilder.AppendLine("{"); + + foreach (var (_, tasks) in category) + foreach (var taskData in tasks) + taskData.Generate(sourceBuilder, defineConstraintBuilder, logger, options.Value.DictionaryKeyTypes); + + sourceBuilder.AppendLine("} // namespace BuildMagicEditor.BuiltIn"); + + await File.WriteAllTextAsync(Path.Combine(outputDir, $"Tasks.{categoryName}.g.cs"), + sourceBuilder.ToString(), Encoding.UTF8, CancellationToken.None); + } + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AppSettings.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AppSettings.cs new file mode 100644 index 0000000..878bca1 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/AppSettings.cs @@ -0,0 +1,42 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BuildMagic.BuiltinTaskGenerator; + +public class AppSettings +{ + public Dictionary Apis { get; set; } = new(); + public string[] DictionaryKeyTypes { get; set; } + + public ApiOptions? GetApiOptions(ApiData api) + { + // appsettings.json cannot contain global:: prefix because Microsoft.Extensions.Configuration interprets colons as a separator. + // details: https://github.com/dotnet/runtime/issues/67616 + + var setterExpression = api.SetterExpression; + if (setterExpression.StartsWith("global::", StringComparison.Ordinal)) + setterExpression = setterExpression["global::".Length..]; + + if (Apis.TryGetValue(setterExpression, out var result) && result.MatchParameterTypes(api)) + return result; + + return null; + } +} + +public record ApiOptions +{ + public string[] ParameterTypes { get; set; } + public bool? Ignored { get; set; } + public string? OverrideDisplayName { get; set; } + + public bool MatchParameterTypes(ApiData api) + { + return ParameterTypes.SequenceEqual(api.Parameters.Select(p => p.TypeExpression)); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/BuildMagic.BuiltinTaskGenerator.csproj b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/BuildMagic.BuiltinTaskGenerator.csproj new file mode 100644 index 0000000..3810e3e --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/BuildMagic.BuiltinTaskGenerator.csproj @@ -0,0 +1,25 @@ + + + + Exe + net8.0 + enable + 12 + + + + + + + + + + + + + + + + + + diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/DefineConstraint.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/DefineConstraint.cs new file mode 100644 index 0000000..dbcf1cd --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/DefineConstraint.cs @@ -0,0 +1,119 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagic.BuiltinTaskGenerator; + +public abstract class DefineConstraint +{ + private static readonly Type[] Precedence = + { + typeof(DefineConstraintOr), + typeof(DefineConstraintAnd), + typeof(DefineConstraintNot), + typeof(DefineConstraintSymbol) + }; + + // DefineConstraintのToString()を行う際に、意図した評価順にするために演算子の優先度を考慮して括弧をつける必要があり、その判定に使用する + + private int? _priority; + public int Priority => _priority ??= Array.IndexOf(Precedence, GetType()); + + public static DefineConstraint operator !(DefineConstraint a) + { + return new DefineConstraintNot(a); + } + + public static DefineConstraint operator &(DefineConstraint lhs, DefineConstraint rhs) + { + return new DefineConstraintAnd(lhs, rhs); + } + + public static DefineConstraint operator |(DefineConstraint lhs, DefineConstraint rhs) + { + return new DefineConstraintOr(lhs, rhs); + } + + public static implicit operator DefineConstraint(string symbol) + { + return new DefineConstraintSymbol(symbol); + } +} + +internal static class DefineConstraintExtensions +{ + public static DefineConstraint ToDefine(this string symbol) + { + return new DefineConstraintSymbol(symbol); + } +} + +public class DefineConstraintSymbol : DefineConstraint +{ + public DefineConstraintSymbol(string symbol) + { + Symbol = symbol; + } + + private string Symbol { get; } + + public override string ToString() + { + return Symbol; + } +} + +public class DefineConstraintNot : DefineConstraint +{ + public DefineConstraintNot(DefineConstraint operand) + { + Operand = operand; + } + + private DefineConstraint Operand { get; } + + public override string ToString() + { + return Priority > Operand.Priority ? $"!({Operand})" : $"!{Operand}"; + } +} + +public class DefineConstraintAnd : DefineConstraint +{ + public DefineConstraintAnd(DefineConstraint lhs, DefineConstraint rhs) + { + Lhs = lhs; + Rhs = rhs; + } + + private DefineConstraint Lhs { get; } + private DefineConstraint Rhs { get; } + + public override string ToString() + { + var l = Priority > Lhs.Priority ? $"({Lhs})" : $"{Lhs}"; + var r = Priority > Rhs.Priority ? $"({Rhs})" : $"{Rhs}"; + return $"{l} && {r}"; + } +} + +public class DefineConstraintOr : DefineConstraint +{ + public DefineConstraintOr(DefineConstraint lhs, DefineConstraint rhs) + { + Lhs = lhs; + Rhs = rhs; + } + + private DefineConstraint Lhs { get; } + private DefineConstraint Rhs { get; } + + public override string ToString() + { + var l = Priority > Lhs.Priority ? $"({Lhs})" : $"{Lhs}"; + var r = Priority > Rhs.Priority ? $"({Rhs})" : $"{Rhs}"; + return $"{l} || {r}"; + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ParameterData.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ParameterData.cs new file mode 100644 index 0000000..6023147 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/ParameterData.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// AnalysisResult / ApiDataにおけるAPIのパラメータ +/// +/// +/// +/// +public class ParameterData(string typeExpression, string name, bool isOutput) +{ + /// + /// 型を表すC#コードの式 + /// + public string TypeExpression { get; } = typeExpression; + + public string Name { get; } = name; + + /// + /// Getterが返す値 + /// IsOutput=falseのパラメータは、ディクショナリのキーとしてシリアライズする + /// + public bool IsOutput { get; } = isOutput; +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Program.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Program.cs new file mode 100644 index 0000000..d49544b --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Program.cs @@ -0,0 +1,34 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.IO; +using BuildMagic.BuiltinTaskGenerator; +using Microsoft.Build.Locator; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using ZLogger; + +MSBuildLocator.RegisterDefaults(); + +var builder = ConsoleApp.CreateBuilder(args) + .ConfigureServices((context, services) => + { + services.Configure(context.Configuration); + services.AddSingleton(); + services.AddSingleton(provider => + new RepositoryStore(provider.GetService>()!, + Path.Combine(Path.GetTempPath(), "BuildMagic.BuiltinTaskGenerator"))); + services.AddSingleton(); + services.AddSingleton(); + }) + .ConfigureLogging((context, loggingBuilder) => + { + loggingBuilder.ClearProviders() + .SetMinimumLevel(LogLevel.Trace) + .AddZLoggerConsole(); + }); + +var app = builder.Build(); +app.AddCommands(); +app.Run(); diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/README.md b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/README.md new file mode 100644 index 0000000..2bba924 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/README.md @@ -0,0 +1,197 @@ +# Builtin Task Generator + +これは、[UnityCsReference](https://github.com/Unity-Technologies/UnityCsReference)のソースをもとに、`PlayerSettings`や`EditorUserBuildSettings`などに対して設定を行う`IBuildTask`実装を事前生成するツールです。 + +新しいUnityバージョンがリリースされ、[UnityCsReferenceにタグとして追加されたら](https://github.com/Unity-Technologies/UnityCsReference/tags)、本ツールを実行することで新しいバージョンに対する解析を行い、生成コードを更新します。 + + +* [Builtin Task Generator](#builtin-task-generator) + * [基本的な使い方](#基本的な使い方) + * [解析結果のキャッシュ](#解析結果のキャッシュ) + * [Unityバージョン](#unityバージョン) + * [処理対象](#処理対象) + * [現在の設定値の取得](#現在の設定値の取得) + * [生成ルール](#生成ルール) + * [処理対象に関するルール](#処理対象に関するルール) + * [バージョン互換性に関するルール](#バージョン互換性に関するルール) + * [`IProjectSettingApplier`実装に関するルール](#iprojectsettingapplier実装に関するルール) + * [タスクパラメータ型に関するルール](#タスクパラメータ型に関するルール) + * [シリアライズできない型の例外的対応](#シリアライズできない型の例外的対応) + * [設定](#設定) + * [`Apis`](#apis) + * [`Ignored`](#ignored) + * [`OverrideDisplayName`](#overridedisplayname) + * [`DictionaryKeyTypes`](#dictionarykeytypes) + + +## 基本的な使い方 + +`generate`サブコマンドを使用します。`-o`オプションで生成ファイル (`.cs`) の出力先ディレクトリを指定します。 + +```shell +dotnet run -- generate -o ../../Packages/jp.co.cyberagent.buildmagic/BuildMagic/Editor/BuiltIn/Generated +``` + +## 解析結果のキャッシュ + +本ツールは、UnityCsReferenceの各リリースバージョンに対して解析を行い、バージョン間の互換性をできるだけ維持できるようにコードを生成します。 +一度解析を行ったバージョンの解析結果は、ローカルにキャッシュし次回以降の解析で再利用することで高速化を図ります。 +キャッシュは以下のディレクトリに保存されます(macOSの場合)。 + +``` +~/Library/Application Support/BuildMagic.BuiltinTaskGenerator/library +``` + +> [!NOTE] +> このパスは`Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)`に基づきます。 + +キャッシュを無視してすべてのバージョンの解析を行う場合は、`-f`オプションを指定してください。 + +## Unityバージョン + +Unity 2022.3.0f1以降のリリース(`f`)バージョンを処理対象としています。 + +## 処理対象 +現時点での処理対象クラスは以下の通りです。 (かっこ内はエディタラベル解析に使用するクラス) + +- `UnityEngine.PlayerSettings` (`UnityEditor.PlayerSettingsEditor`) +- `UnityEditor.EditorUserBuildSettings` + +## 現在の設定値の取得 + +プロジェクトの現在の設定値が取得できる場合は、Configuration型に対し`BuildMagicEditor.IProjectSettingApplier`の実装を生成します。 + +## 生成ルール + +### 処理対象に関するルール + +- `static`かつ`public`かつジェネリックでないメソッド・プロパティのみを処理します。 +- `set`アクセサのないプロパティは無視します。 +- メソッド名の先頭が`Set`でないメソッドは無視します。 +- パラメータ型にシリアライズできない型が含まれる項目は原則として無視します。 + - [一部、例外的に対応している型があります。](#シリアライズできない型の例外的対応) +- その項目が`[Obsolete]`である場合は、生成されるタスク型にも`[Obsolete]`属性が付与されます。 + - ただし、`[Obsolete(IsError: true)]`の項目は無視します。 +- ネスト型がある場合は、そのネスト型のメンバーも処理します。 +- `appsettings.json`で`Ignored`に指定されている項目は無視します。 + +### バージョン互換性に関するルール + +- 処理対象のUnityバージョンのうち、その項目が初めて登場したバージョンの時点で`[Obsolete]`な項目は無視します。 +- 同じ名前で`[Obsolete]`ではない項目が複数存在する場合(オーバーロードなど)は、それ以前のバージョンですでに登場している項目を優先します。 + - それでも優先できる項目がなければ、その名前の項目に対する処理はスキップします。 + +### `IProjectSettingApplier`実装に関するルール + +- プロパティに`get`アクセサがある場合は、それを使用します。 +- メソッド`SetHoge()`に対して`GetHoge()`が存在する場合は、それを使用します。 + - 引数の型と戻り値の型をみてオーバーロードのマッチングを行います。マッチングできない場合はスキップします。 + +### タスクパラメータ型に関するルール + +- プロパティ・メソッドの引数をパラメータとして扱います。 +- パラメータが複数存在する場合は、タプルとして扱います。 +- `IProjectSettingApplier`で使用するゲッターにおいて入力すべきパラメータは、タスクパラメータ型においてディクショナリ (`IReadOnlyDictionary<,>`) のキーとして扱い、その他のパラメータをバリューとして扱います。 + - キーとなるパラメータが複数存在する場合は、`appsettings.json`の[`DictionaryKeyTypes`](#dictionarykeytypes)に指定された順番にキーを選択します。 + - タスク実行時には、それぞれのキーに対して値を設定します。 + - バリューとなるパラメータが複数存在する場合は、タプルとして扱います。 + +#### シリアライズできない型の例外的対応 + +- 一部、直接的にはシリアライズできないが、他のシリアライズ可能な型との相互型変換が行える型については、その型をパラメータ型として扱います。 + - 変換が可能な型のペアは`BuiltinSerializationWrapperRegistry.cs`にハードコードされています。 + + +## 設定 +`appsettings.json`を編集して、コード生成の詳細な設定を行えます。 + +```json +{ + "Apis": { + "UnityEditor.PlayerSettings.SetScriptingDefineSymbols({0}, {1});": { + "ParameterTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::System.String" + ], + "Ignored": true + }, + /* ... */ + }, + "DictionaryKeyTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::UnityEditor.BuildTarget", + "global::UnityEditor.BuildTargetGroup", + /* ... */ + ] +} +``` + +### `Apis` + +各APIごとの設定を行います。 + +キーには[解析結果のキャッシュ](#解析結果のキャッシュ)のJSONファイルに記載されている`SetterExpression`を指定します。 +また、オーバーロードを一意に定めるため、`ParameterTypes`に各パラメータ型のフルネームを指定する必要があります。こちらも同じく解析結果のキャッシュから正しい型表現が得られます。 + +> [!IMPORTANT] +> `SetterExpression`は、実装上の都合により、先頭の`global::`を除いたものを指定する必要があります。 + +```json +{ + "Apis": { + "UnityEditor.PlayerSettings.SetIl2CppCompilerConfiguration({0}, {1});": { + "ParameterTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::UnityEditor.Il2CppCompilerConfiguration" + ], + "Ignored": true, + "OverrideDisplayName": "PlayerSettings: C++ Compiler Configuration (IL2CPP)" + } + } +} +``` + +#### `Ignored` + +`true`を指定すると、生成対象から除外されます。 + +#### `OverrideDisplayName` + +生成されるタスク型の表示名を上書きします。 + +### `DictionaryKeyTypes` + +これは、パラメータ型のうち、ディクショナリのキーとして扱う型の優先順位を指定します。 + +例えば、`PlayerSettings.SetIcons(NamedBuildTarget, Texture2D[], IconKind)`のようなAPIでは、タスクパラメータの型は次のような構造になります。 + +```ts +type PlayerSettingsSetIconsParameters = { + [buildTarget: NamedBuildTarget]: { + [kind: IconKind]: { + icons: Texture2D[]; + } + } +}; +``` + +これに対し、`DictionaryKeyTypes`を指定することで、ネストの順番を入れ替えられます。 + +```json +{ + "DictionaryKeyTypes": [ + "global::UnityEditor.IconKind", + "global::UnityEditor.Build.NamedBuildTarget" + ] +} +``` + +```ts +type PlayerSettingsSetIconsParameters = { + [kind: IconKind]: { + [buildTarget: NamedBuildTarget]: { + icons: Texture2D[]; + } + } +}; +``` diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/RepositoryStore.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/RepositoryStore.cs new file mode 100644 index 0000000..21c6283 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/RepositoryStore.cs @@ -0,0 +1,52 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.IO; +using LibGit2Sharp; +using Microsoft.Extensions.Logging; +using ZLogger; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// リポジトリのクローンを行う +/// +public class RepositoryStore +{ + private readonly ILogger _logger; + private readonly string _path; + + public RepositoryStore(ILogger logger, string path) + { + _logger = logger; + _path = path; + } + + public Repository GetOrClone(string name, string url, out string path) + { + path = Path.Combine(_path, name); + + Directory.CreateDirectory(path); + + if (Repository.IsValid(path)) + { + _logger.LogInformation($"{name}: {path}"); + } + else + { + _logger.LogInformation($"Cloning {url} into {path}..."); + Repository.Clone(url, path, new CloneOptions + { + OnCheckoutProgress = (s, steps, totalSteps) => + { + _logger.ZLogInformation($"{s} ({steps} / {totalSteps})"); + }, + IsBare = false + }); + _logger.LogInformation("Cloned."); + } + + return new Repository(path); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/TaskData.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/TaskData.cs new file mode 100644 index 0000000..b4f0727 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/TaskData.cs @@ -0,0 +1,565 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.Extensions.Logging; +using ZLogger; + +namespace BuildMagic.BuiltinTaskGenerator +{ + public class TaskData + { + private const int IndentSize = 4; + + private readonly string _expectedName; + private readonly ApiOptions? _options; + private readonly TaskParameterData[] _parameters; + private readonly HashSet<(UnityVersion version, ApiData data)> _versions = new(); + + public TaskData(UnityVersion version, ApiData data, ApiOptions? options) + { + _options = options; + _expectedName = data.ExpectedName; + _parameters = data.Parameters.Select(p => new TaskParameterData(p)).ToArray(); + _versions.Add((version, data)); + LatestVersion = version; + LatestVersionApiData = data; + } + + public UnityVersion LatestVersion { get; private set; } + public ApiData LatestVersionApiData { get; private set; } + + public bool MatchesParameterSignature(IEnumerable parameters) + { + var i = 0; + foreach (var parameterData in parameters) + { + if (!_parameters[i].IsCompatibleWith(parameterData)) return false; + i++; + } + + if (i != _parameters.Length) return false; + + return true; + } + + public void VisitNewerVersion(UnityVersion version, ApiData data) + { + if (version < LatestVersion) throw new InvalidOperationException("Cannot visit older version"); + LatestVersion = version; + LatestVersionApiData = data; + + var i = 0; + foreach (var parameterData in data.Parameters) + { + _parameters[i].VisitNewerVersion(parameterData); + i++; + } + + if (i != _parameters.Length) throw new InvalidOperationException(); + _versions.Add((version, data)); + } + + private static RootWeavedTaskParameter[] WeaveRootParameters(IReadOnlyList parameters, + ReadOnlySpan dictionaryKeyTypes) + { + return WeaveParameters(parameters.Select((p, i) => (p, i)).ToList(), dictionaryKeyTypes).Select(p => + new RootWeavedTaskParameter(p.result, p.source.Name, p.source.FormerlySerializedAsNames)).ToArray(); + } + + private static IEnumerable<(WeavedTaskParameter result, TaskParameterData source)> WeaveParameters( + IReadOnlyList<(TaskParameterData parameter, int index)> parameters, ReadOnlySpan dictionaryKeyTypes) + { + // GetterとSetterの引数をマッチングして、設定値のキーとなる型(NamedBuildTarget)とかを自動判別し、ディクショナリとしてシリアライズする + // キーの優先順位はappsettings.jsonのDictionaryKeyTypesに記載された順番に従う + + if (parameters.Count == 1) + return + [ + (new WeavedTaskParameterSingle(parameters[0].parameter, parameters[0].index), + parameters[0].parameter) + ]; + + var keyIndex = parameters.Select(p => p.parameter.TypeExpression).FindIndex(dictionaryKeyTypes); + + if (keyIndex == -1) keyIndex = parameters.FindIndex(p => !p.parameter.IsOutputParameter); + + if (keyIndex >= 0) + { + var key = new WeavedTaskParameterSingle(parameters[keyIndex].parameter, parameters[keyIndex].index); + + var values = parameters.ToList(); + values.RemoveAt(keyIndex); + + var weavedValues = WeaveParameters(values, dictionaryKeyTypes).ToArray(); + + if (weavedValues.Length == 0) throw new InvalidOperationException(); + + WeavedTaskParameter value; + if (weavedValues.Length == 1) + value = weavedValues[0].result; + else + value = new WeavedTaskParameterTuple(weavedValues.Select(v => (v.result, v.source.Name)).ToList()); + + return [(new WeavedTaskParameterDictionary(key, value), parameters[keyIndex].parameter)]; + } + + return parameters.Select(p => + ((WeavedTaskParameter)new WeavedTaskParameterSingle(p.parameter, p.index), p.parameter)); + } + + public void Generate(StringBuilder sourceBuilder, UnityVersionDefineConstraintBuilder defineBuilder, + ILogger logger, ReadOnlySpan dictionaryKeyTypes) + { + var versionDefine = defineBuilder.Get(_versions.Select(v => v.version), out var range, out _)?.ToString(); + + var weavedRootParameters = WeaveRootParameters(_parameters, dictionaryKeyTypes); + + sourceBuilder.AppendLine( + $"// {string.Join(", ", range.Segments.Select(s => $"[{s.Since} - {(s.Until == defineBuilder.Latest ? "(latest)" : s.Until == defineBuilder.GetLatestRevision(s.Until) ? $"({s.Until.Major}.{s.Until.Minor} latest)" : s.Until)}]"))}"); + if (versionDefine != null) + // このタスクのversion defines + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + #if {{versionDefine}} + """); + + // TODO: パラメータクラス生成処理のSource Generatorとの共通化が必要。現状PlayerSettingsにFormerlySerializedAsを要する項目がないので、スキップしている + var hasFormerSerializationNames = weavedRootParameters.Any(p => p.FormerSerializationNames.Any()); + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + [global::BuildMagicEditor.GenerateBuildTaskAccessories(@"{{_options?.OverrideDisplayName ?? LatestVersionApiData.DisplayName}}", PropertyName = @"{{LatestVersionApiData.PropertyName}}")] + """); + + // Obsoleteな項目はTaskにもObsoleteをつける + var obsoleteVersionDefine = + defineBuilder.Get(_versions.Where(v => v.data.IsObsolete).Select(v => v.version), + out var isNeverObsolete); + if (!isNeverObsolete && obsoleteVersionDefine != null) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + #if {{obsoleteVersionDefine.ToString()}} + [global::System.Obsolete] + #endif + """); + + // Task本体 + + // コンストラクタ + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + public class {{_expectedName}}Task : global::BuildMagicEditor.BuildTaskBase + { + public {{_expectedName}}Task({{string.Join(", ", weavedRootParameters.Select(p => $"{p.Item.ToTypeExpression()} {p.FieldName}"))}}) + { + """); + + foreach (var parameter in weavedRootParameters) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + this.{{parameter.FieldName}} = {{parameter.FieldName}}; + """); + + // Run + + sourceBuilder.AppendLine( +/* lang=c# */""" + } + + public override void Run(global::BuildMagicEditor.IPreBuildContext context) + { + """); + + var isFirst = true; + foreach (var grouping in _versions.GroupBy(v => v.data.SetterExpression)) + { + // バージョンごとにSetter Expressionが異なる場合はdefine symbolを切る + var setterExpressionVersionDefine = + defineBuilder.Get(grouping.Select(g => g.version), out _)?.ToString(); + + if (setterExpressionVersionDefine == versionDefine) setterExpressionVersionDefine = null; + + if (setterExpressionVersionDefine != null) + { + if (isFirst) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + #if {{setterExpressionVersionDefine}} + """); + else + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + #elif {{setterExpressionVersionDefine}} + """); + + isFirst = false; + } + else + { + if (!isFirst) throw new InvalidOperationException(); + } + + try + { + var cursor = grouping.Key; + var localCounter = 0; + for (var i = 0; i < weavedRootParameters.Length; i++) + cursor = weavedRootParameters[i].ExtractParameter(cursor, ref localCounter); + + sourceBuilder.AppendLine(cursor); + } + catch (FormatException) + { + logger.ZLogError( + $"Failed to format setter expression: {grouping.Key} with {_parameters.Length} parameters {string.Join(", ", _parameters.Select(p => $"this.{p.Name}"))} for version {LatestVersion}"); + throw; + } + } + + if (!isFirst) + sourceBuilder.AppendLine( + /* lang=c# */ + """ #endif"""); + + sourceBuilder.AppendLine( +/* lang=c# */""" }"""); + + // フィールド + + foreach (var parameter in weavedRootParameters) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + private readonly {{parameter.Item.ToTypeExpression()}} {{parameter.FieldName}}; + """); + + sourceBuilder.AppendLine( +/* lang=c# */"""}"""); + + // applier + + var applierVersionDefine = + defineBuilder.Get(_versions.Where(v => v.data.GetterExpression != null).Select(v => v.version), + out var isNeverGathable)?.ToString(); + + if (!isNeverGathable) + { + if (applierVersionDefine == versionDefine) applierVersionDefine = null; + + if (applierVersionDefine != null) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + #if {{applierVersionDefine}} + """); + + // expression groupings + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + partial class {{_expectedName}}TaskConfiguration : global::BuildMagicEditor.IProjectSettingApplier + { + void global::BuildMagicEditor.IProjectSettingApplier.ApplyProjectSetting() + { + """); + + var isFirstGetterExpressionGroup = true; + foreach (var groupingByGetterExpression in _versions.GroupBy(v => v.data.GetterExpression)) + { + var getterExpression = groupingByGetterExpression.Key; + if (string.IsNullOrEmpty(getterExpression)) continue; + + var getterExpressionVersionDefine = + defineBuilder.Get(groupingByGetterExpression.Select(v => v.version), + out _)?.ToString(); + + if (getterExpressionVersionDefine == applierVersionDefine || + getterExpressionVersionDefine == versionDefine) getterExpressionVersionDefine = null; + + if (getterExpressionVersionDefine != null) + { + sourceBuilder.AppendLine( +/* lang=c# */$$""" + #{{(isFirstGetterExpressionGroup ? "if" : "elif")}} {{getterExpressionVersionDefine}} + """); + isFirstGetterExpressionGroup = false; + } + + try + { + var cursor = getterExpression; + var localCounter = 0; + var parameterValueGetter = "this.Value"; + var parameterValueSetter = $"__BUILDMAGIC__{localCounter++}"; + + var needContainer = weavedRootParameters.Length != 1 || weavedRootParameters[0].Item.HasTuple; + + if (needContainer) + { + sourceBuilder.AppendLine($" var {parameterValueSetter} = {parameterValueGetter};"); + sourceBuilder.AppendLine($" {parameterValueSetter} = new();"); + foreach (var parameter in weavedRootParameters) + cursor = parameter + .ToParameter( + new ContainerExpression( + $"{parameterValueGetter}.{parameter.FieldName}", + $"{parameterValueGetter}.{parameter.FieldName}"), + cursor, ref localCounter); + } + else + { + foreach (var parameter in weavedRootParameters) + cursor = parameter + .ToParameter( + new ContainerExpression(parameterValueGetter, $"var {parameterValueSetter}"), + cursor, ref localCounter); + } + + sourceBuilder.AppendLine(cursor); + sourceBuilder.AppendLine($" this.Value = {parameterValueSetter};"); + } + catch (FormatException) + { + logger.ZLogError( + $"Failed to format setter expression: {getterExpression} with {_parameters.Length} parameters {string.Join(", ", _parameters.Select(p => $"this.{p.Name}"))} for version {LatestVersion}"); + throw; + } + } + + if (!isFirstGetterExpressionGroup) + sourceBuilder.AppendLine( + /* lang=c# */ + """#endif"""); + + sourceBuilder.AppendLine( +/* lang=c# */""" + } + } + """); + + if (applierVersionDefine != null) + sourceBuilder.AppendLine( + /* lang=c# */ + """#endif"""); + } + + if (versionDefine != null) + sourceBuilder.AppendLine( + /* lang=c# */ + """#endif"""); + } + + #region Nested type: RootWeavedTaskParameter + + private record RootWeavedTaskParameter( + WeavedTaskParameter Item, + string FieldName, + IEnumerable FormerSerializationNames) + { + public string ExtractParameter(string next, ref int localCounter) + { + return Item.ExtractParameter($"this.{FieldName}", next, ref localCounter); + } + + public string ToParameter(ContainerExpression containerExpression, string next, ref int localCounter) + { + return Item.ToParameter(containerExpression, next, ref localCounter, 2); + } + } + + #endregion + + #region Nested type: WeavedTaskParameter + + private abstract record WeavedTaskParameter + { + public abstract bool HasTuple { get; } + public abstract string ExtractParameter(string containerExpression, string next, ref int localCounter); + + public abstract string ToParameter(ContainerExpression containerExpression, string next, + ref int localCounter, int indent); + + public abstract string ToTypeExpression(); + } + + #endregion + + #region Nested type: WeavedTaskParameterDictionary + + private record WeavedTaskParameterDictionary(WeavedTaskParameter Key, WeavedTaskParameter Value) + : WeavedTaskParameter + { + public override bool HasTuple => Value.HasTuple || Key.HasTuple; + + public override string ExtractParameter(string containerExpression, string next, ref int localCounter) + { + var localKey = $"__BUILDMAGIC__{localCounter++}"; + var localValue = $"__BUILDMAGIC__{localCounter++}"; + return +/* lang=c# */$$""" + foreach (var ({{localKey}}, {{localValue}}) in {{containerExpression}}) + { + {{Key.ExtractParameter(localKey, Value.ExtractParameter(localValue, next, ref localCounter), ref localCounter)}} + } + """; + } + + public override string ToParameter(ContainerExpression containerExpression, string next, + ref int localCounter, int indent) + { + var dictionaryName = $"__BUILDMAGIC__{localCounter++}"; + var localKey = $"__BUILDMAGIC__{localCounter++}"; + var localValue = $"__BUILDMAGIC__{localCounter++}"; + + var indentStr = new string(' ', indent * IndentSize); + + return +/* lang=c# */$$""" + {{indentStr}}var {{dictionaryName}} = {{containerExpression.Getter}}; + {{indentStr}}{{dictionaryName}} = new(); + {{indentStr}}foreach (var ({{localKey}}, {{localValue}}) in {{containerExpression.Getter}}) + {{indentStr}}{ + {{Key.ToParameter(new ContainerExpression(localKey, $""), Value.ToParameter(new ContainerExpression(localValue, $"{dictionaryName}[{localKey}]"), next, ref localCounter, indent + 1), ref localCounter, indent + 1)}} + {{indentStr}}} + {{indentStr}}{{containerExpression.Setter}} = {{dictionaryName}}; + """; + } + + public override string ToTypeExpression() + { + return + $"global::System.Collections.Generic.IReadOnlyDictionary<{Key.ToTypeExpression()}, {Value.ToTypeExpression()}>"; + } + } + + #endregion + + #region Nested type: WeavedTaskParameterSingle + + private record WeavedTaskParameterSingle(TaskParameterData Parameter, int Index) : WeavedTaskParameter + { + public override bool HasTuple => false; + + public override string ExtractParameter(string containerExpression, string next, ref int localCounter) + { + return $" {next.Replace($"{{{Index.ToString()}}}", $"{containerExpression}")}"; + } + + public override string ToParameter(ContainerExpression containerExpression, string next, + ref int localCounter, int indent) + { + return + $"{new string(' ', indent * IndentSize)}{next.Replace($"{{{Index.ToString()}}}", Parameter.IsOutputParameter ? containerExpression.Setter : containerExpression.Getter)}"; + } + + public override string ToTypeExpression() + { + return Parameter.TypeExpression; + } + } + + #endregion + + #region Nested type: WeavedTaskParameterTuple + + private record WeavedTaskParameterTuple(IReadOnlyList<(WeavedTaskParameter item, string name)> Items) + : WeavedTaskParameter + { + public override bool HasTuple => true; + + public override string ExtractParameter(string containerExpression, string next, ref int localCounter) + { + var cursor = next; + foreach (var (item, name) in Items) cursor = item.ExtractParameter(name, cursor, ref localCounter); + + return +/* lang=c# */$$""" + var ({{string.Join(", ", Items.Select(i => i.name))}}) = {{containerExpression}}; + {{cursor}} + """; + } + + public override string ToParameter(ContainerExpression containerExpression, string next, + ref int localCounter, int indent) + { + var cursor = next; + foreach (var (item, name) in Items) + cursor = item.ToParameter(new ContainerExpression($"", $"var {name}"), cursor, + ref localCounter, indent); + + return +/* lang=c# */$$""" + {{cursor}} + {{new string(' ', indent * IndentSize)}}{{containerExpression.Setter}} = ({{string.Join(", ", Items.Select(i => i.name))}}); + """; + } + + public override string ToTypeExpression() + { + return $"({string.Join(", ", Items.Select(i => $"{i.item.ToTypeExpression()} {i.name}"))})"; + } + } + + #endregion + } + + public record struct ContainerExpression + { + public ContainerExpression(string getter, string setter) + { + Getter = getter; + Setter = setter; + } + + public string Getter { get; } + public string Setter { get; } + } + + public class TaskParameterData + { + private readonly HashSet _formerlySerializedAsNames = new(); + + public TaskParameterData(ParameterData parameterData) + { + TypeExpression = parameterData.TypeExpression; + Name = parameterData.Name; + IsOutputParameter = parameterData.IsOutput; + } + + public string TypeExpression { get; } + + public string Name { get; private set; } + + public bool IsOutputParameter { get; } + + public IEnumerable FormerlySerializedAsNames => _formerlySerializedAsNames; + + public bool IsCompatibleWith(ParameterData parameterData) + { + // TODO: MovedFrom等の考慮? + if (parameterData.IsOutput != IsOutputParameter) return false; + return TypeExpression == parameterData.TypeExpression; + } + + public void VisitNewerVersion(ParameterData parameterData) + { + if (!IsCompatibleWith(parameterData)) throw new InvalidOperationException("Incompatible parameter data"); + if (Name != parameterData.Name) + { + _formerlySerializedAsNames.Add(Name); + Name = parameterData.Name; + } + } + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityApiAnalyzer.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityApiAnalyzer.cs new file mode 100644 index 0000000..7e88916 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityApiAnalyzer.cs @@ -0,0 +1,420 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.MSBuild; +using Microsoft.Extensions.Logging; +using ZLogger; +using ILogger = Microsoft.Build.Framework.ILogger; + +namespace BuildMagic.BuiltinTaskGenerator; + +public class UnityApiAnalyzer( + ILogger logger, + ILogger msBuildLogger, + UnityCsReferenceRepository unityCsReferenceRepository, + AnalysisLibrary library) +{ + private static readonly SymbolDisplayFormat DisplayFormatForType = new( + SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, + SymbolDisplayGenericsOptions.IncludeTypeParameters); + + private CancellationTokenSource _cts; + + /// + /// 各バージョンのUnityCsReferenceからAPIの抽出を行う + /// + /// + /// + /// + /// + public async Task RunAsync(string? netfxBcl, Func versionFilter, bool forceAnalyze, + CancellationToken ct) + { + if (!string.IsNullOrEmpty(netfxBcl)) logger.ZLogInformation($"Using .NET Framework BCL: {netfxBcl}"); + + // Unity 2021以前のUnityCsReferenceに含まれるcsprojは.NET Framework 4.7.1 Development Packを要求してくる + // .NET Framework Development PackはWindows向けにしか提供されていないが、このようにプロジェクトのプロパティをオーバーライドして + // Mono等のBCLを指定することで、Windows以外でも解析を通すことができる + using var workspace = string.IsNullOrEmpty(netfxBcl) + ? MSBuildWorkspace.Create() + : MSBuildWorkspace.Create(new Dictionary + { + { + "_TargetFrameworkDirectories", + $"{netfxBcl};{netfxBcl}/Facades" + }, + { + "_FullFrameworkReferenceAssemblyPaths", + $"{netfxBcl};{netfxBcl}/Facades" + } + }); + + unityCsReferenceRepository.Fetch(); + + foreach (var version in unityCsReferenceRepository.GetVersions() + .Where(versionFilter).Order()) + { + var (result, isNew) = await library.GetForVersionAsync(version, forceAnalyze, ct); + + if (!isNew) continue; + + logger.ZLogInformation($"Processing: {version}"); + + unityCsReferenceRepository.Checkout(version); + + var solutionPath = + Path.Combine(unityCsReferenceRepository.Path, "Projects", "CSharp", "UnityReferenceSource.sln"); + + logger.ZLogInformation($"Opening solution: {solutionPath}"); + var solution = await workspace.OpenSolutionAsync(solutionPath, new MSBuildLogger(msBuildLogger), null, ct); + + logger.ZLogInformation($"Solution loaded: {solution.Projects.Count()} projects"); + + foreach (var project in solution.Projects) + { + if (project.Name is not "UnityEditor") + { + logger.ZLogInformation($"Skipping project: {project.Name}"); + continue; + } + + logger.ZLogInformation($"Processing project: {project.Name}"); + + var compilation = await project.GetCompilationAsync(ct); + + if (compilation == null) + { + logger.ZLogWarning($"Compilation for {project.Name} is null."); + continue; + } + + if (compilation is CSharpCompilation cSharpCompilation) + logger.ZLogInformation($"LanguageVersion: {cSharpCompilation.LanguageVersion.ToString()}"); + + + var numErrors = compilation.GetDiagnostics().Count(d => d.Severity == DiagnosticSeverity.Error); + + if (numErrors > 0) + { + foreach (var diagnostic in compilation.GetDiagnostics() + .Where(d => d.Severity == DiagnosticSeverity.Error)) + logger.ZLogInformation($"{diagnostic}"); + logger.ZLogWarning($"{numErrors} compilation errors"); + } + + var playerSettingsEditor = compilation.GetTypeByMetadataName("UnityEditor.PlayerSettingsEditor"); + + if (playerSettingsEditor == null) + { + logger.ZLogWarning($"Failed to find UnityEditor.PlayerSettingsEditor"); + continue; + } + + var playerSettings = compilation.GetTypeByMetadataName("UnityEditor.PlayerSettings"); + + if (playerSettings == null) + { + logger.ZLogWarning($"Failed to find UnityEditor.PlayerSettings"); + continue; + } + + var editorUserBuildSettings = compilation.GetTypeByMetadataName("UnityEditor.EditorUserBuildSettings"); + + if (editorUserBuildSettings == null) + { + logger.ZLogWarning($"Failed to find UnityEditor.EditorUserBuildSettings"); + continue; + } + + var editorGuiAnalyzer = new UnityEditorGuiAnalyzer(compilation, playerSettingsEditor); + + foreach (var (key, value) in editorGuiAnalyzer.LoweredPropertyNameAndLabel) + logger.ZLogInformation($"{key}: {value}"); + + var categories = result.Categories; + if (!categories.TryGetValue("PlayerSettings", out var playerSettingsCategory)) + playerSettingsCategory = + categories["PlayerSettings"] = new ApiCategory(); + + if (!categories.TryGetValue("EditorUserBuildSettings", out var editorUserBuildSettingsCategory)) + editorUserBuildSettingsCategory = + categories["EditorUserBuildSettings"] = new ApiCategory(); + + ProcessType(playerSettingsCategory, playerSettings, "", ""); + ProcessType(editorUserBuildSettingsCategory, editorUserBuildSettings, "", ""); + + void ProcessType(ApiCategory category, ITypeSymbol type, string expectedName, string propertyName) + { + var typeExpression = type.ToDisplayString(DisplayFormatForType); + + var name = ToTitleCase(type.Name); + expectedName += name; + propertyName = !string.IsNullOrEmpty(propertyName) ? $"{propertyName}.{name}" : name; + + var typeObsoleteAttribute = type.GetAttributes() + .FirstOrDefault(attr => + { + if (attr.AttributeClass is IErrorTypeSymbol errorType) + return errorType.MetadataName is "Obsolete" or "ObsoleteAttribute"; + return attr.AttributeClass?.MetadataName == "ObsoleteAttribute"; + }); + + if (typeObsoleteAttribute is { ConstructorArguments.Length: 2 } && + (bool)typeObsoleteAttribute.ConstructorArguments[1].Value) + // obsolete with error=true + return; + + var isTypeObsolete = typeObsoleteAttribute != null; + + var publicMembers = type.GetMembers() + .Where(m => m.DeclaredAccessibility == Accessibility.Public); + + foreach (var member in publicMembers) + { + if (member is ITypeSymbol nestedType) + ProcessType(category, nestedType, expectedName, propertyName); + + if (!member.IsStatic) continue; + + var obsoleteAttribute = member.GetAttributes() + .FirstOrDefault(attr => + { + if (attr.AttributeClass is IErrorTypeSymbol errorType) + return errorType.MetadataName is "Obsolete" or "ObsoleteAttribute"; + return attr.AttributeClass?.MetadataName == "ObsoleteAttribute"; + }); + + if (obsoleteAttribute is { ConstructorArguments.Length: 2 } && + (bool)obsoleteAttribute.ConstructorArguments[1].Value) + // obsolete with error=true + continue; + + var isObsolete = isTypeObsolete || obsoleteAttribute != null; + + ApiData data; + if (member is IMethodSymbol setterMethod) + { + if (setterMethod.MethodKind is MethodKind.PropertyGet or MethodKind.PropertySet || + setterMethod.IsGenericMethod || + !setterMethod.Name.StartsWith("Set")) continue; // メソッドはSetで始まるもののみ + + // 全てのパラメータがシリアライズ可能か + if (setterMethod.Parameters.Any(p => + !p.Type.BuiltinSerializationWrapperExists(out _) && + !TypeUtils.IsSerializableFieldType(p.Type))) continue; + + var setterExpression = + $"{typeExpression}.{setterMethod.Name}({string.Join(", ", Enumerable.Range(0, setterMethod.Parameters.Length).Select(n => $"{{{n}}}"))});"; + + // getter expression + // find getter method + var (getterExpression, outputParameterIndices) = publicMembers.OfType() + .SelectWhereNotNull>>(getter => + { + if (getter.Name != $"Get{setterMethod.Name[3..]}") return null; + + var getterParametersSetterParameterOrdered = + new IParameterSymbol?[setterMethod.Parameters.Length]; + + var setterParameterIndicesGetterParameterOrdered = + new int[getter.Parameters.Length]; + + List outputParameterIndices = new(); + + for (var i = 0; i < getter.Parameters.Length; i++) + { + var getterParameter = getter.Parameters[i]; + var isOut = getterParameter.RefKind is RefKind.Out or RefKind.Ref; + + // finding setter parameter with same type and name as getter parameter + var setterParameterIndex = setterMethod.Parameters.FindIndex(p => + p.Name == getterParameter.Name && + SymbolEqualityComparer.Default.Equals(p.Type, getterParameter.Type)); + + if (setterParameterIndex == -1) return null; + + if (getterParametersSetterParameterOrdered[setterParameterIndex] != null) + return null; + getterParametersSetterParameterOrdered[setterParameterIndex] = getterParameter; + + setterParameterIndicesGetterParameterOrdered[i] = setterParameterIndex; + + if (isOut) outputParameterIndices.Add(setterParameterIndex); + } + + // find return parameter index + var returnParameterIndex = -1; + for (var i = 0; i < getterParametersSetterParameterOrdered.Length; i++) + if (getterParametersSetterParameterOrdered[i] == null) + { + if (returnParameterIndex != -1) + // multiple return values + return null; + + var parameter = setterMethod.Parameters[i]; + + if (SymbolEqualityComparer.Default.Equals(parameter.Type, + getter.ReturnType)) + returnParameterIndex = i; + else + return null; + } + + if (getter.ReturnType.SpecialType != SpecialType.System_Void && + returnParameterIndex == -1) return null; + + if (returnParameterIndex != -1) outputParameterIndices.Add(returnParameterIndex); + + // create expression + var getterExpression = + $"{(returnParameterIndex == -1 ? "" : $"{{{returnParameterIndex}}} = ")}{typeExpression}.{getter.Name}({string.Join(", ", setterParameterIndicesGetterParameterOrdered.Select((i, getterParameterIndex) => { + return getter.Parameters[getterParameterIndex].RefKind switch { + RefKind.Ref => $"ref {{{i}}}", + RefKind.Out => $"out {{{i}}}", + _ => $"{{{i}}}" + }; + }))});"; + + return (getterExpression, outputParameterIndices); + }).FirstOrDefault(); + + var resultPropertyName = $"{propertyName}.{ToTitleCase(setterMethod.Name)}()"; + + if (!editorGuiAnalyzer.LoweredPropertyNameAndLabel.TryGetValue( + setterMethod.Name[3..].ToLowerInvariant(), out var labelName)) + labelName = Utils.ToNiceLabelName(setterMethod.Name.AsSpan()[3..]); + + data = new ApiData($"{expectedName}{ToTitleCase(member.Name)}", resultPropertyName, + $"{propertyName}: {labelName}", isObsolete, + setterExpression, getterExpression); + + for (var i = 0; i < setterMethod.Parameters.Length; i++) + { + var parameter = setterMethod.Parameters[i]; + data.Parameters.Add( + new ParameterData(parameter.Type.ToDisplayString(DisplayFormatForType), + parameter.Name, outputParameterIndices?.Contains(i) ?? true)); + } + } + else if (member is IPropertySymbol property) + { + if (property.SetMethod == null) continue; + if (property.IsIndexer) continue; + if (property.Type is INamedTypeSymbol named && named.IsGenericType) continue; + + // シリアライズ可能か + if (!property.Type.BuiltinSerializationWrapperExists(out _) && + !TypeUtils.IsSerializableFieldType(property.Type)) continue; + + var setterExpression = $"{typeExpression}.{property.Name} = {{0}};"; + var resultPropertyName = $"{propertyName}.{ToTitleCase(member.Name)}"; + + var getterExpression = property.GetMethod == null + ? null + : $"{{0}} = {typeExpression}.{property.Name};"; + + if (!editorGuiAnalyzer.LoweredPropertyNameAndLabel.TryGetValue( + member.Name.ToLowerInvariant(), out var labelName)) + { + // [NativeMethod] attributes can be a hint to serialized property name + var nativeMethodAttr = property.SetMethod.GetAttributes().FirstOrDefault(attr => + attr.AttributeClass.ContainingNamespace.ToString() == + "UnityEngine.Bindings.NativeMethodAttribute"); + + string? nativeName = null; + if (nativeMethodAttr != null) + { + if (nativeMethodAttr.ConstructorArguments.Length >= 1) + nativeName = nativeMethodAttr.ConstructorArguments[0].Value as string; + else + nativeName = nativeMethodAttr.NamedArguments + .FirstOrDefault(a => a.Key == "name").Value.Value as string; + } + + if (nativeName != null && + nativeName.StartsWith("Set", StringComparison.OrdinalIgnoreCase)) + nativeName = nativeName[3..]; + + if (nativeName == null || !editorGuiAnalyzer.LoweredPropertyNameAndLabel.TryGetValue( + nativeName.ToLowerInvariant(), out labelName)) + labelName = Utils.ToNiceLabelName(member.Name); + } + + data = new ApiData($"{expectedName}Set{ToTitleCase(member.Name)}", resultPropertyName, + $"{propertyName}: {labelName}", isObsolete, + setterExpression, getterExpression); + data.Parameters.Add( + new ParameterData(property.Type.ToDisplayString(DisplayFormatForType), property.Name, + true)); + } + else + { + continue; + } + + category.Apis.Add(data); + } + } + } + + await library.SaveAsync(version, ct); + } + } + + /// + /// 先頭だけ大文字にする + /// + /// + /// + private string ToTitleCase(string s) + { + if (s.Length == 0) return s; + if (char.IsUpper(s[0])) return s; + Span nameSpan = stackalloc char[s.Length]; + s.CopyTo(nameSpan); + nameSpan[0] = char.ToUpperInvariant(nameSpan[0]); + return nameSpan.ToString(); + } + + #region Nested type: MSBuildLogger + + public class MSBuildLogger : ILogger + { + private ILogger _logger; + + public MSBuildLogger(ILogger logger) + { + _logger = logger; + } + + #region ILogger Members + + public void Initialize(IEventSource eventSource) + { + //eventSource.ErrorRaised += (sender, args) => { logger.ZLogInformation($"[MSBuild | {Verbosity}] {args.Message}"); }; + //eventSource.AnyEventRaised += (sender, args) => { _logger.ZLogInformation($"[MSBuild | {Verbosity}] {args.Message}"); }; + } + + public void Shutdown() + { + } + + public LoggerVerbosity Verbosity { get; set; } = LoggerVerbosity.Quiet; + public string Parameters { get; set; } + + #endregion + } + + #endregion +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityCsReferenceRepository.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityCsReferenceRepository.cs new file mode 100644 index 0000000..454ead2 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityCsReferenceRepository.cs @@ -0,0 +1,100 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using LibGit2Sharp; +using Microsoft.Extensions.Logging; +using ZLogger; + +namespace BuildMagic.BuiltinTaskGenerator; + +public class UnityCsReferenceRepository : IDisposable +{ + private readonly ILogger _logger; + private readonly string _name; + private readonly string _path; + private readonly Repository _repo; + + public UnityCsReferenceRepository(ILogger logger, RepositoryStore store) + { + var name = "UnityCsReference"; + var url = "https://github.com/Unity-Technologies/UnityCsReference.git"; + _logger = logger; + _name = name; + _repo = store.GetOrClone(name, url, out _path); + _logger.LogInformation($"UnityCsReference: {_path}"); + } + + public string Path => _path; + + #region IDisposable Members + + public void Dispose() + { + _repo.Dispose(); + } + + #endregion + + public IEnumerable GetVersions() + { + foreach (var tag in _repo.Tags) + if (UnityVersion.TryParse(tag.FriendlyName, out var version)) + yield return version; + } + + public void Checkout(UnityVersion version) + { + _logger.LogInformation($"Checking out version {version} for {_name}..."); + var canonicalName = $"refs/tags/{version}"; + Commands.Checkout(_repo, canonicalName, new CheckoutOptions + { + CheckoutModifiers = CheckoutModifiers.Force + }); + } + + public void UpdateSubmodule(string name) + { + _logger.ZLogInformation($"updating submodule: {name}"); + _repo.Submodules.Update(name, new SubmoduleUpdateOptions + { + Init = true, + FetchOptions = + { + OnProgress = message => + { + _logger.ZLogInformation($"{message}"); + return true; + } + }, + OnCheckoutProgress = (path, steps, totalSteps) => + { + _logger.ZLogInformation($"{path} (step {steps} / {totalSteps})"); + } + }); + } + + public void Fetch() + { + var options = new FetchOptions(); + options.TagFetchMode = TagFetchMode.Auto; + + options.OnProgress += message => + { + _logger.LogInformation(message); + return true; + }; + + var remote = _repo.Network.Remotes["origin"]; + var refSpecs = remote.FetchRefSpecs.Select(x => x.Specification); + + _logger.LogInformation($"Fetching {_name}..."); + + Commands.Fetch(_repo, remote.Name, refSpecs, options, "Fetch"); + + _logger.LogInformation("Fetched."); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityEditorGuiAnalyzer.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityEditorGuiAnalyzer.cs new file mode 100644 index 0000000..96f1866 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityEditorGuiAnalyzer.cs @@ -0,0 +1,225 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// エディタコードからラベル等を取得する +/// +public class UnityEditorGuiAnalyzer +{ + private readonly Compilation _compilation; + private readonly Dictionary _fieldAssignments = new(); + private readonly Dictionary _loweredPropertyNameAndLabel = new(); + private readonly HashSet _seenMethods = new(); + + public UnityEditorGuiAnalyzer(Compilation compilation, ITypeSymbol editorType) + { + _compilation = compilation; + + var onEnableMethod = editorType + .GetMembers("OnEnable") + .OfType() + .First(); + + var onInspectorGuiMethod = editorType + .GetMembers("OnInspectorGUI") + .OfType() + .First(); + + AnalyzeMethod(onEnableMethod); + AnalyzeMethod(onInspectorGuiMethod); + } + + public IReadOnlyDictionary LoweredPropertyNameAndLabel => _loweredPropertyNameAndLabel; + + private IOperation? GetInitialValue(IFieldSymbol field) + { + // フィールドの初期値を取得する + var syntax = field + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .SelectMany(s => + { + if (s is FieldDeclarationSyntax fieldDecl) return fieldDecl.Declaration.Variables; + if (s is VariableDeclarationSyntax declaration) return declaration.Variables; + if (s is VariableDeclaratorSyntax declarator) return new[] { declarator }; + return null; + }) + .Select(d => d?.Initializer?.Value).FirstOrDefault(); + + if (syntax == null || !SymbolEqualityComparer.Default.Equals(field.ContainingAssembly, _compilation.Assembly)) + return null; + + var semanticModel = _compilation.GetSemanticModel(syntax.SyntaxTree); + + var root = semanticModel.GetOperation(syntax); + + return root; + } + + private void AnalyzeMethod(IMethodSymbol method) + { + // 再帰ループ防止 + if (!_seenMethods.Add(method)) return; + + var syntax = method + .DeclaringSyntaxReferences + .Select(r => r.GetSyntax()) + .OfType() + .FirstOrDefault(s => s.Body != null); + + if (syntax == null) + return; + + if (!SymbolEqualityComparer.Default.Equals(method.ContainingAssembly, _compilation.Assembly)) + return; + + var semanticModel = _compilation.GetSemanticModel(syntax.SyntaxTree); + + var root = semanticModel.GetOperation(syntax) as IMethodBodyOperation; + + if (root == null) return; + + AnalyzeOperation(method, root); + } + + private IOperation ResolveStoredValue(IOperation operation) + { + // フィールドに代入された値を取得する + switch (operation) + { + case IFieldReferenceOperation fieldReference: + { + var field = fieldReference.Field; + + if (_fieldAssignments.TryGetValue(field, out var value)) return value; + + return GetInitialValue(field) ?? operation; + } + } + + return operation; + } + + private void AnalyzeOperation(IMethodSymbol currentMethod, IOperation operation) + { + // フィールドへの代入を記録 + if (operation is IAssignmentOperation assignment) + if (assignment.Target is IFieldReferenceOperation fieldReference) + { + var field = fieldReference.Field; + _fieldAssignments[field] = assignment.Value; + } + + if (operation is IInvocationOperation invocation) + { + var method = invocation.TargetMethod; + + if (method.Name == "BuildEnumPopup" || + method.ContainingType.ContainingNamespace.ToString() is "UnityEditor" && + method.ContainingType.Name is "EditorGUILayout" or "EditorGUI") + { + // EditorGUI.~~Field()、EditorGUILayout.~~Field()に注目 + if (!method.Name.EndsWith("Field", StringComparison.Ordinal)) return; + + // 引数を取得 + + if (!TryGetParameter(invocation, "property", out var propertyArgument) && + !TryGetParameter(invocation, "prop", out propertyArgument)) + return; + + _ = !TryGetParameter(invocation, "label", out var labelArgument) && + !TryGetParameter(invocation, "uiString", out labelArgument); + + // 引数がフィールドに由来しているなら、それを解決 + + propertyArgument = ResolveStoredValue(propertyArgument); + + labelArgument = labelArgument != null ? ResolveStoredValue(labelArgument) : null; + + // ラベルがローカライズされている場合は EditorGUI.TrTextContent() が呼ばれているので、その引数を展開 + + if (labelArgument is IInvocationOperation { TargetMethod: { Name: "TrTextContent" } } labelInvocation) + { + var args = labelInvocation.Arguments; + if (args.Length >= 1) labelArgument = labelInvocation.Arguments[0].Value; + } + + // プロパティはFindPropertyAssert()で取得されているので、引数を展開してプロパティパスを取得 + + string? propertyName = null; + if (propertyArgument is IInvocationOperation + { + Instance: IInstanceReferenceOperation, TargetMethod: { Name: "FindPropertyAssert" } + } propertyInvocation) + { + var args = propertyInvocation.Arguments; + if (args.Length >= 1 && propertyInvocation.Arguments[0].Value is ILiteralOperation + { + Type: { SpecialType: SpecialType.System_String } + } literal) + propertyName = literal.ConstantValue.Value as string; + } + + string? labelValue = null; + if (labelArgument is ILiteralOperation + { + Type: { SpecialType: SpecialType.System_String } + } labelLiteral) + labelValue = labelLiteral.ConstantValue.Value as string; + + // プロパティパスとラベルを対応づける + + if (propertyName != null) + { + labelValue ??= Utils.ToNiceLabelName(propertyName); + + _loweredPropertyNameAndLabel[propertyName.ToLowerInvariant()] = labelValue; + } + } + else + { + // 同じインスタンスのメソッドを呼んでいる場合は再帰的に解析 + if (invocation.Instance is IInstanceReferenceOperation || + method.IsStatic && method.ContainingType == currentMethod.ContainingType) + AnalyzeMethod(method); + } + + return; + } + + foreach (var childOperation in operation.ChildOperations) AnalyzeOperation(currentMethod, childOperation); + } + + private bool TryGetParameter(IInvocationOperation invocation, string name, + [NotNullWhen(true)] out IOperation? operation) + { + var method = invocation.TargetMethod; + var parameters = method.Parameters + .Select((p, i) => (p, i)); + var labelParameterSet = + parameters.FirstOrDefault(p => p.p.Name == name); + + if (labelParameterSet.p == null) + { + operation = null; + return false; + } + + var labelParameterIndex = labelParameterSet.i; + + // get label argument + operation = invocation.Arguments[labelParameterIndex].Value; + return true; + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersion.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersion.cs new file mode 100644 index 0000000..30810a3 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersion.cs @@ -0,0 +1,131 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagic.BuiltinTaskGenerator; + +public readonly struct UnityVersion : IEquatable, IComparable +{ + private static readonly char[] ReleaseChars = + [ + 'a', // alpha + 'b', // beta + 'f', // final + 'p' // patch + ]; + + public int Major { get; } + public int Minor { get; } + public int Revision { get; } + private int ReleaseTypeIndex { get; } + public char ReleaseType => ReleaseChars[ReleaseTypeIndex]; + public int IncrementalVersion { get; } + + public UnityVersion(int major, int minor, int revision, char releaseType, int incrementalVersion) + { + Major = major; + Minor = minor; + Revision = revision; + ReleaseTypeIndex = Array.IndexOf(ReleaseChars, releaseType); + if (ReleaseTypeIndex == -1) throw new ArgumentOutOfRangeException(nameof(releaseType)); + IncrementalVersion = incrementalVersion; + } + + public static bool TryParse(string version, out UnityVersion result) + { + result = default; + + var parts = version.Split('.'); + if (parts.Length != 3 || !int.TryParse(parts[0], out var major) || + !int.TryParse(parts[1], out var minor)) + return false; + + var releaseSep = parts[2].IndexOfAny(ReleaseChars); + if (releaseSep == -1 || parts[2].Length <= releaseSep + 1) return false; + var release = parts[2][releaseSep]; + + if (!int.TryParse(parts[2][..releaseSep], out var revision) || + !int.TryParse(parts[2][(releaseSep + 1)..], out var incremental)) + return false; + + result = new UnityVersion(major, minor, revision, release, incremental); + return true; + } + + public override string ToString() + { + return $"{Major}.{Minor}.{Revision}{ReleaseChars[ReleaseTypeIndex]}{IncrementalVersion}"; + } + + public bool Equals(UnityVersion other) + { + return Major == other.Major && Minor == other.Minor && Revision == other.Revision && + ReleaseTypeIndex == other.ReleaseTypeIndex && IncrementalVersion == other.IncrementalVersion; + } + + public override bool Equals(object? obj) + { + return obj is UnityVersion other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(Major, Minor, Revision, ReleaseTypeIndex, IncrementalVersion); + } + + public static bool operator ==(UnityVersion left, UnityVersion right) + { + return left.Equals(right); + } + + public static bool operator !=(UnityVersion left, UnityVersion right) + { + return !left.Equals(right); + } + + public static bool operator <(UnityVersion left, UnityVersion right) + { + return left.CompareTo(right) < 0; + } + + public static bool operator >(UnityVersion left, UnityVersion right) + { + return left.CompareTo(right) > 0; + } + + public static bool operator <=(UnityVersion left, UnityVersion right) + { + return left.CompareTo(right) <= 0; + } + + public static bool operator >=(UnityVersion left, UnityVersion right) + { + return left.CompareTo(right) >= 0; + } + + public static implicit operator string(UnityVersion source) + { + return source.ToString(); + } + + public static implicit operator UnityVersion(string source) + { + if (!TryParse(source, out var result)) throw new ArgumentException(); + return result; + } + + public int CompareTo(UnityVersion other) + { + var majorComparison = Major.CompareTo(other.Major); + if (majorComparison != 0) return majorComparison; + var minorComparison = Minor.CompareTo(other.Minor); + if (minorComparison != 0) return minorComparison; + var revisionComparison = Revision.CompareTo(other.Revision); + if (revisionComparison != 0) return revisionComparison; + var releaseTypeComparison = ReleaseTypeIndex.CompareTo(other.ReleaseTypeIndex); + if (releaseTypeComparison != 0) return releaseTypeComparison; + return IncrementalVersion.CompareTo(other.IncrementalVersion); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionDefineConstraintBuilder.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionDefineConstraintBuilder.cs new file mode 100644 index 0000000..5c8202f --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionDefineConstraintBuilder.cs @@ -0,0 +1,247 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BuildMagic.BuiltinTaskGenerator; + +public class UnityVersionDefineConstraintBuilder +{ + private readonly UnityVersion[] _knownVersionsAscending; + + public UnityVersionDefineConstraintBuilder(IEnumerable knownVersions) + { + _knownVersionsAscending = knownVersions.Order().ToArray(); + + if (_knownVersionsAscending.Length == 0) + throw new ArgumentException("At least one version must be provided.", nameof(knownVersions)); + } + + public UnityVersion Earliest => _knownVersionsAscending[0]; + public UnityVersion Latest => _knownVersionsAscending[^1]; + + public UnityVersion GetLatestRevision(UnityVersion version) + { + if (version.Major == Latest.Major && version.Minor == Latest.Minor) return Latest; + + for (var i = Array.IndexOf(_knownVersionsAscending, version) + 1; i < _knownVersionsAscending.Length; i++) + { + var v = _knownVersionsAscending[i]; + if (v.Major != version.Major || v.Minor != version.Minor) return _knownVersionsAscending[i - 1]; + } + + throw new InvalidOperationException(); + } + + private UnityVersionRange ToRange(IEnumerable versions) + { + UnityVersion? currentSince = null; + UnityVersion? currentUntil = null; + + using var versionsIter = versions.GetEnumerator(); + + if (!versionsIter.MoveNext()) return new UnityVersionRange(); + + var result = new UnityVersionRange(); + + foreach (var knownVersion in _knownVersionsAscending) + if (knownVersion == versionsIter.Current) + { + currentSince ??= knownVersion; + currentUntil = knownVersion; + if (!versionsIter.MoveNext()) break; + } + else if (currentSince != null) + { + result += new UnityVersionRangeSegment(currentSince.Value, currentUntil.Value); + currentSince = null; + currentUntil = null; + } + + if (currentSince != null) result += new UnityVersionRangeSegment(currentSince.Value, currentUntil.Value); + + return result; + } + + public DefineConstraint? Get(IEnumerable versions, out bool isNever) + { + return Get(versions, out _, out isNever); + } + + public DefineConstraint? Get(IEnumerable versions, out UnityVersionRange range, out bool isNever) + { + range = ToRange(versions); + + if (!range.Segments.Any()) + { + // empty + isNever = true; + return null; + } + + isNever = false; + + DefineConstraint? allConstraint = null; + foreach (var segment in range.Segments) + { + var start = segment.Since; + var end = segment.Until; + + // 最低サポートverで有効なので、それ以前のバージョンでも有効になるようにする + var firstAvailable = start == Earliest; + + // 最新verで有効なので、それ以降のバージョンでも有効になるようにする + var latestAvailable = end == Latest; + + var minorLocal = start.Major == end.Major && start.Minor == end.Minor; + + DefineConstraint? constraint = null; + + var startIndex = Array.IndexOf(_knownVersionsAscending, start); + var endIndex = Array.IndexOf(_knownVersionsAscending, end); + + // 最新ではないminorにおける最新のrevisionは、今後追加されるrevisionに対して有効にする + // 生成コードの差分が減り、新しいUnityリリースに対応しやすくなる + // (e.g. 7000.0.xがすでに出ているが、6000.1.x系にまだ更新がきていて、最新6000.1.xにおいて有効だが7000.0.0において無効な項目は、将来の6000.1.xバージョンに対しては項目が有効になるようにする) + + var latestRevisionInPrevMinor = !latestAvailable && _knownVersionsAscending[endIndex + 1].Revision == 0; + + if (minorLocal && !latestAvailable && !firstAvailable) + { + var needNewerFlag = latestRevisionInPrevMinor; + + var activeInMinor = _knownVersionsAscending.Count(c => start <= c && c <= end); + var allMinor = _knownVersionsAscending.Count(c => c.Major == start.Major && c.Minor == start.Minor); + var inactiveInMinor = allMinor - activeInMinor; + + var useNewerFlag = needNewerFlag || inactiveInMinor < activeInMinor; // prefer short form + + if (useNewerFlag) + { + var c = $"UNITY_{start.Major}_{start.Minor}_OR_NEWER".ToDefine(); + var nextMinorIndex = -1; + for (var i = endIndex + 1; i < _knownVersionsAscending.Length; i++) + { + var v = _knownVersionsAscending[i]; + if (v.Major != end.Major || v.Minor != end.Minor) + { + nextMinorIndex = i; + break; + } + + c &= !$"UNITY_{v.Major}_{v.Minor}_{v.Revision}".ToDefine(); + } + + if (nextMinorIndex != -1) + { + var nextMinor = _knownVersionsAscending[nextMinorIndex]; + c &= !$"UNITY_{nextMinor.Major}_{nextMinor.Minor}_OR_NEWER".ToDefine(); + } + + // negate inactive versions before start in the same minor + for (var i = startIndex - 1; i > 0; i--) + { + var v = _knownVersionsAscending[i]; + if (v.Major != start.Major || v.Minor != start.Minor) + break; + c &= !$"UNITY_{v.Major}_{v.Minor}_{v.Revision}".ToDefine(); + } + + constraint = c; + } + else + { + for (var i = startIndex; i <= endIndex; i++) + { + var ver = _knownVersionsAscending[i]; + + var c = $"UNITY_{ver.Major}_{ver.Minor}_{ver.Revision}".ToDefine(); + + if (constraint == null) + constraint = c; + else + constraint |= c; + } + } + } + else + { + if (!latestAvailable) + { + var nextVerIndex = Array.IndexOf(_knownVersionsAscending, end) + 1; + var nextVer = _knownVersionsAscending[nextVerIndex]; + + if (latestRevisionInPrevMinor) + { + constraint = !$"UNITY_{nextVer.Major}_{nextVer.Minor}_OR_NEWER".ToDefine(); + } + else + { + // マイナーバージョンの途中で終わっている + // この場合、endの次のバージョン以降のマイナーバージョンを除外する方法だと、新しいマイナーバージョンがリリースされた場合に対応できない + // endまでのマイナーバージョンをすべて含める方法にする + + constraint = !$"UNITY_{end.Major}_{end.Minor}_OR_NEWER".ToDefine(); + + DefineConstraint? individualVersions = null; + + for (var i = endIndex; i >= 0; i--) + { + var ver = _knownVersionsAscending[i]; + if (ver.Major != end.Major || ver.Minor != end.Minor) break; + if (ver < start) break; + + var c = $"UNITY_{ver.Major}_{ver.Minor}_{ver.Revision}".ToDefine(); + + if (individualVersions == null) + individualVersions = c; + else + individualVersions |= c; + } + + if (individualVersions != null) constraint |= individualVersions; + } + } + + if (!firstAvailable) + { + // startから有効化 + + var low = $"UNITY_{start.Major}_{start.Minor}_OR_NEWER".ToDefine(); + if (constraint != null) + constraint &= low; + else + constraint = low; + + if (start.Revision != 0) + { + // XXXX.Y.ZZ + + DefineConstraint? individualVersions = null; + + for (var i = startIndex - 1; i >= 0; i--) + { + var ver = _knownVersionsAscending[i]; + if (ver.Major != start.Major || ver.Minor != start.Minor) break; + var c = !$"UNITY_{ver.Major}_{ver.Minor}_{ver.Revision}".ToDefine(); + if (individualVersions == null) + individualVersions = c; + else + individualVersions &= c; + } + + if (individualVersions != null) constraint &= individualVersions; + } + } + } + + if (allConstraint == null) allConstraint = constraint; + else allConstraint |= constraint; + } + + return allConstraint; + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionRange.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionRange.cs new file mode 100644 index 0000000..651d67f --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/UnityVersionRange.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; +using System.Linq; + +namespace BuildMagic.BuiltinTaskGenerator; + +/// +/// Unityのバージョン範囲(単一の区間)を表す +/// +/// +/// +public record UnityVersionRangeSegment(UnityVersion Since, UnityVersion Until) // both inclusive +{ + /// + /// 可能な場合は範囲を結合する + /// + /// + /// + /// + public bool TryUnion(UnityVersionRangeSegment other, out UnityVersionRangeSegment result) + { + result = default; + if (Until < other.Since || other.Until < Since) return false; + + result = new UnityVersionRangeSegment( + Since < other.Since ? Since : other.Since, + Until > other.Until ? Until : other.Until + ); + return true; + } + + public static implicit operator UnityVersionRange(UnityVersionRangeSegment src) + { + return new UnityVersionRange(src); + } +} + +/// +/// Unityのバージョン範囲(複数の区間)を表す +/// +public class UnityVersionRange +{ + private List _segments = new(); + + public UnityVersionRange() + { + } + + public UnityVersionRange(UnityVersionRangeSegment segment) + { + _segments.Add(segment); + } + + public IEnumerable Segments => _segments; + + /// + /// 和集合をとる + /// + /// + /// + /// + public static UnityVersionRange operator +(UnityVersionRange lhs, UnityVersionRange rhs) + { + var result = new UnityVersionRange(); + result._segments.AddRange(lhs._segments); + + foreach (var segOther in rhs._segments) + { + var cursor = segOther; + + for (var i = 0; i < result._segments.Count; i++) + { + var seg = result._segments[i]; + if (seg.TryUnion(cursor, out var newSeg)) + { + result._segments.RemoveAt(i); + i--; + + cursor = newSeg; + } + } + + result._segments.Add(cursor); + } + + result._segments = result._segments.OrderBy(s => s.Since).ToList(); + + return result; + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Utils.cs b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Utils.cs new file mode 100644 index 0000000..f9db98b --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/Utils.cs @@ -0,0 +1,98 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace BuildMagic.BuiltinTaskGenerator; + +public class Utils +{ + private static bool IsAlphaNumeric(char c) + { + return c is >= '0' and <= '9' or >= 'a' and <= 'z' or >= 'A' and <= 'Z'; + } + + private static bool StartsWithKnownName(ReadOnlySpan name, out int skipped) + { + ReadOnlySpan knownNames = ["iOS", "iPad", "iPod", "iPhone", "visionOS", "x86", "x64", "ARM", "Il2Cpp"]; + + foreach (var knownName in knownNames) + if (name.StartsWith(knownName, StringComparison.Ordinal)) + { + skipped = knownName.Length; + return true; + } + + skipped = 0; + return false; + } + + public static string ToNiceLabelName(ReadOnlySpan src) + { + Span buffer = stackalloc char[1024]; + + var current = buffer; + + var prevDivided = false; + + var prev = ' '; + + while (current.Length > 0 && src.Length > 0) + { + var prevDividedCurrent = prevDivided; + prevDivided = false; + + if (prevDividedCurrent) + { + current[0] = prev = ' '; + current = current[1..]; + } + + if (StartsWithKnownName(src, out var skipped)) + { + if (prev != ' ') + { + current[0] = prev = ' '; + current = current[1..]; + } + + src[..skipped].CopyTo(current); + src = src[skipped..]; + current = current[skipped..]; + prevDivided = true; + continue; + } + + var c = src[0]; + + var isUpper = c is >= 'A' and <= 'Z'; + var isDigit = c is >= '0' and <= '9'; + var prevUpper = prev is >= 'A' and <= 'Z'; + // var nextLower = src.Length >= 2 && src[1] is >= 'a' and <= 'z'; + var prevDigit = prev is >= '0' and <= '9'; + + if (prev != ' ' && + (isDigit && !prevDigit && !prevUpper || isUpper && !prevUpper /* || isUpper && prevUpper && nextLower*/ + )) + { + current[0] = prev = ' '; + current = current[1..]; + } + + if (prev == ' ' && c is >= 'a' and <= 'z') c = (char)(c - (char)('a' - 'A')); + + current[0] = prev = c; + current = current[1..]; + src = src[1..]; + } + + var length = + (int)Unsafe.ByteOffset(ref MemoryMarshal.GetReference(buffer), ref MemoryMarshal.GetReference(current)) / + sizeof(char); + + return buffer[..length].ToString(); + } +} diff --git a/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/appsettings.json b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/appsettings.json new file mode 100644 index 0000000..484a6d6 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.BuiltinTaskGenerator/appsettings.json @@ -0,0 +1,103 @@ +{ + "Apis": { + "UnityEditor.PlayerSettings.SetScriptingDefineSymbols({0}, {1});": { + "ParameterTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::System.String" + ], + "Ignored": true + }, + "UnityEditor.PlayerSettings.SetIl2CppCompilerConfiguration({0}, {1});": { + "ParameterTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::UnityEditor.Il2CppCompilerConfiguration" + ], + "OverrideDisplayName": "PlayerSettings: C++ Compiler Configuration (IL2CPP)" + }, + "UnityEditor.PlayerSettings.Android.buildApkPerCpuArchitecture = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.Android: Split APKs by target architecture" + }, + "UnityEditor.PlayerSettings.Android.androidIsGame = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.Android: Android Game" + }, + "UnityEditor.PlayerSettings.Android.minSdkVersion = {0};": { + "ParameterTypes": [ + "global::UnityEditor.AndroidSdkVersions" + ], + "OverrideDisplayName": "PlayerSettings.Android: Minimum API Level" + }, + "UnityEditor.PlayerSettings.Android.targetSdkVersion = {0};": { + "ParameterTypes": [ + "global::UnityEditor.AndroidSdkVersions" + ], + "OverrideDisplayName": "PlayerSettings.Android: Target API Level" + }, + "UnityEditor.PlayerSettings.Android.useAPKExpansionFiles = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.Android: Split Application Binary" + }, + "UnityEditor.PlayerSettings.Android.forceSDCardPermission = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.Android: Write Permission (Force SD Card)" + }, + "UnityEditor.PlayerSettings.Android.startInFullscreen = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.Android: Hide Navigation Bar" + }, + "UnityEditor.PlayerSettings.vulkanEnablePreTransform = {0};" : { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings: Apply display rotation during rendering (Android)" + }, + "UnityEditor.PlayerSettings.iOS.appleEnableAutomaticSigning = {0};": { + "ParameterTypes": [ + "global::System.Boolean" + ], + "OverrideDisplayName": "PlayerSettings.iOS: Automatically Sign" + }, + "UnityEditor.PlayerSettings.iOS.appleDeveloperTeamID = {0};": { + "ParameterTypes": [ + "global::System.String" + ], + "OverrideDisplayName": "PlayerSettings.iOS: Signing Team ID" + }, + "UnityEditor.PlayerSettings.iOS.sdkVersion = {0};": { + "ParameterTypes": [ + "global::UnityEditor.iOSSdkVersion" + ], + "OverrideDisplayName": "PlayerSettings.iOS: Target SDK" + }, + "UnityEditor.PlayerSettings.iOS.targetOSVersionString = {0};": { + "ParameterTypes": [ + "global::System.String" + ], + "OverrideDisplayName": "PlayerSettings.iOS: Target minimum iOS Version" + } + }, + "DictionaryKeyTypes": [ + "global::UnityEditor.Build.NamedBuildTarget", + "global::UnityEditor.BuildTarget", + "global::UnityEditor.BuildTargetGroup", + "global::UnityEditor.IconKind", + "global::UnityEngine.LogType", + "global::UnityEditor.AspectRatio", + "global::UnityEditor.iOSLaunchScreenImageType", + "global::UnityEditor.PlayerSettings.WSAImageType", + "global::UnityEditor.PlayerSettings.WSAImageScale", + "global::UnityEditor.PlayerSettings.WSACapability", + "global::UnityEditor.PlayerSettings.WSATargetFamily" + ] +} \ No newline at end of file diff --git a/BuildMagic.Externals/BuildMagic.Externals.sln b/BuildMagic.Externals/BuildMagic.Externals.sln new file mode 100644 index 0000000..8535c5a --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Externals.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMagic.Generators", "BuildMagic.Generators\BuildMagic.Generators.csproj", "{10C0857C-63CC-4572-9977-76FDAC392B93}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildMagic.BuiltinTaskGenerator", "BuildMagic.BuiltinTaskGenerator\BuildMagic.BuiltinTaskGenerator.csproj", "{DD2CB1DB-9D89-4F08-89C7-F507511AC038}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {10C0857C-63CC-4572-9977-76FDAC392B93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {10C0857C-63CC-4572-9977-76FDAC392B93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {10C0857C-63CC-4572-9977-76FDAC392B93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {10C0857C-63CC-4572-9977-76FDAC392B93}.Release|Any CPU.Build.0 = Release|Any CPU + {DD2CB1DB-9D89-4F08-89C7-F507511AC038}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD2CB1DB-9D89-4F08-89C7-F507511AC038}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD2CB1DB-9D89-4F08-89C7-F507511AC038}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD2CB1DB-9D89-4F08-89C7-F507511AC038}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/BuildMagic.Externals/BuildMagic.Externals.sln.DotSettings b/BuildMagic.Externals/BuildMagic.Externals.sln.DotSettings new file mode 100644 index 0000000..bd2e236 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Externals.sln.DotSettings @@ -0,0 +1,114 @@ + + <?xml version="1.0" encoding="utf-16"?><Profile name="CoreTech: Full Cleanup"><CSReorderTypeMembers>True</CSReorderTypeMembers><XMLReformatCode>True</XMLReformatCode><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeAuto="True" ArrangeBraces="True" ArrangeCVQualifiers="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeOverridingFunctions="True" ArrangeSlashesInIncludeDirectives="True" ArrangeTypeAliases="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><ShaderLabReformatCode>True</ShaderLabReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><HtmlReformatCode>True</HtmlReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="CoreTech: Full Cleanup" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="CoreTech: Full Cleanup &amp; Update Header"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><ShaderLabReformatCode>True</ShaderLabReformatCode><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><XMLReformatCode>True</XMLReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><HtmlReformatCode>True</HtmlReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="CoreTech: Full Cleanup &amp;amp; Update Header" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> + \ No newline at end of file diff --git a/BuildMagic.Externals/BuildMagic.Generators/BuildMagic.Generators.csproj b/BuildMagic.Externals/BuildMagic.Generators/BuildMagic.Generators.csproj new file mode 100644 index 0000000..4d71f2e --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Generators/BuildMagic.Generators.csproj @@ -0,0 +1,24 @@ + + + + netstandard2.0 + enable + 12 + + ..\..\Packages\jp.co.cyberagent.buildmagic\BuildMagic\Editor\Generators\BuildMagic.Generators + true + cs + + + + + + + + + BuildTaskAccessory.cs + + + + + diff --git a/BuildMagic.Externals/BuildMagic.Generators/BuildTaskAccessoriesGenerator.cs b/BuildMagic.Externals/BuildMagic.Generators/BuildTaskAccessoriesGenerator.cs new file mode 100644 index 0000000..6e03a02 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Generators/BuildTaskAccessoriesGenerator.cs @@ -0,0 +1,482 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using BuildMagicEditor; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace BuildMagic.Generators; + +[Generator(LanguageNames.CSharp)] +public class BuildTaskAccessoriesGenerator : IIncrementalGenerator +{ + #region IIncrementalGenerator Members + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var tasks = context.SyntaxProvider.ForAttributeWithMetadataName( + "BuildMagicEditor.GenerateBuildTaskAccessoriesAttribute", + static (node, token) => node is ClassDeclarationSyntax or StructDeclarationSyntax, + static (context, token) => context); + + context.RegisterSourceOutput(tasks, EmitAccessories); + context.RegisterSourceOutput(tasks.Collect().Combine(context.CompilationProvider), EmitRegisterer); + } + + #endregion + + private void EmitRegisterer(SourceProductionContext context, + (ImmutableArray sourceContexts, Compilation compilation) tuple) + { + var (sourceContexts, compilation) = tuple; + if (compilation.AssemblyName != Consts.BuildMagicEditorAssemblyName) return; + + + StringBuilder sourceBuilder = new(); + + + sourceBuilder.AppendLine( +/* lang=c# */""" + namespace BuildMagicEditor + { + partial class BuildTaskBuilderProvider + { + private partial void RegisterBuiltInBuilder() + { + """); + + foreach (var sourceContext in sourceContexts) + { + if (sourceContext.TargetSymbol is not INamedTypeSymbol typeSymbol) continue; + if (typeSymbol.IsGenericType) continue; // TODO: ジェネリクス対応 + + var constructors = GetTargetConstructor(typeSymbol); + if (constructors.Length != 1) return; + + var constructor = constructors[0]; + + string fullName; + if (typeSymbol.ContainingType != null) + fullName = + $"{typeSymbol.ContainingType.ToDisplayString(new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.IncludeTypeParameters))}.{typeSymbol.Name}"; + else + fullName = typeSymbol.ToDisplayString(new SymbolDisplayFormat( + SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)); + + var needDedicatedParameterType = constructor.Parameters.Length > 1; + var parameterTypeExpression = $"{fullName}Parameters"; + if (!needDedicatedParameterType) + { + string EmitValueTupleContainer(IEnumerable<(string? name, string typeExpression)> tupleMembers) + { + needDedicatedParameterType = true; + return "dummy"; + } + + var singleParameterTypeExpression = "global::BuildMagicEditor.EmptyParameter"; + foreach (var parameter in constructor.Parameters) + singleParameterTypeExpression = + ToParameterTypeExpression(parameter.Type, + new Dictionary(SymbolEqualityComparer.Default), + EmitValueTupleContainer); + + if (!needDedicatedParameterType) parameterTypeExpression = singleParameterTypeExpression; + } + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + Register<{{fullName}}, {{parameterTypeExpression}}>(new {{fullName}}Builder()); + """); + } + + sourceBuilder.AppendLine( +/* lang=c# */""" + } + } + } + """); + + context.AddSource("BuildTaskBuilderProvider.BuiltIn.g.cs", + SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + private static IMethodSymbol[] GetTargetConstructor(INamedTypeSymbol typeSymbol) + { + if (typeSymbol.InstanceConstructors.Length == 1) + // IBuildTask types with exact one constructor don't need attributes + return new[] { typeSymbol.InstanceConstructors[0] }; + // multiple constructors with [BuildTaskConstructor] will cause error + return typeSymbol.InstanceConstructors.Where(c => + c.GetAttributes().Any(attr => + attr.AttributeClass.MatchFullMetadataName(Consts.BuildTaskConstructorAttribute))).ToArray(); + } + + private void EmitAccessories(SourceProductionContext context, GeneratorAttributeSyntaxContext task) + { + var attribute = task.Attributes.FirstOrDefault(a => + a.AttributeClass.MatchFullMetadataName("BuildMagicEditor.GenerateBuildTaskAccessoriesAttribute")); + + if (attribute == default) return; + + var attrTargets = attribute.NamedArguments.FirstOrDefault(a => a.Key == "Targets"); + + var generationTargets = attrTargets.Key == "Targets" + ? (BuildTaskAccessories)attrTargets.Value.Value! + : BuildTaskAccessories.All; + + var propertyName = attribute.NamedArguments.FirstOrDefault(a => a.Key == "PropertyName").Value.Value as string; + + var displayName = attribute.ConstructorArguments.Length == 1 + ? attribute.ConstructorArguments[0].Value as string + : null; + + if (generationTargets == BuildTaskAccessories.None) return; + + if (task.TargetSymbol is not INamedTypeSymbol typeSymbol) throw new InvalidOperationException(); + var wrapperTypes = new Dictionary(SymbolEqualityComparer.Default); + + var availableWrapperTypes = typeSymbol.GetAttributes() + .Where(attr => + attr.AttributeClass.MatchFullMetadataName("BuildMagicEditor.UseSerializationWrapperAttribute")) + .Select(attr => attr.ConstructorArguments[0].Value as ITypeSymbol); + + foreach (var wrapperType in availableWrapperTypes) + foreach (var attr in wrapperType.GetAttributes().Where(attr => + attr.AttributeClass.MatchFullMetadataName("BuildMagicEditor.SerializationWrapperAttribute"))) + { + if (attr.ConstructorArguments.Length == 0) continue; + if (attr.ConstructorArguments[0].Value is not ITypeSymbol targetType) continue; + + wrapperTypes[targetType] = wrapperType; + } + + // throw new Exception(string.Join(", ", wrapperTypes.Select(t => $"{t.Key}:{t.Value}"))); + + var ns = typeSymbol.ContainingNamespace; + + var name = typeSymbol.Name; + + string fullName; + if (typeSymbol.ContainingType != null) + fullName = + $"{typeSymbol.ContainingType.ToDisplayString(new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, SymbolDisplayGenericsOptions.IncludeTypeParameters))}.{typeSymbol.Name}"; + else + fullName = typeSymbol.ToDisplayString(new SymbolDisplayFormat(SymbolDisplayGlobalNamespaceStyle.Included, + SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces)); + + var accessibility = typeSymbol.DeclaredAccessibility.ToDisplayString(); + + // select constructor + + var constructors = GetTargetConstructor(typeSymbol); + if (constructors.Length == 0) return; + + if (constructors.Length != 1) + { + context.ReportDiagnostic(Diagnostic.Create( + new DiagnosticDescriptor("BUILDMAGIC0001", "Multiple constructors with BuildTaskConstructor attribute", + "Multiple constructors with BuildTaskConstructor attribute", "Compiler", DiagnosticSeverity.Error, + true), task.TargetNode.GetLocation())); + return; + } + + var constructor = constructors[0]; + + // make generic expressions + var typeParametersExpression = typeSymbol.ToTypeParametersExpression(); + var typeConstraintsExpression = typeSymbol.ToTypeConstraintsExpression(); + + // [MovedFrom] attribute. If the IBuildTask implementation is marked as moved from other name, also mark the generated IBuildConfiguration implementation as moved from. + var movedFromAttributes = typeSymbol.GetAttributes().Where(attr => + attr.AttributeClass.MatchFullMetadataName("UnityEngine.Scripting.APIUpdating.MovedFromAttribute")); + + StringBuilder sourceBuilder = new(); + + if (!ns.IsGlobalNamespace) + { + sourceBuilder.AppendLine($"namespace {ns}"); + sourceBuilder.AppendLine("{"); + } + + // containing types + void AppendContainingTypeScope(INamedTypeSymbol previous) + { + var current = previous.ContainingType; + if (current == null) return; + AppendContainingTypeScope(current); + sourceBuilder.AppendLine($"partial class {current.Name}{current.ToTypeParametersExpression()}"); + sourceBuilder.AppendLine("{"); + } + + void AppendContainingTypeScopeCloser(INamedTypeSymbol previous) + { + var current = previous.ContainingType; + if (current == null) return; + AppendContainingTypeScopeCloser(current); + sourceBuilder.AppendLine($"}} // partial class {current.Name}{current.ToTypeParametersExpression()}"); + } + + AppendContainingTypeScope(typeSymbol); + + List<(string containerName, IEnumerable<(string? name, string typeExpression)>)> tupleMemberSet = new(); + Dictionary parameterTypeExpressions = new(); + + string EmitValueTupleContainer(IEnumerable<(string? name, string typeExpression)> tupleMembers) + { + // NOTE: should we change this to more human-readable name? + var containerName = $"__BUILDMAGIC__AnonymousTupleContainer__{tupleMemberSet.Count}"; + tupleMemberSet.Add((containerName, tupleMembers)); + return containerName; + } + + foreach (var parameter in constructor.Parameters) + parameterTypeExpressions[parameter] = + ToParameterTypeExpression(parameter.Type, wrapperTypes, EmitValueTupleContainer); + + // dedicated parameter type isn't necessary when there is only one parameter and no anonymous tuple container + var needDedicatedParameterType = constructor.Parameters.Length > 1 || tupleMemberSet.Count > 0; + + var parameterTypeExpression = needDedicatedParameterType + ? $"{fullName}Parameters{typeParametersExpression}" + : parameterTypeExpressions.FirstOrDefault().Value ?? "global::BuildMagicEditor.EmptyParameter"; + + if (generationTargets.HasFlag(BuildTaskAccessories.Configuration)) + { + if (!string.IsNullOrEmpty(displayName) || !string.IsNullOrEmpty(propertyName)) + { + sourceBuilder.Append($"[global::BuildMagicEditor.BuildConfiguration("); + + bool hasDisplayName = !string.IsNullOrEmpty(displayName); + if (hasDisplayName) + { + sourceBuilder.Append($"DisplayName = @\"{displayName}\""); + } + + if (!string.IsNullOrEmpty(propertyName)) + { + if(hasDisplayName) sourceBuilder.Append($", "); + sourceBuilder.Append($"PropertyName = @\"{propertyName}\""); + } + + sourceBuilder.AppendLine($")]"); + } + + // [MovedFrom] + if (movedFromAttributes.Any()) + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + [{{string.Join(", ", movedFromAttributes.Select(attr => attr.ConstructorArguments).Select(args => $"global::UnityEngine.Scripting.APIUpdating.MovedFrom({string.Join(", ", args.Select((a, index) => { + if (index == 3 && a.Value != null) return $"\"{a.Value as string}Configuration\""; + return a.ToCSharpString(); + }))})"))}}] + """); + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + [global::System.Serializable] + {{accessibility}} partial class {{name}}Configuration{{typeParametersExpression}} : global::BuildMagicEditor.BuildConfigurationBase<{{fullName}}{{typeParametersExpression}}, {{parameterTypeExpression}}> {{typeConstraintsExpression}} + { + """); + + sourceBuilder.AppendLine( +/* lang=c# */"""}"""); + } + + if (generationTargets.HasFlag(BuildTaskAccessories.Parameters) && needDedicatedParameterType) + { + sourceBuilder.AppendLine( +/* lang=c# */$$""" + + [global::System.Serializable] + {{accessibility}} class {{name}}Parameters{{typeParametersExpression}} {{typeConstraintsExpression}} + { + """); + + foreach (var kvp in parameterTypeExpressions) + { + var parameter = kvp.Key; + var typeExp = kvp.Value; + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + public {{typeExp}} {{parameter.Name}}; + """); + } + + // emit tuple containers + + foreach (var (containerName, tupleMembers) in tupleMemberSet) + EmitValueTupleContainerTypeDefinition(sourceBuilder, tupleMembers, containerName); + + sourceBuilder.AppendLine( +/* lang=c# */"""}"""); + } + + if (generationTargets.HasFlag(BuildTaskAccessories.Builder)) + { + if (typeSymbol.ContainingAssembly.Name != "BuildMagic.Editor") + sourceBuilder.AppendLine( + /* lang=c# */ + $$""" + [global::BuildMagicEditor.BuildTaskBuilder(typeof({{fullName}}{{typeParametersExpression}}), typeof({{parameterTypeExpression}}))] + """); + + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + {{accessibility}} class {{name}}Builder{{typeParametersExpression}} : global::BuildMagicEditor.BuildTaskBuilderBase<{{fullName}}{{typeParametersExpression}}, {{parameterTypeExpression}}> {{typeConstraintsExpression}} + { + public override {{fullName}}{{typeParametersExpression}} Build({{parameterTypeExpression}} value) + { + """); + + sourceBuilder.AppendLine( +/* lang=c# */$$""" + return new {{fullName}}{{typeParametersExpression}}({{string.Join(", ", constructor.Parameters.Select(p => ToParameterReferenceExpression(p.Type, needDedicatedParameterType ? $"value.{p.Name}" : "value", wrapperTypes, out _)))}}); + } + } + """); + } + + AppendContainingTypeScopeCloser(typeSymbol); + + if (!ns.IsGlobalNamespace) sourceBuilder.AppendLine($"}} // {ns}"); + + var filename = $"{typeSymbol.ToFullMetadataName()}.g.cs"; + + context.AddSource(filename, SourceText.From(sourceBuilder.ToString(), Encoding.UTF8)); + } + + private static void EmitValueTupleContainerTypeDefinition(StringBuilder builder, + IEnumerable<(string? name, string typeExpression)> members, string uniqueName) + { + builder.AppendLine( +/* lang=c# */$$""" + [global::System.Serializable] + public struct {{uniqueName}} + { + """); + var index = 0; + foreach (var (name, typeExp) in members) + { + index++; + builder.AppendLine( +/* lang=c# */$$""" + public {{typeExp}} {{name ?? $"Item{index}"}}; + """); + } + + // conversions + builder.AppendLine( +/* lang=c# */$$""" + public static implicit operator ({{string.Join(", ", members.Select(m => $"{m.typeExpression} {m.name ?? ""}"))}})({{uniqueName}} source) + { + return ({{string.Join(", ", members.Select((m, i) => m.name ?? $"Item{i + 1}").Select(s => $"source.{s}"))}}); + } + + public static implicit operator {{uniqueName}}(global::System.ValueTuple<{{string.Join(", ", members.Select(m => m.typeExpression))}}> source) + { + var result = new {{uniqueName}}(); + ({{string.Join(", ", members.Select((m, i) => m.name ?? $"Item{i + 1}").Select(s => $"result.{s}"))}}) = ({{string.Join(", ", members.Select((m, i) => $"source.Item{i + 1}"))}}); + return result; + } + + """); + + builder.AppendLine( +/* lang=c# */""" }"""); + } + + private string ToParameterTypeExpression(ITypeSymbol parameterType, + IReadOnlyDictionary wrappers, + Func, string> emitValueTupleContainer) + { + if (parameterType is INamedTypeSymbol named && + named.MatchFullMetadataName("System.Collections.Generic.IReadOnlyDictionary`2")) + { + var tKey = named.TypeArguments[0]; + var tValue = named.TypeArguments[1]; + + return + $"global::BuildMagicEditor.SerializableDictionary<{ToParameterTypeExpression(tKey, wrappers, emitValueTupleContainer)}, {ToParameterTypeExpression(tValue, wrappers, emitValueTupleContainer)}>"; + } + + if (ValueTupleInfo.TryCreate(parameterType, out var tupleInfo)) + { + // extract tuple as struct + var tupleContainerSymbol = emitValueTupleContainer(tupleInfo.GetElements().Select(info => + (info.Name, + ToParameterTypeExpression(info.Type, wrappers, emitValueTupleContainer))) + .ToArray()); + return tupleContainerSymbol; + } + + if (wrappers.TryGetValue(parameterType, out var wrapperType)) + return wrapperType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + if (parameterType.BuiltinSerializationWrapperExists(out var wrapperTypeExpression)) + return wrapperTypeExpression; + + return parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + + private string ToParameterReferenceExpression(ITypeSymbol parameterType, string sourceValueExpression, + IReadOnlyDictionary wrappers, out bool needTransformation) + { + if (parameterType is INamedTypeSymbol named && + named.MatchFullMetadataName("System.Collections.Generic.IReadOnlyDictionary`2")) + { + var tKey = ToParameterReferenceExpression(named.TypeArguments[0], "kvp.Key", wrappers, + out var keyNeedTransformation); + var tValue = ToParameterReferenceExpression(named.TypeArguments[1], "kvp.Value", wrappers, + out var valueNeedTransformation); + needTransformation = true; + + if (!keyNeedTransformation && !valueNeedTransformation) return $"{sourceValueExpression}.ToDictionary()"; + + // parameter should be serialized as SerializableDictionary + return + $"(global::System.Collections.Generic.IReadOnlyDictionary<{named.TypeArguments[0].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}, {named.TypeArguments[1].ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}>)global::System.Linq.Enumerable.ToDictionary({sourceValueExpression}.ToDictionary(), kvp => {tKey}, kvp => {tValue})"; + } + + if (ValueTupleInfo.TryCreate(parameterType, out var tupleInfo)) + { + needTransformation = true; + return $"({string.Join(", ", + tupleInfo.GetElements().Select((elementInfo, index) => ToParameterReferenceExpression(elementInfo.Type, + $"{sourceValueExpression}.{elementInfo.Name ?? $"Item{index + 1}"}", wrappers, out _)))})"; + } + + if (wrappers.TryGetValue(parameterType, out _)) + { + // unwrap + needTransformation = true; + return + $"({parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}){sourceValueExpression}"; + } + + if (parameterType.BuiltinSerializationWrapperExists(out _)) + { + // unwrap + needTransformation = true; + return + $"({parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}){sourceValueExpression}"; + } + + needTransformation = false; + + return sourceValueExpression; + } +} diff --git a/BuildMagic.Externals/BuildMagic.Generators/Consts.cs b/BuildMagic.Externals/BuildMagic.Generators/Consts.cs new file mode 100644 index 0000000..49e7904 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Generators/Consts.cs @@ -0,0 +1,11 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +namespace BuildMagic.Generators; + +internal class Consts +{ + public static readonly string BuildMagicEditorAssemblyName = "BuildMagic.Editor"; + public static readonly string BuildTaskConstructorAttribute = "BuildMagicEditor.BuildTaskConstructorAttribute"; +} diff --git a/BuildMagic.Externals/BuildMagic.Generators/IsExternalInit.cs b/BuildMagic.Externals/BuildMagic.Generators/IsExternalInit.cs new file mode 100644 index 0000000..fb84c85 --- /dev/null +++ b/BuildMagic.Externals/BuildMagic.Generators/IsExternalInit.cs @@ -0,0 +1,11 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + + +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit + { + } +} diff --git a/BuildMagic.Externals/Common/BuiltinSerializationWrapperRegistry.cs b/BuildMagic.Externals/Common/BuiltinSerializationWrapperRegistry.cs new file mode 100644 index 0000000..103056d --- /dev/null +++ b/BuildMagic.Externals/Common/BuiltinSerializationWrapperRegistry.cs @@ -0,0 +1,25 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace BuildMagic; + +internal static class BuiltinSerializationWrapperRegistry +{ + private static readonly (string targetTypeFullMetadataName, string wrapperTypeExpression)[] _wrappers = + { + ("UnityEditor.Build.NamedBuildTarget", "global::BuildMagicEditor.NamedBuildTargetSerializationWrapper") + }; + + public static bool BuiltinSerializationWrapperExists(this ITypeSymbol typeSymbol, out string wrapperTypeExpression) + { + wrapperTypeExpression = _wrappers + .FirstOrDefault(tuple => typeSymbol.MatchFullMetadataName(tuple.targetTypeFullMetadataName)) + .wrapperTypeExpression; + + return !string.IsNullOrEmpty(wrapperTypeExpression); + } +} diff --git a/BuildMagic.Externals/Common/LinqPlus.cs b/BuildMagic.Externals/Common/LinqPlus.cs new file mode 100644 index 0000000..9179393 --- /dev/null +++ b/BuildMagic.Externals/Common/LinqPlus.cs @@ -0,0 +1,64 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BuildMagic; + +public static class LinqPlus +{ + public static IEnumerable WhereNotNull(this IEnumerable source) where T : class + { + return source.Where(x => x != null)!; + } + + public static IEnumerable WhereNotNull(this IEnumerable source) where T : struct + { + return source.Where(x => x.HasValue).Select(x => x!.Value); + } + + public static IEnumerable SelectWhereNotNull(this IEnumerable source, + Func predicate) where TResult : class + { + return source.Select(predicate).WhereNotNull(); + } + + public static IEnumerable SelectWhereNotNull(this IEnumerable source, + Func predicate) where TResult : struct + { + return source.Select(predicate).WhereNotNull(); + } + + public static int FindIndex(this IEnumerable source, Predicate predicate) + { + var i = 0; + foreach (var element in source) + { + if (predicate(element)) + return i; + i++; + } + + return -1; + } + +#if NETCOREAPP3_0_OR_GREATER + public static int FindIndex(this IEnumerable source, ReadOnlySpan matchingElements) + where T : IEquatable? + { + var i = 0; + foreach (var element in source) + { + if (matchingElements.Contains(element)) + return i; + i++; + } + + return -1; + } + +#endif +} diff --git a/BuildMagic.Externals/Common/TypeUtils.cs b/BuildMagic.Externals/Common/TypeUtils.cs new file mode 100644 index 0000000..93b21b5 --- /dev/null +++ b/BuildMagic.Externals/Common/TypeUtils.cs @@ -0,0 +1,223 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; + +namespace BuildMagic; + +internal static class TypeUtils +{ + [ThreadStatic] private static StringBuilder? _tempStringBuilder; + + [ThreadStatic] private static List? _tempStringList; + + private static readonly ReadOnlyMemory BuiltinSerializableTypes = new[] + { + "UnityEngine.Vector3", + "UnityEngine.Vector3Int", + "UnityEngine.Vector2", + "UnityEngine.Vector2Int", + "UnityEngine.Quaternion", + "UnityEngine.Color", + "UnityEngine.Bounds", + "UnityEngine.Vector4", + "UnityEngine.Rect", + "UnityEngine.RectInt", + "UnityEngine.Matrix4x4", + "UnityEngine.Color32", + "UnityEngine.LayerMask", + "UnityEngine.PropertyName", + "UnityEngine.Rendering.SphericalHarmonicsL2", + "UnityEngine.Hash128", + "UnityEngine.RenderingLayerMask", + "UnityEngine.AnimationCurve", + "UnityEngine.Gradient", + "UnityEngine.RectOffset" + }; + + public static bool MatchFullMetadataName(this ISymbol? symbol, string fullMetadataName) + { + if (symbol is null) return false; + + static bool Match(ref ReadOnlySpan name, ISymbol? symbol) + { + if (symbol is null) return false; + if (symbol is IModuleSymbol) return true; + if (symbol is INamespaceSymbol { IsGlobalNamespace: true }) return true; + if (!Match(ref name, symbol.ContainingSymbol)) return false; + + if (!name.StartsWith(symbol.MetadataName.AsSpan())) return false; + name = name.Slice(symbol.MetadataName.Length); + if (name.Length >= 1 && name[0] == '.') name = name.Slice(1); + return true; + } + + var s = fullMetadataName.AsSpan(); + return Match(ref s, symbol); + } + + public static string ToFullMetadataName(this INamedTypeSymbol symbol) + { + _tempStringList ??= new List(); + _tempStringList.Clear(); + + _tempStringList.Add(symbol.MetadataName); + + var cursor = symbol; + while (cursor.ContainingType != null) + { + cursor = cursor.ContainingType; + _tempStringList.Add(cursor.MetadataName); + } + + if (cursor.ContainingNamespace != null && !cursor.ContainingNamespace.IsGlobalNamespace) + _tempStringList.Add(cursor.ContainingNamespace.ToString()); + + _tempStringBuilder ??= new StringBuilder(); + _tempStringBuilder.Clear(); + + for (var i = _tempStringList.Count - 1; i >= 0; i--) + { + _tempStringBuilder.Append(_tempStringList[i]); + if (i != 0) _tempStringBuilder.Append("."); + } + + return _tempStringBuilder.ToString(); + } + + public static string ToTypeParameterConstraintExpression(this ITypeParameterSymbol symbol) + { + _tempStringList ??= new List(); + _tempStringList.Clear(); + + if (symbol.HasValueTypeConstraint && !symbol.HasUnmanagedTypeConstraint) _tempStringList.Add("struct"); + + if (symbol.HasReferenceTypeConstraint) _tempStringList.Add("class"); + + if (symbol.HasNotNullConstraint) _tempStringList.Add("notnull"); + + if (symbol.HasUnmanagedTypeConstraint) _tempStringList.Add("unmanaged"); + + foreach (var baseTypeConstraint in symbol.ConstraintTypes.Where(t => t.TypeKind == TypeKind.Class)) + _tempStringList.Add(baseTypeConstraint.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + foreach (var baseTypeConstraint in symbol.ConstraintTypes.Where(t => t.TypeKind == TypeKind.Interface)) + _tempStringList.Add(baseTypeConstraint.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + + if (symbol.HasConstructorConstraint) _tempStringList.Add("new()"); + + if (_tempStringList.Count == 0) return ""; + + return $"where {symbol.Name} : {string.Join(", ", _tempStringList)}"; + } + + public static string ToTypeParametersExpression(this INamedTypeSymbol symbol) + { + var typeParameters = symbol.TypeParameters; + return typeParameters.Length == 0 + ? "" + : $"<{string.Join(", ", typeParameters.Select(p => p.Name))}>"; + } + + public static string ToTypeConstraintsExpression(this INamedTypeSymbol symbol) + { + var typeParameters = symbol.TypeParameters; + return typeParameters.Length == 0 + ? "" + : string.Join(" ", + typeParameters.Select(p => p.ToTypeParameterConstraintExpression()) + .Where(e => !string.IsNullOrEmpty(e))); + } + + public static bool IsSerializedField(IFieldSymbol field) + { + if (field.IsStatic) return false; + if (field.IsReadOnly) return false; + if (field.GetAttributes() + .Any(attr => attr.AttributeClass.MatchFullMetadataName("System.NonSerializedAttribute"))) return false; + + var hasSerializeReference = field.GetAttributes().Any(attr => + attr.AttributeClass.MatchFullMetadataName("UnityEngine.SerializeReference")); + + if (hasSerializeReference && field.Type.IsReferenceType) return true; + + var hasSerializeField = field.GetAttributes() + .Any(attr => attr.AttributeClass.MatchFullMetadataName("UnityEngine.SerializeField")); + + if (field.DeclaredAccessibility != Accessibility.Public && !hasSerializeField && !hasSerializeReference) + return false; + + return IsSerializableFieldType(field.Type); + } + + public static bool IsSerializableFieldType(ITypeSymbol type) + { + if (type.IsStatic) return false; + + if (type is IArrayTypeSymbol arrayType) return IsSerializableFieldType(arrayType.ElementType); + + if (type.SpecialType is + SpecialType.System_Byte or + SpecialType.System_SByte or + SpecialType.System_UInt16 or + SpecialType.System_Int16 or + SpecialType.System_UInt32 or + SpecialType.System_Int32 or + SpecialType.System_UInt64 or + SpecialType.System_Int64 or + SpecialType.System_Boolean or + SpecialType.System_Single or + SpecialType.System_Double or + SpecialType.System_Char or + SpecialType.System_String) + return true; + + if (type.BaseType is { SpecialType: SpecialType.System_Enum }) return true; + + if (GetBaseTypes(type, true).OfType() + .Any(t => t.MatchFullMetadataName("UnityEngine.Object"))) return true; + + if (type is INamedTypeSymbol namedType) + { + if (namedType.IsUnboundGenericType) return false; + if (namedType.IsGenericType && namedType.ConstructUnboundGenericType() + .MatchFullMetadataName("System.Collections.Generic.List`1")) + return IsSerializableFieldType(namedType.TypeArguments[0]); + + foreach (var fullMetadataName in BuiltinSerializableTypes.Span) + if (namedType.MatchFullMetadataName(fullMetadataName)) + return true; + + if (namedType.IsSerializable && !type.IsReadOnly) return true; + } + + return false; + } + + public static IEnumerable GetBaseTypes(this ITypeSymbol type, bool includeSelf) + { + var cursor = type; + if (includeSelf) yield return cursor; + while ((cursor = cursor.BaseType) != null) yield return cursor; + } + + public static string ToDisplayString(this Accessibility accessibility) + { + return accessibility switch + { + Accessibility.NotApplicable => "", + Accessibility.Private => "private", + Accessibility.ProtectedAndInternal => "private protected", + Accessibility.Protected => "protected", + Accessibility.Internal => "internal", + Accessibility.ProtectedOrInternal => "protected internal", + Accessibility.Public => "public", + _ => throw new ArgumentOutOfRangeException(nameof(accessibility), accessibility, null) + }; + } +} diff --git a/BuildMagic.Externals/Common/ValueTupleInfo.cs b/BuildMagic.Externals/Common/ValueTupleInfo.cs new file mode 100644 index 0000000..f72822d --- /dev/null +++ b/BuildMagic.Externals/Common/ValueTupleInfo.cs @@ -0,0 +1,55 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace BuildMagic; + +internal readonly record struct ValueTupleInfo +{ + private ValueTupleInfo(INamedTypeSymbol tupleType) + { + TupleType = tupleType; + } + + public INamedTypeSymbol TupleType { get; } + + public IEnumerable GetElements() + { + foreach (var info in GetElementsCore(TupleType)) yield return info; + } + + private static IEnumerable GetElementsCore(INamedTypeSymbol tupleType) + { + foreach (var element in tupleType.TupleElements) + { + var type = element.Type; + if (type is INamedTypeSymbol { IsTupleType: true } tuple) + { + foreach (var info in GetElementsCore(tuple)) yield return info; + + continue; + } + + yield return new ValueTupleElementInfo(element.Name, element.Type); + } + } + + public static bool TryCreate(ITypeSymbol type, out ValueTupleInfo info) + { + if (type is not INamedTypeSymbol { IsTupleType: true } tuple) + { + info = default; + return false; + } + + info = new ValueTupleInfo(tuple); + return true; + } +} + +internal readonly record struct ValueTupleElementInfo(string? Name, ITypeSymbol Type) +{ +} diff --git a/BuildMagic.sln.DotSettings b/BuildMagic.sln.DotSettings new file mode 100644 index 0000000..f0831ae --- /dev/null +++ b/BuildMagic.sln.DotSettings @@ -0,0 +1,115 @@ + + <?xml version="1.0" encoding="utf-16"?><Profile name="CoreTech: Full Cleanup"><CSReorderTypeMembers>True</CSReorderTypeMembers><XMLReformatCode>True</XMLReformatCode><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeAuto="True" ArrangeBraces="True" ArrangeCVQualifiers="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeOverridingFunctions="True" ArrangeSlashesInIncludeDirectives="True" ArrangeTypeAliases="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><ShaderLabReformatCode>True</ShaderLabReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><HtmlReformatCode>True</HtmlReformatCode><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="CoreTech: Full Cleanup" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS></Profile> + <?xml version="1.0" encoding="utf-16"?><Profile name="CoreTech: Full Cleanup &amp; Update Header"><AspOptimizeRegisterDirectives>True</AspOptimizeRegisterDirectives><CppAddTypenameTemplateKeywords>True</CppAddTypenameTemplateKeywords><CppRemoveElseKeyword>True</CppRemoveElseKeyword><CppShortenQualifiedName>True</CppShortenQualifiedName><CppDeleteRedundantSpecifier>True</CppDeleteRedundantSpecifier><CppRemoveStatement>True</CppRemoveStatement><CppDeleteRedundantTypenameTemplateKeywords>True</CppDeleteRedundantTypenameTemplateKeywords><CppCStyleToStaticCastDescriptor>True</CppCStyleToStaticCastDescriptor><CppReplaceExpressionWithBooleanConst>True</CppReplaceExpressionWithBooleanConst><CppMakeIfConstexpr>True</CppMakeIfConstexpr><CppMakePostfixOperatorPrefix>True</CppMakePostfixOperatorPrefix><CppChangeSmartPointerToMakeFunction>True</CppChangeSmartPointerToMakeFunction><CppReplaceThrowWithRethrowFix>True</CppReplaceThrowWithRethrowFix><CppReplaceExpressionWithNullptr>True</CppReplaceExpressionWithNullptr><CppCodeStyleCleanupDescriptor ArrangeBraces="True" ArrangeAuto="True" ArrangeFunctionDeclarations="True" ArrangeNestedNamespaces="True" ArrangeTypeAliases="True" ArrangeCVQualifiers="True" ArrangeSlashesInIncludeDirectives="True" ArrangeOverridingFunctions="True" SortIncludeDirectives="True" SortMemberInitializers="True" /><CppReformatCode>True</CppReformatCode><CSReorderTypeMembers>True</CSReorderTypeMembers><CSCodeStyleAttributes ArrangeVarStyle="True" ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" ArrangeArgumentsStyle="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeCodeBodyStyle="True" ArrangeTrailingCommas="True" ArrangeObjectCreation="True" ArrangeDefaultValue="True" /><ShaderLabReformatCode>True</ShaderLabReformatCode><RemoveCodeRedundanciesVB>True</RemoveCodeRedundanciesVB><Xaml.RedundantFreezeAttribute>True</Xaml.RedundantFreezeAttribute><Xaml.RemoveRedundantModifiersAttribute>True</Xaml.RemoveRedundantModifiersAttribute><Xaml.RemoveRedundantNameAttribute>True</Xaml.RemoveRedundantNameAttribute><Xaml.RemoveRedundantResource>True</Xaml.RemoveRedundantResource><Xaml.RemoveRedundantCollectionProperty>True</Xaml.RemoveRedundantCollectionProperty><Xaml.RemoveRedundantAttachedPropertySetter>True</Xaml.RemoveRedundantAttachedPropertySetter><Xaml.RemoveRedundantStyledValue>True</Xaml.RemoveRedundantStyledValue><Xaml.RemoveRedundantNamespaceAlias>True</Xaml.RemoveRedundantNamespaceAlias><Xaml.RemoveForbiddenResourceName>True</Xaml.RemoveForbiddenResourceName><Xaml.RemoveRedundantGridDefinitionsAttribute>True</Xaml.RemoveRedundantGridDefinitionsAttribute><Xaml.RemoveRedundantUpdateSourceTriggerAttribute>True</Xaml.RemoveRedundantUpdateSourceTriggerAttribute><Xaml.RemoveRedundantBindingModeAttribute>True</Xaml.RemoveRedundantBindingModeAttribute><Xaml.RemoveRedundantGridSpanAttribut>True</Xaml.RemoveRedundantGridSpanAttribut><XMLReformatCode>True</XMLReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSUseAutoProperty>True</CSUseAutoProperty><CSMakeFieldReadonly>True</CSMakeFieldReadonly><CSMakeAutoPropertyGetOnly>True</CSMakeAutoPropertyGetOnly><CSArrangeQualifiers>True</CSArrangeQualifiers><CSFixBuiltinTypeReferences>True</CSFixBuiltinTypeReferences><HtmlReformatCode>True</HtmlReformatCode><VBOptimizeImports>True</VBOptimizeImports><VBShortenReferences>True</VBShortenReferences><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><VBReformatCode>True</VBReformatCode><VBFormatDocComments>True</VBFormatDocComments><CSReformatCode>True</CSReformatCode><CSharpFormatDocComments>True</CSharpFormatDocComments><FormatAttributeQuoteDescriptor>True</FormatAttributeQuoteDescriptor><IDEA_SETTINGS>&lt;profile version="1.0"&gt; + &lt;option name="myName" value="CoreTech: Full Cleanup &amp;amp; Update Header" /&gt; +&lt;/profile&gt;</IDEA_SETTINGS><RIDER_SETTINGS>&lt;profile&gt; + &lt;Language id="CSS"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="EditorConfig"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="HTTP Request"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Handlebars"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Ini"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JSON"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Jade"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="JavaScript"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Markdown"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="Properties"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="RELAX-NG"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="SQL"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="XML"&gt; + &lt;Rearrange&gt;true&lt;/Rearrange&gt; + &lt;OptimizeImports&gt;true&lt;/OptimizeImports&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; + &lt;Language id="yaml"&gt; + &lt;Reformat&gt;true&lt;/Reformat&gt; + &lt;/Language&gt; +&lt;/profile&gt;</RIDER_SETTINGS><CSUpdateFileHeader>True</CSUpdateFileHeader></Profile> + + True \ No newline at end of file diff --git a/Documentation~ b/Documentation~ new file mode 120000 index 0000000..2b01ddf --- /dev/null +++ b/Documentation~ @@ -0,0 +1 @@ +./Packages/jp.co.cyberagent.buildmagic/Documentation~ \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline.meta new file mode 100644 index 0000000..a8c12f9 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d39963d2d4dfd4b1996236c74e99ceaf +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor.meta new file mode 100644 index 0000000..cf4b1b3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eaa74f2e47ec2410c82200b5aa5a86e8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs new file mode 100644 index 0000000..a7c2717 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("BuildMagic.Commandline.Editor.Tests")] diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs.meta new file mode 100644 index 0000000..36c30bc --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/AssemblyInfo.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4b743f0667604b0990e0cce2a36c6802 +timeCreated: 1711044066 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef new file mode 100644 index 0000000..d7e9c68 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "BuildMagic.Commandline.Editor", + "rootNamespace": "BuildMagicEditor.Commandline", + "references": [ + "GUID:39027f064f53542dd9bfef00495cfd85" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef.meta new file mode 100644 index 0000000..5ff0d5d --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagic.Commandline.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 738c81374d4cb430bb017ec5c5c58e75 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs new file mode 100644 index 0000000..40b94d8 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs @@ -0,0 +1,242 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using BuildMagicEditor; +using BuildMagicEditor.BuiltIn; +using BuildMagicEditor.Commandline.Error; +using BuildMagicEditor.Commandline.Internal; +using BuildMagicEditor.Extensions; +using UnityEditor; +using UnityEngine; +using BuildPipeline = BuildMagicEditor.BuildPipeline; + +/// +/// The interface for editor batch mode command line. +/// +public static class BuildMagicCLI +{ + private static readonly int SuccessCode = 0; + private static readonly int FailedCode = 1; + + private static readonly string SchemeOption = "-scheme"; + private static readonly string OverrideOption = "-override"; + private static readonly string StrictOption = "-strict"; + + private static readonly string LoggerTag = "BuildMagic"; + + private static readonly string SetLocationPathPropertyName = "BuildPlayerOptions.assetBundleManifestPath"; + + private static readonly Dictionary OverrideAliases = new() + { + { "-output", SetLocationPathPropertyName } + }; + + /// + /// Perform the switch. + /// + public static void PreBuild() + { + // TODO: System.Commandline ライクな実装に仕上げたいが後回し + RunCommand(context => + { + var configurations = BuildConfigurationUtility.ResolveConfigurations( + context.BaseScheme?.PreBuildConfigurations ?? Enumerable.Empty(), + context.CurrentScheme.PreBuildConfigurations); + + var preBuildTasks = configurations + .Select(c => CreateBuildTask(c, context)) + .OfType>() + .ToList(); + + preBuildTasks.AddRange(GetUnresolvedOverrideProperties(context).OfType>()); + + BuildPipeline.PreBuild(preBuildTasks); + }); + } + + /// + /// Perform the build. + /// + public static void Build() + { + // TODO: System.Commandline ライクな実装に仕上げたいが後回し + RunCommand(context => + { + var internalPrepareTasks = CreateInternalPrepareTasks(context); + + var configurations = BuildConfigurationUtility.ResolveConfigurations( + context.BaseScheme?.PostBuildConfigurations ?? Enumerable.Empty(), + context.CurrentScheme.PostBuildConfigurations); + + var postBuildTasks = configurations + .Select(c => CreateBuildTask(c, context)) + .OfType>() + .ToList(); + + postBuildTasks.AddRange(GetUnresolvedOverrideProperties(context).OfType>()); + + var isStrict = context.CommandLineParser.HasFlag(StrictOption); + + BuildPipeline.Build(internalPrepareTasks.GenerateBuildPlayerOptions(), postBuildTasks, isStrict); + }); + } + + private static IEnumerable GetUnresolvedOverrideProperties(Context context) + { + return context.BuildPropertyResolver.GetUnresolvedOverrideProperties().Select(prop => + { + if (!BuildConfigurationTypeUtility.TryGetConfigurationType(prop.Name, + out var configurationType)) + throw new CommandLineArgumentException($"No such property found: {prop.Name}"); + + var configuration = Activator.CreateInstance(configurationType) as IBuildConfiguration; + + return CreateBuildTask(configuration, context); + }); + } + + private static IBuildTask[] CreateInternalPrepareTasks(Context context) + { + var setLocationPathConfiguration = new BuildPlayerOptionsSetLocationPathNameTaskConfiguration(); + var property = context.BuildPropertyResolver.ResolveProperty(setLocationPathConfiguration); + + var setConfigurationPathTaskBuilder = + context.TaskBuilderProvider.GetBuilder(setLocationPathConfiguration.TaskType, property.ValueType); + + List> tasks = new() + { + new BuildPlayerOptionsApplyEditorSettingsTask(), + setConfigurationPathTaskBuilder.Build(property.Value) as IBuildTask + }; + + tasks.AddRange(GetUnresolvedOverrideProperties(context).OfType>()); + + return tasks.ToArray(); + } + + private static void RunCommand(Action command) + { + EditorApplication.Exit(InvokeCommand(command)); + } + + private static int InvokeCommand(Action command) + { + // NOTE: いったん固定でUnityのコンソールログに吐き出す + // もしログを別で書き出したい場合はここを変えられるようにする + var logger = Debug.unityLogger; + + try + { + command(Context.Create(logger)); + return SuccessCode; + } + catch (Exception e) + { + logger.LogError(LoggerTag, $"[BuildMagic] Failed to run command."); + logger.LogException(e); + return FailedCode; + } + } + + internal static BuildScheme LoadScheme(CommandLineParser parser) + { + var name = parser.ParseFirst(SchemeOption); + + var scheme = BuildSchemeLoader.LoadAll().FirstOrDefault(s => s.Name == name); + if (scheme == null) + { + throw new CommandLineArgumentException($"No such scheme found: {name}"); + } + + return scheme; + } + + internal static BuildScheme LoadBaseScheme(BuildScheme scheme) + { + if (string.IsNullOrEmpty(scheme.BaseSchemeName)) + return null; + + var baseScheme = BuildSchemeLoader.LoadAll().FirstOrDefault(s => s.Name == scheme.BaseSchemeName); + if (baseScheme == null) + throw new CommandLineArgumentException($"No such base scheme found: {scheme.BaseSchemeName}"); + + return baseScheme; + } + + internal static List ParseOverrideProperties( + CommandLineParser parser, + IDictionary aliases = null) + { + var properties = new List(); + + // No override properties specified. + if (parser.TryParse(OverrideOption, out var options)) + // override options are separated by comma such as "KEY1=VALUE1". + foreach (var keyValue in options) + { + // the override property is separated by equal sign such as "KEY1=VALUE1". + var ps = keyValue.Split('='); + if (ps.Length != 2) continue; + + properties.Add(new OverrideProperty(ps[0], ps[1])); + } + + if (aliases != null) + foreach (var alias in aliases) + if (parser.TryParse(alias.Key, out var value) && value.Length == 1) + properties.Add(new OverrideProperty(alias.Value, value[0])); + + return properties; + } + + internal static IBuildTask CreateBuildTask(IBuildConfiguration configuration, Context context) + { + var buildProperty = context.BuildPropertyResolver.ResolveProperty(configuration); + var taskBuilder = context.TaskBuilderProvider.GetBuilder(configuration.TaskType, buildProperty.ValueType); + return taskBuilder.Build(buildProperty.Value); + } + + internal class Context + { + public CommandLineParser CommandLineParser { get; } + public IBuildScheme CurrentScheme { get; } + public IBuildScheme BaseScheme { get; } + public BuildPropertyResolver BuildPropertyResolver { get; } + + public BuildTaskBuilderProvider TaskBuilderProvider { get; } + + // NOTE: ロガーは、BuildMagic用に一枚ラップしてもよい(タグやプレフィックスの指定が面倒なので) + public ILogger Logger { get; } + + public static Context Create(ILogger logger) + { + var parser = CommandLineParser.Create(); + var scheme = LoadScheme(parser); + var baseScheme = LoadBaseScheme(scheme); + var resolver = BuildPropertyResolver.CreateDefault(ParseOverrideProperties(parser, OverrideAliases)); + var provider = BuildTaskBuilderProvider.CreateDefault(); + + return new Context(parser, scheme, baseScheme, provider, resolver, logger); + } + + private Context( + CommandLineParser parser, + IBuildScheme scheme, + IBuildScheme baseScheme, + BuildTaskBuilderProvider provider, + BuildPropertyResolver resolver, + ILogger logger) + { + CommandLineParser = parser; + CurrentScheme = scheme; + BaseScheme = baseScheme; + BuildPropertyResolver = resolver; + TaskBuilderProvider = provider; + Logger = logger; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs.meta new file mode 100644 index 0000000..16d3b34 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/BuildMagicCLI.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 513651522ca9845a5b8d9c1e9f4d9158 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error.meta new file mode 100644 index 0000000..13ac15b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 678b3dc1b6c24afebc6c0143ccb846b9 +timeCreated: 1710377190 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs new file mode 100644 index 0000000..1671aa3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs @@ -0,0 +1,22 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Error +{ + /// + /// Exception thrown when a build property cannot be resolved. + /// + public class BuildPropertyResolveException : Exception + { + public BuildPropertyResolveException(string message) : base(message) + { + } + + public BuildPropertyResolveException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs.meta new file mode 100644 index 0000000..2f187a3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/BuildPropertyResolveException.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63ea73ca6a0e498fb2540a71433f3b22 +timeCreated: 1711167132 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs new file mode 100644 index 0000000..7d4c2c9 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs @@ -0,0 +1,13 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Error +{ + public class CommandLineArgumentException : Exception + { + public CommandLineArgumentException(string message) : base(message) { } + } +} \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs.meta new file mode 100644 index 0000000..513d622 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Error/CommandLineArgumentException.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 78cb5d6b387b467eb3fe4bb72a804c2f +timeCreated: 1710377338 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal.meta new file mode 100644 index 0000000..0d84e5b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 75654c22df2244df99e94be5494cf899 +timeCreated: 1710305617 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs new file mode 100644 index 0000000..14b665b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs @@ -0,0 +1,85 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using BuildMagicEditor.Commandline.Error; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Resolves build properties considering the command line arguments. + /// + internal class BuildPropertyResolver + { + private BuildPropertyResolver( + IReadOnlyList overrideProperties, IBuildPropertyDeserializer[] deserializers) + { + _overrideProperties = overrideProperties.ToDictionary(x => x.Name); + _unresolvedOverrideProperties = overrideProperties.ToHashSet(); + _deserializers = deserializers; + } + + public static BuildPropertyResolver CreateDefault(IReadOnlyList overrideProperties) + { + return new BuildPropertyResolver(overrideProperties, CreateDefaultDeserializers()); + } + + /// + /// Resolve the property for the configuration. + /// + /// The configuration to resolve. + /// Resolved property. + public IBuildProperty ResolveProperty(IBuildConfiguration configuration) + { + var buildProperty = configuration.GatherProperty(); + if (!_overrideProperties.TryGetValue(configuration.PropertyName, out var overrideProperty)) + // If no override property specified, return the property of IBuildConfiguration. + return buildProperty; + + _unresolvedOverrideProperties.Remove(overrideProperty); + + try + { + foreach (var deserializer in _deserializers) + if (deserializer.WillProcess(buildProperty.ValueType)) + { + // If the deserializer is found, deserialize the value. + var deserializedValue = + deserializer.Deserialize(overrideProperty.Value, buildProperty.ValueType); + + return BuildProperty.Create(buildProperty.Name, buildProperty.ValueType, deserializedValue); + } + + throw new BuildPropertyResolveException($"No deserializer for {buildProperty.ValueType}."); + } + catch (Exception e) + { + throw new BuildPropertyResolveException("Failed to resolve the property.", e); + } + } + + public OverrideProperty[] GetUnresolvedOverrideProperties() + { + return _unresolvedOverrideProperties.ToArray(); + } + + private static IBuildPropertyDeserializer[] CreateDefaultDeserializers() + { + return new IBuildPropertyDeserializer[] + { + new IntBuildPropertyDeserializer(), + new SingleBuildPropertyDeserializer(), + new EnumBuildPropertyDeserializer(), + new StringBuildPropertyDeserializer(), + new SerializableTypeBuildPropertyDeserializer() + }; + } + + private readonly Dictionary _overrideProperties; + private readonly IBuildPropertyDeserializer[] _deserializers; + private readonly HashSet _unresolvedOverrideProperties; + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs.meta new file mode 100644 index 0000000..faf5494 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/BuildPropertyResolver.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 80d1411320be44bb8ef0d3c357c21713 +timeCreated: 1711065891 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs new file mode 100644 index 0000000..31049ac --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs @@ -0,0 +1,97 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.Linq; +using BuildMagicEditor.Commandline.Error; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Command line parser for BuildMagic CLI. + /// + internal class CommandLineParser + { + /// + /// The prefix of option string. + /// + private static readonly string OptionPrefix = "-"; + + internal CommandLineParser(string[] args) + { + _args = args; + } + + public static CommandLineParser Create() + { + return new CommandLineParser(Environment.GetCommandLineArgs()); + } + + /// + /// Parse options with specified key. + /// + /// The key for options. + /// The options. + /// + /// If options are found, returns true. Otherwise, returns false. + /// + public bool TryParse(string key, out string[] options) + { + if (!TryParseInternal(key, out options)) return false; + return options.Length > 0; + } + + private bool TryParseInternal(string key, out string[] options) + { + // If the key does not start with the option prefix, add it. + if (!key.StartsWith(OptionPrefix)) key = $"{OptionPrefix}{key}"; + + var ret = new List(); + var hasFlag = false; + for (var i = 0; i < _args.Length; i++) + { + if (_args[i] == key) + { + hasFlag = true; + if (i + 1 >= _args.Length) continue; + + // option prefix is not allowed as a value. + if (_args[i + 1].StartsWith(OptionPrefix)) continue; + + ret.Add(_args[i + 1]); + } + } + + options = ret.ToArray(); + return hasFlag; + } + + public string[] Parse(string key) + { + if (TryParse(key, out var options)) + { + return options; + } + + throw new CommandLineArgumentException($"{key} does not specified."); + } + + public string ParseFirst(string key) + { + return Parse(key).First(); + } + + public bool HasFlag(string key, bool allowValue = false) + { + if (!TryParse(key, out var options)) return false; + if (!allowValue && options.Length != 0) + throw new CommandLineArgumentException( + $"{key} must be specified as a flag and does not accept any value."); + return true; + } + + private readonly string[] _args; + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs.meta new file mode 100644 index 0000000..8a05b2c --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/CommandLineParser.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 84f9e129613041f18adcf08aebaac54b +timeCreated: 1710376015 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer.meta new file mode 100644 index 0000000..eb97e62 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9e4dd3607a634164ab3965de32746c73 +timeCreated: 1711067673 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs new file mode 100644 index 0000000..db70aff --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs @@ -0,0 +1,26 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Enum build property deserializer. + /// + internal class EnumBuildPropertyDeserializer : IBuildPropertyDeserializer + { + /// + public bool WillProcess(Type valueType) + { + return valueType.IsEnum; + } + + /// + public object Deserialize(string value, Type valueType) + { + return Enum.Parse(valueType, value); + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..9fa0138 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/EnumBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 04b336c7f2f84a5b98dd241e8c051183 +timeCreated: 1711152019 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs new file mode 100644 index 0000000..d478f12 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs @@ -0,0 +1,26 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Int32 build property deserializer. + /// + internal class IntBuildPropertyDeserializer : IBuildPropertyDeserializer + { + /// + public bool WillProcess(Type valueType) + { + return valueType == typeof(int); + } + + /// + public object Deserialize(string value, Type _) + { + return int.Parse(value); + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..71e76c3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/IntBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dbffcb01c36c4070a11b8de9624e0e75 +timeCreated: 1711068346 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs new file mode 100644 index 0000000..76afa30 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs @@ -0,0 +1,27 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using UnityEngine; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Serializable type build property deserializer. + /// + internal class SerializableTypeBuildPropertyDeserializer : IBuildPropertyDeserializer + { + /// + public bool WillProcess(Type valueType) + { + return !valueType.IsAbstract && !valueType.IsPrimitive && valueType.IsSerializable; + } + + /// + public object Deserialize(string value, Type valueType) + { + return JsonUtility.FromJson(value, valueType); + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..17f7f1e --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SerializableTypeBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 13285c89469441a7a45efc574940d650 +timeCreated: 1711152870 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs new file mode 100644 index 0000000..0b1075b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs @@ -0,0 +1,26 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// float build property deserializer. + /// + internal class SingleBuildPropertyDeserializer : IBuildPropertyDeserializer + { + /// + public bool WillProcess(Type valueType) + { + return valueType == typeof(float); + } + + /// + public object Deserialize(string value, Type _) + { + return float.Parse(value); + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..744199b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/SingleBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1a0ce67cff24415f8c1cc6fd8cce256a +timeCreated: 1711152571 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs new file mode 100644 index 0000000..9da3a93 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs @@ -0,0 +1,23 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Internal +{ + internal class StringBuildPropertyDeserializer : IBuildPropertyDeserializer + { + /// + public bool WillProcess(Type valueType) + { + return valueType == typeof(string); + } + + /// + public object Deserialize(string value, Type _) + { + return value; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..f78db8a --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/Deserializer/StringBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 84586c551ad5423cbf22f7e2f014c6c0 +timeCreated: 1711152758 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs new file mode 100644 index 0000000..f801553 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs @@ -0,0 +1,36 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Interface for deserializing build properties from string. + /// + internal interface IBuildPropertyDeserializer + { + /// + /// Determines if this deserializer can process the given value type. + /// + /// + /// The type of the value to process. + /// + bool WillProcess(Type valueType); + + /// + /// Deserialize the given value into a build property. + /// + /// + /// The string value of the property. + /// + /// + /// The type of the value to deserialize. + /// + /// + /// Build property. + /// + object Deserialize(string value, Type valueType); + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs.meta new file mode 100644 index 0000000..0accdeb --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/IBuildPropertyDeserializer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 01cb8844b7a9429b8afc39ddb2e40df1 +timeCreated: 1711067573 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs new file mode 100644 index 0000000..bd1273d --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs @@ -0,0 +1,28 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +namespace BuildMagicEditor.Commandline.Internal +{ + /// + /// Override property. + /// + internal class OverrideProperty + { + /// + /// Name of the property. + /// + public string Name { get; } + + /// + /// Value of the property. + /// + public string Value { get; } + + public OverrideProperty(string name, string value) + { + Name = name; + Value = value; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs.meta new file mode 100644 index 0000000..996edc3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Editor/Internal/OverrideProperty.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 30cfafc8a5cc4cde96405b585deae06f +timeCreated: 1711063217 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests.meta new file mode 100644 index 0000000..602ff45 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d93ea3138b91f4cd3aa8343b30b334ce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef new file mode 100644 index 0000000..d408592 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef @@ -0,0 +1,25 @@ +{ + "name": "BuildMagic.Commandline.Editor.Tests", + "rootNamespace": "BuildMagicEditor.Commandline.Tests", + "references": [ + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "BuildMagic.Editor", + "BuildMagic.Commandline.Editor" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef.meta new file mode 100644 index 0000000..1c1d1e3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagic.Commandline.Editor.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b4363f0d64ad465e91453a0c1203cc0 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs new file mode 100644 index 0000000..efa0a7c --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs @@ -0,0 +1,79 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System.Collections.Generic; +using BuildMagicEditor.Commandline.Internal; +using NUnit.Framework; + +namespace BuildMagicEditor.Commandline.Tests +{ + public class BuildMagicCLITest + { + [Test] + public void Internal_ParseOverriderProperties() + { + var args = "-batchmode -quit -override KEY1=VALUE1 -override KEY2=VALUE2".Split(' '); + var commandlineParser = new CommandLineParser(args); + + var overrideProperties = BuildMagicCLI.ParseOverrideProperties(commandlineParser); + Assert.AreEqual(2, overrideProperties.Count); + Assert.AreEqual("KEY1", overrideProperties[0].Name); + Assert.AreEqual("VALUE1", overrideProperties[0].Value); + + Assert.AreEqual("KEY2", overrideProperties[1].Name); + Assert.AreEqual("VALUE2", overrideProperties[1].Value); + } + + [Test] + public void Internal_ParseOverriderProperties_NoOverrideSpecified() + { + var args = "-batchmode -quit".Split(' '); + var commandlineParser = new CommandLineParser(args); + + var overrideProperties = BuildMagicCLI.ParseOverrideProperties(commandlineParser); + Assert.AreEqual(0, overrideProperties.Count); + } + + [Test] + public void Internal_ParseOverriderProperties_Aliases() + { + var args = "-batchmode -quit -override KEY1=VALUE1 -key2 VALUE2".Split(' '); + var commandlineParser = new CommandLineParser(args); + + var aliases = new Dictionary + { + { "key2", "KEY2" } + }; + + var overrideProperties = BuildMagicCLI.ParseOverrideProperties(commandlineParser, aliases); + + Assert.AreEqual(2, overrideProperties.Count); + + Assert.AreEqual("KEY1", overrideProperties[0].Name); + Assert.AreEqual("VALUE1", overrideProperties[0].Value); + + Assert.AreEqual("KEY2", overrideProperties[1].Name); + Assert.AreEqual("VALUE2", overrideProperties[1].Value); + } + + [Test] + public void Internal_ParseOverriderProperties_Aliases_WithoutOverride() + { + var args = "-batchmode -quit -override -key2 VALUE2".Split(' '); + var commandlineParser = new CommandLineParser(args); + + var aliases = new Dictionary + { + { "key2", "KEY2" } + }; + + var overrideProperties = BuildMagicCLI.ParseOverrideProperties(commandlineParser, aliases); + + Assert.AreEqual(1, overrideProperties.Count); + + Assert.AreEqual("KEY2", overrideProperties[0].Name); + Assert.AreEqual("VALUE2", overrideProperties[0].Value); + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs.meta new file mode 100644 index 0000000..6a8e525 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildMagicCLITest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66b4c71c705da47249fabd879d7627fe +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs new file mode 100644 index 0000000..5e0efa6 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs @@ -0,0 +1,102 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using BuildMagicEditor.Commandline.Internal; +using NUnit.Framework; + +namespace BuildMagicEditor.Commandline.Tests +{ + public class SerializableTypeBuildPropertyDeserializerTest + { + [Test] + public void SerializableTypeBuildPropertyDeserializer_WillProcess() + { + var deserializer = new SerializableTypeBuildPropertyDeserializer(); + Assert.IsTrue(deserializer.WillProcess(typeof(SerializableClass))); + Assert.IsFalse(deserializer.WillProcess(typeof(NonSerializableClass))); + } + + [Test] + public void SerializableTypeBuildPropertyDeserializer_Deserialize() + { + var deserializer = new SerializableTypeBuildPropertyDeserializer(); + var deserializedValue = deserializer.Deserialize("{\"value\":123}", typeof(SerializableClass)); + + Assert.AreEqual(typeof(SerializableClass), deserializedValue.GetType()); + Assert.AreEqual(123, ((SerializableClass)deserializedValue).value); + } + + [Test] + public void IntBuildPropertyDeserializer_WillProcess() + { + var deserializer = new IntBuildPropertyDeserializer(); + Assert.IsTrue(deserializer.WillProcess(typeof(int))); + Assert.IsFalse(deserializer.WillProcess(typeof(string))); + } + + [Test] + public void IntBuildPropertyDeserializer_Deserialize() + { + var deserializer = new IntBuildPropertyDeserializer(); + var deserializedValue = deserializer.Deserialize("345", typeof(int)); + + Assert.AreEqual(typeof(int), deserializedValue.GetType()); + Assert.AreEqual(345, (int)deserializedValue); + } + + [Test] + public void SingleBuildPropertyDeserializer_WillProcess() + { + var deserializer = new SingleBuildPropertyDeserializer(); + Assert.IsTrue(deserializer.WillProcess(typeof(float))); + Assert.IsFalse(deserializer.WillProcess(typeof(string))); + } + + [Test] + public void SingleBuildPropertyDeserializer_Deserialize() + { + var deserializer = new SingleBuildPropertyDeserializer(); + var deserializedValue = deserializer.Deserialize("12.5", typeof(float)); + + Assert.AreEqual(typeof(float), deserializedValue.GetType()); + Assert.AreEqual(12.5f, (float)deserializedValue); + } + + [Test] + public void EnumBuildPropertyDeserializer_WillProcess() + { + var deserializer = new EnumBuildPropertyDeserializer(); + Assert.IsTrue(deserializer.WillProcess(typeof(EnumType))); + Assert.IsFalse(deserializer.WillProcess(typeof(string))); + } + + [Test] + public void EnumBuildPropertyDeserializer_Deserialize() + { + var deserializer = new EnumBuildPropertyDeserializer(); + var deserializedValue = deserializer.Deserialize("Two", typeof(EnumType)); + + Assert.AreEqual(typeof(EnumType), deserializedValue.GetType()); + Assert.AreEqual(EnumType.Two, (EnumType)deserializedValue); + } + + private enum EnumType + { + One, + Two, + Three + } + + private class NonSerializableClass + { + } + + [Serializable] + private class SerializableClass + { + public int value; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs.meta new file mode 100644 index 0000000..8516fa9 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyDeserializerTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e822c7c5e3ef45e6b9f6bec7e9c7651e +timeCreated: 1711165476 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs new file mode 100644 index 0000000..31e4a1c --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs @@ -0,0 +1,56 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using BuildMagicEditor.Commandline.Internal; +using NUnit.Framework; + +namespace BuildMagicEditor.Commandline.Tests +{ + public class BuildPropertyResolverTest + { + [Test] + public void Resolve_WithOverride_IntProperty() + { + var overrideProperty = new OverrideProperty("int", "345"); + var resolver = BuildPropertyResolver.CreateDefault(new[] { overrideProperty }); + + var property = resolver.ResolveProperty(new SampleIntBuildConfiguration()); + Assert.AreEqual(int.Parse(overrideProperty.Value), (int)property.Value); + } + + [Test] + public void Resolve_WithoutOverride() + { + var resolver = BuildPropertyResolver.CreateDefault(Array.Empty()); + + var configuration = new SampleIntBuildConfiguration(); + var property = resolver.ResolveProperty(configuration); + Assert.AreEqual((int)configuration.GatherProperty().Value, (int)property.Value); + } + + [Test] + public void Resolve_WithWrongPropertyName() + { + var overrideProperty = new OverrideProperty("wrongName", "345"); + var resolver = BuildPropertyResolver.CreateDefault(new[] { overrideProperty }); + + var configuration = new SampleIntBuildConfiguration(); + var property = resolver.ResolveProperty(configuration); + Assert.AreEqual((int)configuration.GatherProperty().Value, (int)property.Value); + } + + private class SampleIntBuildConfiguration : IBuildConfiguration + { + public string PropertyName => GatherProperty().Name; + + public IBuildProperty GatherProperty() + { + return BuildProperty.Create("int", 123); + } + + public Type TaskType => null; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs.meta new file mode 100644 index 0000000..025eb43 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/BuildPropertyResolverTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4e4a9ff1e77943d5a4a253f1726bc726 +timeCreated: 1711167059 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs new file mode 100644 index 0000000..dc5a328 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs @@ -0,0 +1,80 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using BuildMagicEditor.Commandline.Error; +using BuildMagicEditor.Commandline.Internal; +using NUnit.Framework; + +namespace BuildMagicEditor.Commandline.Tests +{ + public class CommandLineParserTest + { + [Test] + public void CommandlineParserTest_TryParse() + { + var args = $"{PreBuildCommandline} -batchmode -quit -override KEY1=VALUE1 -override KEY2=VALUE2" + .Split(' '); + + var parser = new CommandLineParser(args); + Assert.IsTrue(parser.TryParse("override", out var options)); + Assert.AreEqual(2, options.Length); + Assert.AreEqual("KEY1=VALUE1", options[0]); + Assert.AreEqual("KEY2=VALUE2", options[1]); + + // The key with option prefix are supported. + Assert.IsTrue(parser.TryParse("-override", out options)); + Assert.AreEqual(2, options.Length); + Assert.AreEqual("KEY1=VALUE1", options[0]); + Assert.AreEqual("KEY2=VALUE2", options[1]); + + // A non-existent key is specified. + Assert.IsFalse(parser.TryParse("none", out _)); + } + + [Test] + public void CommandlineParserTest_TryParse_EdgeCase_OptionsWerePassedConsecutively() + { + // "-override -override" is not valid. + var args = + $"{PreBuildCommandline} -batchmode -quit -override -override KEY1=VALUE1 -override KEY2=VALUE2" + .Split(' '); + + var parser = new CommandLineParser(args); + Assert.IsTrue(parser.TryParse("override", out var options)); + Assert.AreEqual(2, options.Length); + Assert.AreEqual("KEY1=VALUE1", options[0]); + Assert.AreEqual("KEY2=VALUE2", options[1]); + } + + [Test] + public void CommandlineParserTest_Parse() + { + var args = $"{PreBuildCommandline} -batchmode -quit -override KEY1=VALUE1 -override KEY2=VALUE2" + .Split(' '); + + var parser = new CommandLineParser(args); + var options = parser.Parse("override"); + Assert.AreEqual(2, options.Length); + Assert.AreEqual("KEY1=VALUE1", options[0]); + Assert.AreEqual("KEY2=VALUE2", options[1]); + + // The key with option prefix are supported. + options = parser.Parse("-override"); + Assert.AreEqual(2, options.Length); + Assert.AreEqual("KEY1=VALUE1", options[0]); + Assert.AreEqual("KEY2=VALUE2", options[1]); + + void CheckThrowingException() + { + parser.Parse("none"); + } + + // If A non-existent key is specified, CommandLineArgumentException is thrown. + Assert.That(CheckThrowingException, Throws.TypeOf()); + } + + private static readonly string PreBuildCommandline = + "/Applications/Unity/Hub/Editor/2021.3.31f1/Unity.app/Contents/MacOS/Unity -projectPath /Path/To/Your/Project -executeMethod BuildMagicCLI.PreBuild"; + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs.meta new file mode 100644 index 0000000..dba0b69 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Commandline/Tests/CommandLineParserTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a25b996866844f89a6aaeed48dbdf90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window.meta new file mode 100644 index 0000000..7515900 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e908fefd57bc24952b9265cf0112ebde +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor.meta new file mode 100644 index 0000000..e12f5cc --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e7550239f19eb4ae69db1c4a921ea975 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs new file mode 100644 index 0000000..23ba2b8 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs @@ -0,0 +1,20 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using UnityEditor; +using UnityEngine.UIElements; + +namespace BuildMagic.Window.Editor +{ + internal static class AssetLoader + { + private const string AssetPath = "Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/"; + + public static VisualTreeAsset LoadUxml(string name) + => AssetDatabase.LoadAssetAtPath($"{AssetPath}/Uxml/{name}.uxml"); + + public static StyleSheet LoadUss(string name) + => AssetDatabase.LoadAssetAtPath($"{AssetPath}/Uss/{name}.uss"); + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs.meta new file mode 100644 index 0000000..93a4532 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetLoader.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7aee3c083e4547589fbe2ae9a3e5ba1a +timeCreated: 1711960660 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs new file mode 100644 index 0000000..62a9ac5 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs @@ -0,0 +1,20 @@ +// -------------------------------------------------------------- +// Copyright 2024 CyberAgent, Inc. +// -------------------------------------------------------------- + +using System; +using UnityEditor; + +namespace BuildMagic.Window.Editor +{ + internal class AssetSaveProcessor : AssetModificationProcessor + { + public static event Action OnWillSaveAssetsEvent; + + private static string[] OnWillSaveAssets(string[] paths) + { + OnWillSaveAssetsEvent?.Invoke(); + return paths; + } + } +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs.meta new file mode 100644 index 0000000..c2bb780 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/AssetSaveProcessor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1abbb2ff05f84097a61ea5900eafd6b7 +timeCreated: 1716174578 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets.meta new file mode 100644 index 0000000..c64e44d --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1df14c07120c47358b1e7bd5ce200ae1 +timeCreated: 1711439040 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/.gitignore b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates.meta new file mode 100644 index 0000000..d8c16b9 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e8cad22f9a06c43a3b4b5563f676c76e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json new file mode 100644 index 0000000..484327f --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json @@ -0,0 +1,54 @@ +{ + "_name": "BuiltinTemplate", + "_postBuildConfigurations": [], + "_internalPrepareConfigurations": [], + "_preBuildConfigurations": [ + { + "rid": 1000 + }, + { + "rid": 1001 + }, + { + "rid": 1002 + } + ], + "references": { + "version": 2, + "RefIds": [ + { + "rid": 1000, + "type": { + "class": "BuildPlayerOptionsAddBuildOptionsTaskConfiguration", + "ns": "BuildMagicEditor.BuiltIn", + "asm": "BuildMagic.Editor" + }, + "data": { + "_value": 0 + } + }, + { + "rid": 1001, + "type": { + "class": "BuildPlayerOptionsAddScenesTaskConfiguration", + "ns": "BuildMagicEditor.BuiltIn", + "asm": "BuildMagic.Editor" + }, + "data": { + "_value": [] + } + }, + { + "rid": 1002, + "type": { + "class": "BuildPlayerOptionsSetLocationPathNameTaskConfiguration", + "ns": "BuildMagicEditor.BuiltIn", + "asm": "BuildMagic.Editor" + }, + "data": { + "_value": "Build/" + } + } + ] + } +} \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json.meta new file mode 100644 index 0000000..cb06636 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Templates/BuiltinTemplate.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 64d03605cdb3a4c858cc40e844e6ab5c +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss.meta new file mode 100644 index 0000000..4f4a748 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 049c845d13b1544f486e44ce9fd34b7d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss new file mode 100644 index 0000000..e72e908 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss @@ -0,0 +1,75 @@ +ListView.hide-size .unity-list-view__size-field { + display: none; +} + +ListView.hide-empty .unity-list-view__empty-label { + display: none; +} + +.configuration-list-foldout > #unity-content { + margin-left: 24px; +} + +Label.left-entry { + padding-left: 18px; +} + +.right-pane-container { +} + +.header { + padding-top: 16px; + padding-bottom: 16px; + padding-left: 12px; + flex-direction: row; + flex-shrink: 0; +} + +.configuration-list-foldout > Toggle { + font-size: 15px; + -unity-font-style: bold; + padding-left: 12px; + padding-top: 4px; + padding-bottom: 4px; + background-color: var(--unity-colors-box-background); + margin-right: 0; + margin-left: 0; + margin-top: 0; + margin-bottom: 8px; +} + +Label.configuration-name { + margin-top: 6px; + margin-bottom: 6px; + font-size: 13px; +} + +.configuration-list-foldout { + margin-bottom: 20px; + margin-top: 0; +} + +.setting-name { + -unity-font-style: bold; + font-size: 18px; +} + +.scheme-link-label-leading { + opacity: 0.7; +} + +.scheme-link-label-link { + color: var(--unity-colors-link-text); +} + +.scheme-link-label-link:hover { + color: var(--unity-colors-link-text); + opacity: 0.7; +} + +.scheme-link-label { + flex-direction: row; + align-self: flex-end; + margin-bottom: 1px; + margin-left: 4px; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss.meta new file mode 100644 index 0000000..7f2e6c3 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: db6bd1054d55e43f88d317c87db7c0bf +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss new file mode 100644 index 0000000..c4bdd62 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss @@ -0,0 +1,4 @@ +VisualElement.auto-mark { + background-color: rgb(223, 223, 223); + width: 4px; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss.meta new file mode 100644 index 0000000..ee234b4 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Dark.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cabc78b97d2b94991ba370e91a18c23d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss new file mode 100644 index 0000000..4a092f7 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss @@ -0,0 +1,4 @@ +VisualElement.auto-mark { + background-color: rgb(26, 26, 26); + width: 4px; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss.meta new file mode 100644 index 0000000..ab0a255 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/BuildMagicWindow_Light.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c35ebb4023324d7296baf9ebf50761d +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers.meta new file mode 100644 index 0000000..fb8534d --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 650e0a2215d504d6884519b0cc11542e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss new file mode 100644 index 0000000..f1f6b73 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss @@ -0,0 +1,128 @@ +.patformgrouping-box { + background-color: var(--unity-colors-helpbox-background); + border-top-color: var(--unity-colors-helpbox-border); + border-right-color: var(--unity-colors-helpbox-border); + border-bottom-color: var(--unity-colors-helpbox-border); + border-left-color: var(--unity-colors-helpbox-border); + flex-grow: 1; +} + +.patformgrouping-tab-button { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + padding-top: 0; + padding-right: 0; + padding-bottom: 0; + padding-left: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-width: 1px; + overflow: hidden; + border-top-width: 0.5px; + border-right-width: 0.5px; + border-left-width: 0.5px; + flex-direction: row; + border-bottom-color: rgba(255, 255, 255, 0); + flex-grow: 1; + background-color: var(--unity-colors-helpbox-background); + border-top-color: var(--unity-colors-helpbox-border); + border-right-color: var(--unity-colors-helpbox-border); + border-left-color: var(--unity-colors-helpbox-border); +} + +.patformgrouping-tab-button.inactive { + background-color: var(--unity-colors-tab-background); + border-bottom-color: var(--unity-colors-helpbox-border); +} + +.patformgrouping-tab-button.inactive:hover { + background-color: var(--unity-colors-tab-background-hover); +} + +.patformgrouping-tab-remove-button { + visibility: visible; + background-color: rgba(0, 0, 0, 0); + position: absolute; + right: 0; + top: 0; + bottom: 0; + justify-content: center; + border-top-width: 0; + border-right-width: 0; + border-bottom-width: 0; + border-left-width: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + padding-top: 0; + padding-right: 5px; + padding-bottom: 0; + padding-left: 5px; +} + +.patformgrouping-tab-remove-button:hover Label { + background-color: rgba(99, 99, 99, 0.52); +} + +.patformgrouping-tab-button-image { + flex-grow: 1; + margin-top: 2px; + margin-bottom: 2px; + background-color: rgba(0, 0, 0, 0); +} + +.platformgrouping-root { + flex-grow: 1; + overflow: hidden; + border-top-width: 0.5px; + border-right-width: 0.5px; + border-bottom-width: 0.5px; + border-left-width: 0.5px; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + margin-top: 5px; + margin-right: 5px; + margin-bottom: 5px; + margin-left: 5px; +} + +.platformgrouping-tab-container { + flex-direction: row; + height: 25px; +} + +.platformgrounping-tab-button-container { + flex-grow: 1; + flex-direction: row; +} + +.platformgrouping-tab-add-button { + flex-grow: 0; + padding-left: 5px; + padding-right: 5px; +} + +.platformgrouping-content-container { + flex-grow: 1; + border-top-width: 0; + border-right-width: 0.5px; + border-bottom-width: 0.5px; + border-left-width: 0.5px; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; + padding-top: 10px; + padding-right: 10px; + padding-bottom: 10px; + padding-left: 10px; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss.meta new file mode 100644 index 0000000..c1f36ca --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/Drawers/SerializableDictionaryTabView.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bd5a34acb356d4401ba34cfb2b69896e +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows.meta new file mode 100644 index 0000000..855f154 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fc188257a8c74c8d933a3b5a0cb4f815 +timeCreated: 1716535879 \ No newline at end of file diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss new file mode 100644 index 0000000..b50269d --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss @@ -0,0 +1,35 @@ +Button.tab { + margin-top: 0; + margin-right: 0; + margin-bottom: 0; + margin-left: 0; + padding-top: 0; + padding-right: 0; + padding-bottom: 0; + padding-left: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + border-bottom-width: 1px; + overflow: hidden; + border-top-width: 0.5px; + border-right-width: 0.5px; + border-left-width: 0.5px; + flex-direction: row; + border-bottom-color: rgba(255, 255, 255, 0); + flex-grow: 1; + background-color: var(--unity-colors-helpbox-background); + border-top-color: var(--unity-colors-helpbox-border); + border-right-color: var(--unity-colors-helpbox-border); + border-left-color: var(--unity-colors-helpbox-border); +} + +Button.tab.selected { + background-color: var(--unity-colors-tab-background); + border-bottom-color: var(--unity-colors-helpbox-border); +} + +Button.tab.selected:hover { + background-color: var(--unity-colors-tab-background-hover); +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss.meta new file mode 100644 index 0000000..f6f4875 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eefc5bfee6e6844f9992e54abe92693e +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss new file mode 100644 index 0000000..0b1ba9b --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss @@ -0,0 +1,19 @@ +.diff-red * { + background-color: #2E1C20; +} + +.diff-red-strong * { + background-color: #783634; +} + +.diff-green * { + background-color: #18261F; +} + +.diff-green-strong * { + background-color: #2F5632; +} + +.diff-empty * { + background-color: #171B22; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss.meta new file mode 100644 index 0000000..15cc1b6 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Dark.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fb8b6d073474c471a98e9a8062030890 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss new file mode 100644 index 0000000..0f61503 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss @@ -0,0 +1,19 @@ +.diff-red * { + background-color: #FCECEA; +} + +.diff-red-strong * { + background-color: #F7C5C1; +} + +.diff-green * { + background-color: #E1FAE3; +} + +.diff-green-strong * { + background-color: #BAECBF; +} + +.diff-empty * { + background-color: #F7F8FA; +} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss.meta new file mode 100644 index 0000000..200a6ca --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uss/SubWindows/DiffWindow_Light.uss.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4d979b620d6f45378e7ecf70f7b81e5 +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 12385, guid: 0000000000000000e000000000000000, type: 0} + disableValidation: 0 diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml.meta new file mode 100644 index 0000000..be2349c --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fcadcbcbad6ea49b1aec8d77f4d66cb8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml new file mode 100644 index 0000000..f017d51 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml @@ -0,0 +1,5 @@ + + + + + diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml.meta new file mode 100644 index 0000000..0cc82ea --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/ConfigurationEntry.uxml.meta @@ -0,0 +1,10 @@ +fileFormatVersion: 2 +guid: 7626b547203fc45f9af5190cb125bb2b +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 13804, guid: 0000000000000000e000000000000000, type: 0} diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers.meta b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers.meta new file mode 100644 index 0000000..ed7e583 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4aac02d5f888547d89a4e305064de694 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers/SerializableDictionaryTabView.uxml b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers/SerializableDictionaryTabView.uxml new file mode 100644 index 0000000..2611709 --- /dev/null +++ b/Packages/jp.co.cyberagent.buildmagic/BuildMagic.Window/Editor/Assets/Uxml/Drawers/SerializableDictionaryTabView.uxml @@ -0,0 +1,10 @@ + +