Skip to content

Commit

Permalink
upd: UnityCsProjectConverter
Browse files Browse the repository at this point in the history
  • Loading branch information
sator-imaging committed Jul 22, 2024
1 parent c4d754b commit cb660fd
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 102 deletions.
203 changes: 109 additions & 94 deletions Unity/Editor/UnityCsProjectConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using Unity.CodeEditor;
using UnityEditor;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
Expand Down Expand Up @@ -81,16 +79,20 @@ static string OnGeneratedCSProject(string path, string content)
if (!Prefs.Instance.EnableGenerator)
return content;

if (CompilationPipeline.codeOptimization == CodeOptimization.Debug)
{
if (Prefs.Instance.DisableInDebugMode)
return content;
}

const string MSBUILD_AUTO_IMPORT = "Directory.Build";
CreateFileIfNotExists(MSBUILD_AUTO_IMPORT + EXT_PROPS, "<Nullable>enable</Nullable>");
CreateFileIfNotExists(MSBUILD_AUTO_IMPORT + EXT_TARGETS, null);

//// TODO: double clicking .cs file in Unity always call this method for all assemblies (.asmdef)
//// need to make it more efficient and faster
//UnityEngine.Debug.Log(nameof(UnityCsProjectConverter) + ": " + nameof(OnGeneratedCSProject) + ": " + path);

CreateFileIfNotExists(UNITY_PROJ_DIR_NAME + EXT_SHARED + EXT_PROPS);
CreateFileIfNotExists(UNITY_PROJ_DIR_NAME + EXT_EDITOR + EXT_PROPS);
CreateFileIfNotExists(UNITY_PROJ_DIR_NAME + EXT_SHARED + EXT_TARGETS);
CreateFileIfNotExists(UNITY_PROJ_DIR_NAME + EXT_EDITOR + EXT_TARGETS);

var xdoc = XDocument.Parse(content);
var ns = XNamespace.Get(XML_NS);
var root = xdoc.Root;
Expand All @@ -108,52 +110,51 @@ static string OnGeneratedCSProject(string path, string content)
}


const string TAG_PROPERTY_GROUP = "PropertyGroup";

var propGroup = xdoc.Descendants(ns.GetName(TAG_PROPERTY_GROUP)).FirstOrDefault();
if (propGroup == null)
{
UnityEngine.Debug.LogError("unable to take .csproj node: " + TAG_PROPERTY_GROUP);
return content;
}


// Custom .props/.targets!!
const string TAG_IMPORT = "Import";
const string ATTR_PROJECT = "Project";
var importTypes = new string[] { EXT_SHARED, EXT_EDITOR };

/* = .props = */

// reversed order!! later appears earlier in xml
root.AddFirst(new XComment(string.Empty));

foreach (var import in importTypes.Reverse()) // reversed order!!
// NOTE: these update will show "Attach to Unity" button in VS toolbar.
// but it is not work correctly just slows down VS launch time.
//https://github.com/Unity-Technologies/com.unity.ide.visualstudio/blob/master/Packages/com.unity.ide.visualstudio/Editor/ProjectGeneration/SdkStyleProjectGeneration.cs
/*
const string TAG_CAPABILITY = "ProjectCapability";
const string ATTR_INCLUDE = "Include";
const string ATTR_REMOVE = "Remove";
const string TAG_ITEMGROUP = "ItemGroup";
if (Prefs.Instance.EnableSdkStyle)
{
if (_generateForBuild && import == EXT_EDITOR)
continue;

root.AddFirst(new XElement(ns.GetName(TAG_IMPORT), new XAttribute(ATTR_PROJECT, UNITY_PROJ_DIR_NAME + import + EXT_PROPS)));
}

root.AddFirst(new XComment(nameof(UnityCsProjectConverter)));
root.AddFirst(new XComment(string.Empty));
//header
var headerItemGroup = new XElement(ns.GetName(TAG_ITEMGROUP));
headerItemGroup.Add(new XComment(nameof(UnityCsProjectConverter)));
foreach (var cap in new string[]
{
"Unity",
})
{
headerItemGroup.Add(new XElement(ns.GetName(TAG_CAPABILITY), new XAttribute(ATTR_INCLUDE, cap)));
}
root.AddFirst(headerItemGroup);
/* = .targets = */

root.Add(new XComment(string.Empty));
root.Add(new XComment(nameof(UnityCsProjectConverter)));

foreach (var import in importTypes)
{
if (_generateForBuild && import == EXT_EDITOR)
continue;
//footer
var footerItemGroup = new XElement(ns.GetName(TAG_ITEMGROUP));
footerItemGroup.Add(new XComment(nameof(UnityCsProjectConverter)));
root.Add(new XElement(ns.GetName(TAG_IMPORT), new XAttribute(ATTR_PROJECT, UNITY_PROJ_DIR_NAME + import + EXT_TARGETS)));
foreach (var cap in new string[]
{
"LaunchProfiles",
"SharedProjectReferences",
"ReferenceManagerSharedProjects",
"ProjectReferences",
"ReferenceManagerProjects",
"COMReferences",
"ReferenceManagerCOM",
"AssemblyReferences",
"ReferenceManagerAssemblies",
})
{
footerItemGroup.Add(new XElement(ns.GetName(TAG_CAPABILITY), new XAttribute(ATTR_REMOVE, cap)));
}
root.Add(footerItemGroup);
}

root.Add(new XComment(string.Empty));
*/


//version!?
Expand Down Expand Up @@ -191,32 +192,33 @@ public XDocumentWriter(StringBuilder sb) : base(sb, CultureInfo.InvariantCulture
public override Encoding Encoding => Encoding.UTF8;
}

readonly static StringBuilder cache_sb = new(capacity: 65536); // usual .csproj file size is around 60KB
readonly static StringBuilder cache_sb = new(capacity: 65536); // usual .csproj file size is around 60KiB
readonly static XDocumentWriter cache_writer = new(cache_sb);


/* helper ================================================================ */

static void CreateFileIfNotExists(string fileName)
static void CreateFileIfNotExists(string fileName, string? propertyGroupContent)
{
if (File.Exists(fileName))
return;

File.WriteAllText(fileName,
string content =
$@"<Project xmlns=""{XML_NS}"">
<PropertyGroup>
</PropertyGroup>
</Project>
",
Encoding.UTF8);
}
";

if (propertyGroupContent != null)
{
content += " " + propertyGroupContent.Replace("\n", "\n ").TrimEnd() + Environment.NewLine;
}

static XmlNode CreateCommentNode(XmlDocument xml, XmlNode root, string comment)
{
var result = xml.CreateNode(XmlNodeType.Comment, nameof(UnityCsProjectConverter), root.NamespaceURI);
result.InnerText = comment;
return result;
content +=
$@" </PropertyGroup>
</Project>
";

File.WriteAllText(fileName, content, Encoding.UTF8);
}


Expand All @@ -238,37 +240,8 @@ static void RegisterRegenerateActionOnlyOnBatchBuild()

static void RegenerateProjectFiles()
{
var generatorList = new List<object>();
foreach (var t in TypeCache.GetTypesDerivedFrom<object>())
{
if (!t.GetInterfaces().Any(x => x.Name == "IGenerator")) // vs and vscode plugin use same interface name
continue;

var generator = Activator.CreateInstance(t)
?? throw new NullReferenceException(t.ToString());

generatorList.Add(generator);
}

if (generatorList.Count == 0)
{
return;
}
else if (generatorList.Count > 1)
{
UnityEngine.Debug.LogError("multiple .csproj generators found: " + string.Join(", ", generatorList));
return;
}

MethodInfo Sync;
Sync = generatorList[0].GetType().GetMethod(nameof(Sync))
?? throw new NullReferenceException(nameof(Sync) + " method not found: " + generatorList[0]);

Sync.Invoke(generatorList[0], Array.Empty<object>());

// for commandline build
Console.WriteLine(nameof(UnityCsProjectConverter) + ": " + nameof(RegenerateProjectFiles));
UnityEngine.Debug.Log(nameof(UnityCsProjectConverter) + ": " + nameof(RegenerateProjectFiles));
var current = CodeEditor.Editor.CurrentCodeEditor;
current.SyncAll();
}


Expand Down Expand Up @@ -340,6 +313,7 @@ static class EditorExtension
const string MENU_ROOT = "File/C# Project (.csproj)/";
const string MENU_ENABLE_GENERATOR = MENU_ROOT + "Enable Override";
const string MENU_DISABLE_ON_BUILD = MENU_ROOT + "Disable Override on Build";
const string MENU_DISABLE_IN_DEBUG_MODE = MENU_ROOT + "Disable Override in Debug Mode \t (experimental)";
const string MENU_ENABLE_SDK_STYLE = MENU_ROOT + "Enable SDK Style \t (latest .csproj format)";
const string MENU_USE_VOID_SDK = MENU_ROOT + "Use \"Void\" SDK";
const string MENU_REVIEW_CSPROJ = MENU_ROOT + "Review Resulting .csproj...";
Expand All @@ -358,14 +332,23 @@ static void UnityEditor_Initialize()
var prefs = Prefs.Instance;
Menu.SetChecked(MENU_ENABLE_GENERATOR, prefs.EnableGenerator);
Menu.SetChecked(MENU_DISABLE_ON_BUILD, prefs.DisableOnBuild);
Menu.SetChecked(MENU_DISABLE_IN_DEBUG_MODE, prefs.DisableInDebugMode);
Menu.SetChecked(MENU_ENABLE_SDK_STYLE, prefs.EnableSdkStyle);
Menu.SetChecked(MENU_USE_VOID_SDK, prefs.UseVoidSdk);

// need to save to .json before recompile in Unity editor
CompilationPipeline.compilationStarted += _ => prefs.Save();
CompilationPipeline.compilationStarted -= OnCompilationStarted;
CompilationPipeline.compilationStarted += OnCompilationStarted;

CompilationPipeline.codeOptimizationChanged -= OnCodeOptimizationChanged;
CompilationPipeline.codeOptimizationChanged += OnCodeOptimizationChanged;
};
}

readonly static Action<object> OnCompilationStarted = _ => Prefs.Instance.Save();

readonly static Action<CodeOptimization> OnCodeOptimizationChanged = _ => RegenerateProjectFiles();


/* = enable generator = */

Expand All @@ -392,6 +375,30 @@ static void Menu_DisableOnBuild_Toggle()
}


/* = disable in debug mode = */

[MenuItem(MENU_DISABLE_IN_DEBUG_MODE, validate = true)]
static bool Menu_DisableInDebugMode_Validate() => Prefs.Instance.EnableGenerator;

[MenuItem(MENU_DISABLE_IN_DEBUG_MODE, priority = PRIORITY_MENU)]
static void Menu_DisableInDebugMode_Toggle()
{
if (Prefs.Instance.DisableInDebugMode == false)
{
if (!EditorUtility.DisplayDialog(nameof(UnityCsProjectConverter),
"Strongly recommend that turn off \"Auto Reload Project\" option in Tools for Unity preference found in Visual Studio.\n\n"
+ "When auto-reloading is enabled and Visual Studio is open while changing to debug mode, it could cause VS indefinitely reloading.",
"Confirm", "cancel"))
{
return;
}
}

var toggled = !Prefs.Instance.DisableInDebugMode;
Prefs.Instance.DisableInDebugMode = toggled;
Menu.SetChecked(MENU_DISABLE_IN_DEBUG_MODE, toggled);
}


/* = enable sdk style = */

Expand Down Expand Up @@ -569,6 +576,7 @@ public sealed class Prefs
// menus
[SerializeField] bool enableGenerator = true;
[SerializeField] bool disableOnBuild = false;
[SerializeField] bool disableInDebugMode = false;
[SerializeField] bool enableSdkStyle = true;
[SerializeField] bool useVoidSdk = true;

Expand Down Expand Up @@ -655,6 +663,13 @@ public bool UseVoidSdk
//Save();
}
}

public bool DisableInDebugMode
{
get => disableInDebugMode;
set => disableInDebugMode = value;
}

}

}
Expand Down
9 changes: 1 addition & 8 deletions Unity/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,14 @@ UnityCsProjectConverter.Prefs.Instance.UseVoidSdk = false;

# `.csproj` Overrides

Extension will add the following overrides to `.csproj`.
To customize property, you can use the following overrides to `.csproj`.

- Unity project folder
- `<ProjectFolderName>.UnityShared.props`
- `<ProjectFolderName>.UnityShared.targets`
- `<ProjectFolderName>.UnityEditor.props` (see *note*)
- `<ProjectFolderName>.UnityEditor.targets` (see *note*)
- Unity project or ascendant folder
- `Directory.Build.props` (by msbuild)
- `Directory.Build.targets` (by msbuild)

> [!NOTE]
> `.props` overrides loaded in earlier stage of parsing `.csproj` file and then `.targets` overrides loaded around end of parse.
>
> `UnityEditor` override is loaded later than `UnityShared` and only loaded in Unity Editor environment ***except for building app in Editor***.


Expand Down

0 comments on commit cb660fd

Please sign in to comment.